/**
 * Allows tables containing form elements to have rows dynamically added and removed.
 * This also works on sets of arbitrary elements.
 *
 * @copyright Camna, LLC.
 * @author Aaron Birchler <abirchler@camna.com>
 */

;(function($) {

	// Options
	$.formTableOptions = {
		removeRowSelector:				'.remove_row_button', // Selector used to find elements used to remove elements
		removeTemplateRow:				true, // Whether or not to remove the template row from the document after initially cloning it
		addRowSelector:						'.add_row_button', // Selector used to find elements used to add elements
		formTableSectionSelector: '.form_table_section',
		clonedRowClass:						'cloned_row', // Class to apply to rows that are contained by the container of the cloned elements
		rowSelector:        			'tr, .form_table_row',
		templateRowSelector:			':last', // Selector used to filter the children of the container and determine the element to use as the template for cloned ones	
		clonedFieldPrefix:				'', // String to preprend to cloned form elements
		resetClonedFields:				'auto', // Whether or not to reset form elements within cloned elements
		fieldIncrementValue:			0, // The form element index to use for the first cloned form elements
		containerSelector:				'.form_table_container, tbody', // Selector used to find the element that contains the template element and which the cloned elements will be added to
		minCount:                 0,
		maxCount:                 null,
		arrayDepth:               0, // Used to determine what index to increment when renaming a field
		removeConfirmation:       "",
		setFieldNamesArr:					function(incVal, depth){
		
			$(this).attr('name', function(){
			
			  var depthPattern = "";
			  var endNum = 2;
			  
			  if (depth > 0){
			    depthPattern = "(\\[\\-?\\d*\\]\\[\\D*\\]){"+depth+"}";
			    endNum = 3;
			  }
			
        var pattern = "^([^\\[]+"+depthPattern+")\\[\\-?\\d*\\](.*)$";
        var regexp = new RegExp(pattern);
        var replace = '$1['+incVal+']$'+endNum;
        
        return $(this).attr('name').replace(regexp, replace);
			})
		},
		setFieldIdsArr:						function(incVal, depth){
		  
			$(this).attr('id', function(){
			
				var id = $(this).attr('id');
				
			  var pattern = "^(\\D+)_-?\\d+(\\D.*)?$";
        var replace = "$1_"+incVal+"$2";
			
			  if (depth > 0){
			    pattern = "^(\\D+(?:_-?\\d+){"+depth+"})_-?\\d+(\\D.*)?$";
			  }
			
        var regexp = new RegExp(pattern);
			
        return id.replace(regexp, replace);
			})
		},
		dependencySelector:				'',
		dependentFieldFn:					function(options){
		
			var deps = $(options.dependencySelector);
			
			deps.each(function(){
			
				$(this).append('<option value="test">TEST</option>');
				this.style.color = "green";
			});
		},
		newRowCallback:						function(){}
	};

  $.expr[':'].formTable = function(obj, index, meta, stack){
  
    var matched = $(obj).data("formTableElement") ? true : false;
    
    return matched;
  };

  $.expr[':'].formTableContainer = function(obj, index, meta, stack){
  
    var matched = $(obj).data("formTableContainer") ? true : false;
    
    return matched;
  };

  $.expr[':'].formTableRow = function(obj, index, meta, stack){
  
    var matched = $(obj).data("formTableRow") ? true : false;
    
    return matched;
  };

	/**
	 * Remove values from form elements
	 * This is useful when cloning a row with values already set in the form elements
	 * @return object
	 * @author Aaron Birchler <abirchler@camna.com>
	 */
	$.fn.clearFormElements = function() {
		
		this.each(function(){
		
			$(this).find('input:text, input:password, textarea, select').val('');
		});
		
		return this;
	}
	
	/**
	 * Changes form element name attributes so that they are associated with the correct row when submitted
	 * @param array inputs An array of form elements
	 * @param int incVal The index to assign to the form elements. This is optional, and if it is left undefined, the current increment value will be used.
	 * @return void
	 * @author Aaron Birchler <abirchler@camna.com>
	 */
	$.fn.setFormFieldNames = function(inputs, incVal){
	
    var n = this.length;
    
    if (n == 1){
    
      var options = this.data('formTableOptions');

      if (incVal === undefined){
        incVal = this.data("fieldIncrementValue");
      }
          
      if (options.arrayDepth == 0) {
  		  var formTables = this.parents(":formTable"); // Determines the depth of the form table
  		  depth = formTables.length - 1;
  		}
  		else {
  		  depth = options.arrayDepth;
  		}
  		
  		var numInputs = inputs.length;
  		
  		for (var i=0; i<numInputs; i++){
  		  options.setFieldNamesArr.call(inputs[i], incVal, depth);
  		}
    }
    
    return this;
	}
	
	/**
	 * Changes element id attributes
	 * @param array elements An array of elements that need to have their ids changed
	 * @param int incVal The index to assign to the elements. This is optional, and if it is left undefined, the current increment value will be used.
	 * @return void
	 * @author Aaron Birchler <abirchler@camna.com>
	 */
	$.fn.setFormFieldIds = function(elements, incVal){
	
    var n = this.length;
    
    if (n == 1){
    
      var options = this.data('formTableOptions');
      
      var incVal;
      
      if (incVal === undefined){
        incVal = this.data("fieldIncrementValue");
      }
    
      if (options.arrayDepth > 0) {
  		  var formTables = this.parents(":formTable"); // Determines the depth of the form table
  		  depth = formTables.length - 1;
  		}
  		else {
  		  depth = options.arrayDepth;
  		}
  		
  		var numElements = elements.length;
  		
  		for (var i=0; i<numElements; i++){
  		  options.setFieldIdsArr.call(elements[i], incVal, depth);
  		}
    }
    
    return this;
	}
	
	/**
	 * Clones the template table's template row and appends it to the table
	 * @return object
	 * @author Aaron Birchler <abirchler@camna.com>
	 */
	$.fn.addRow = function() {
	
		this.each(function(){
		
			var o = $(this);
		
		  var options = o.data("formTableOptions");
		
			if (options && o.is(options.containerSelector)){
			
        var evt = $.Event("formTable:addRow");
        o.trigger(evt);
        
        if (!evt.isDefaultPrevented()){
  				
  				var f = o.data('templateFunc');
  				
  				var template = f(this);
  				
  				var secIncVal = o.data('fieldIncrementValue');
  				
  				var newRow = template.clone(true);
  				newRow.data("formTableRow", true).data("fieldIncrementValue", secIncVal);
  				
  				var elements = newRow.find('*[id]').andSelf();
  				var inputs = newRow.find('input, textarea, select, button');
  				
  				o.setFormFieldIds(elements).setFormFieldNames(inputs);
  				
  				o.data('fieldIncrementValue', secIncVal+1);

  				newRow.find('input, textarea, select, button').each(function(){
  					var oldName = $(this).attr('name');
  					$(this).attr('name', options.clonedFieldPrefix + oldName);
  				});

  				if (options.resetClonedFields != false) newRow.clearFormElements();
  				
  				newRow.appendTo(this);
  				  				
  				if (options.dependencySelector != '') {
  				
  					options.dependentFieldFn.apply(this, [options]);
  				}
  
  				var callback = options.newRowCallback;
  				
  				if ($.isFunction(callback)) {
  				
  					callback.apply(newRow);
  				}
  				
  				evt = $.Event('formTable:rowAdded');
  				evt.incrementValue = secIncVal;
  				newRow.trigger(evt);
  		  }
			}
		});
		
		return this;
	};
	
	/**
	 * Removes the table row that contains the element
	 * @param object
	 * @return object
	 * @author Aaron Birchler ,aaron@aaronbirchler.com>
	 */
	$.fn.removeRow = function(options) {
	
		this.each(function(){
		
			var targetRow = $(this).closest(":formTableContainer > *");

			var evt = $.Event('formTable:removeRow');
			targetRow.trigger(evt);
			
			if (!evt.isDefaultPrevented()){
		    var parent = targetRow.parent();
  			targetRow.remove();
  		  parent.trigger("formTable:rowRemoved");
  		}
		});
		
		return this;
	}
	
	/**
	 * Clones the row that any new rows will use as a tempmlate
	 * The default behavior is to copy the last row of the table.
	 * @return object
	 * @author Aaron Birchler <abirchler@camna.com>
	 */
	$.fn.copyFormTableRow = function(options) {
	
		this.each(function(){
		
		  var t = $(this);
		
      var isTemplateRowRemoved = false;
		
			if (t.is(options.containerSelector)){
			
        var templateFunc;
        
				if (options.templateRowSelector.charAt){

			    var r = t.children(options.templateRowSelector);
			    var tmplt = r.clone(true);
  				if (options.removeTemplateRow){
  				  r.remove();
  				  isTemplateRowRemoved = true;
  				}

				  templateFunc = function(){return r;};
				}
				
        else if ($.isPlainObject(options.templateRowSelector.tagName)){

			    var r = $(options.templateRowSelector);
			    var tmplt = r.clone(true);
  				if (options.removeTemplateRow){
  		      r.remove();
            isTemplateRowRemoved = true;
  				}

				  templateFunc = function(){return r;};
				}
				
				else if ($.isFunction(options.templateRowSelector)){
				  templateFunc = options.templateRowSelector;
				}
				
				else{
				  templateFunc = function(){};
				}
				
        var incVal = options.fieldIncrementValue;
        
        // If the template row was not removed from the container--it still exists, so the increment value
        // is incremented to prevent two rows from having the same increment value
        // if (!isTemplateRowRemoved) incVal = incVal + 1;
			
				t.data('fieldIncrementValue', incVal).data('templateFunc', templateFunc);

        // At this point, any existing rows in the container will have increment values set.
        // The increment values for these rows start at one less than the template's increment value
        // and decrement as they become earlier in the container.
        var rows = t.children();
        var n = rows.length;
        
        rows.each(function(i){
          $(this).data("fieldIncrementValue", incVal-n+i).data("formTableRow", true);
        });
			}
		});
		
		return this;
	}
	
	/**
	 * Initializes a form-table section so that it can have rows dynamically added and removed
	 * @param object userOptions
	 * @return object
	 * @author Aaron Birchler <abirchler@camna.com>
	 */
	$.fn.extend({initFormTable: function(userOptions) {
	
		var options = $.extend({}, $.formTableOptions, userOptions);

    $(this).data("formTableElement", true);			 

		$(this).each(function(){
		
			var tbody = $(this).find(options.containerSelector).first();
			tbody.data("formTableContainer", true);
			
			tbody.copyFormTableRow(options);
		 
			var addButtons = $(this).find(options.addRowSelector);
		 
			addButtons.data('form_table_tbody', tbody);
			
			tbody.data('setFieldNamesArr', options.setFieldNamesArr)
			     .data('setFieldIdsArr', options.setFieldIdsArr)
			     .data('fieldIncrementValue', options.fieldIncrementValue)
			     .data('newRowCallback', options.newRowCallback)
			     .data('formTableOptions', options);
		 
			$(this).find(options.addRowSelector).click(function(evt){
        
        evt.preventDefault();
				var tbody = $(this).data('form_table_tbody');
				tbody.addRow();
			});

			$(this).find(options.removeRowSelector).live('click', function(evt){

				evt.preventDefault();
				
				if (!options.removeConfirmation || (options.removeConfirmation && confirm(options.removeConfirmation))) {
  				$(evt.target).removeRow(options);
  		  }
			});
		 
			if (options.dependencySelector !== '') {

				var dependentSection = $(options.dependencySelector);
			}
		});

		return this;
	}});
	
})(jQuery)
