$(function() {
	// Ensures AJAX requests are properly formated so that Rails knows how to respond to them.
	$.ajaxSetup({
	  beforeSend: function(xhr) {xhr.setRequestHeader("Accept", "text/javascript")}
	});

	/* Ensures AJAX requests have the proper Rails authenticity token. See the following for more info:
	   http://henrik.nyh.se/2008/05/rails-authenticity-token-with-jquery
	   http://dev.jquery.com/ticket/3387
	*/
	$(document).ajaxSend(function(event, request, settings) {
	  var csrfParam = $("meta[name=csrf-param]").attr("content");
	  var csrfToken = $("meta[name=csrf-token]").attr("content");
	  if (csrfParam != null && csrfToken != null) {
	    settings.data = settings.data || '';
	    settings.data += (settings.data ? "&" : '') + [csrfParam, encodeURIComponent(csrfToken)].join('=');
	  };
	});
});

(function($) {
  // Global plugin settings.
  var settings = null;
  
  /* Compares the first number found in a pair of strings in order to determine sort order.
   * @a - The first number.
   * @b - The second number.
   */
  function compareFirstNumber(a, b) {
  	a = new Number(a.match(/\d+/));
  	b = new Number(b.match(/\d+/));
  	if (a < b) { return -1; }
  	if (a > b) { return 1; }
  	return 0;
  }

  /* Chops off the last string segment designated by delimiter.
   * If no delimiter is found then the original string is returned instead.
   * @string - Required. The string to chop.
   * @delimiter - Optional. The delimiter used to chop up the string. Defaults to '_'.
   */
  function stringChop(string, delimiter) {
  	var chopped = string;
  	if (delimiter == undefined) { delimiter = '_'; }
  	var endIndex = string.lastIndexOf(delimiter);
  	if (endIndex > 1) {chopped = string.slice(0, endIndex);}
  	return chopped;
  }

  /* Answers the ID found in the string based off of a certain delimiter.
   * @string - Required. The string to obtain the ID from.
   * @delimiter - Optional. The delimiter used to distinquish the ID from the string. Defaults to: '_'.
   */
  function getId(string, delimiter) {
  	var id = string;
  	if (delimiter == undefined) { delimiter = '_'; }
  	var endIndex = string.lastIndexOf(delimiter) + 1;
  	if (endIndex < string.length) {id = string.slice(endIndex);}
  	return id;
  }

  /* Replaces an existing number within a string with a new number based on position in the string (either first or last postion).
   * @string - The string for which to replace an old number with a new number.
   * @newNumber - The new number to be replaced in the string.
   * @position - The position of the old number in the string to be replaced.
   */
  function replaceNumber(string, newNumber, position) {
    if (string != undefined) {
      if (position == undefined) { position = 0; }
  	  var numbers = string.match(/\d+/g);
  	  if (numbers != null && numbers.length > 1) {
  	    var oldNumber;
  			var index;
  			switch(position) {
  				case 0:
  				  oldNumber = numbers[0];
  					index = string.indexOf(oldNumber);
  					break;
  				case 1:
  				  oldNumber = numbers.reverse()[0];
  					index = string.lastIndexOf(oldNumber);
  					break;
  			}
  			var prefix = string.substring(0, index);
  			var suffix = string.substring(index + oldNumber.length, string.length);
  			string = prefix + newNumber + suffix;
  	  }
    }
    return string;
  }

  /* Increments the first or last number in a string (if any, defaults to "first").
   * If no number is found then the original string is returned.
   * @string - The string to manipulate.
   * @position - The position of the number (first or last) to increment.
   */
  function incrementNumber(string, position) {
    if (string != undefined) {
  		if (position == undefined) { position = "first"; }
  	  var numbers = string.match(/\d+/g);
  	  if (numbers != null) {
  			var oldNumber;
  			var newNumber;
  			var index;
  			switch(position) {
  				case "first":
  					oldNumber = numbers[0];
  					newNumber = new Number(oldNumber) + 1;
  					index = string.indexOf(oldNumber);
  					break;
  				case "last":
  					oldNumber = numbers.reverse()[0];
  					newNumber = new Number(oldNumber) + 1;
  					index = string.lastIndexOf(oldNumber);
  					break;
  			}
  			var prefix = string.substring(0, index);
  			var suffix = string.substring(index + oldNumber.length, string.length);
  			string = prefix + newNumber + suffix;
  	  }
  	}
  	return string;
  }

  /* Builds an array of all body element attributes (if not already set).
   * @attribute - The attribute to find.
   */
  function buildBodyAttributes(attribute) {
  	bodyAttributes = $.map($("body").find('[' + attribute + ']'), function(element) {
  		return $(element).attr(attribute);
  	});	
  	return bodyAttributes;
  }
  
  /* Makes an array of attributes unique by comparing each attribute in the array against all body element attributes.
   * @attributes - The attributes to make unique.
   * @attribute - The attribute to search for.
   * @index - The starting index in the arravy of attributes.
   */
  function uniquifyAttributes(attributes, attribute, index) {
  	if (index < attributes.length) {
  		var numbers = attributes[index].match(/\d+/g);
  		var strings = attributes[index].split(/\d+/);
  		if (numbers != null && numbers.length == 1) {
  			var constant = attributes[index].split(/\d+/).join('-');
  			var familiars = [];
  			var bodyAttributes = buildBodyAttributes(attribute);
  			for (x in bodyAttributes) {
  				var current = bodyAttributes[x].split(/\d+/).join('-');
  				if (current == constant) {
  					familiars.push(bodyAttributes[x]);
  				}
  			}
  			var last = incrementNumber(familiars.sort(compareFirstNumber).pop());
  			attributes.splice(index, 1, last);
  			bodyAttributes.push(last);
  		}
  		index++;
  		uniquifyAttributes(attributes, attribute, index);
  	}
  	bodyAttributes = null;
  	return attributes;
  }

  /* Ensures all child element attibutes are unique for a given root element.
   * @element - TODO...
   * @attribute - TODO...
   * @selector - TODO...
   */
  function uniquifyChildren(element, attribute, selector) {
  	// Acquire the child elements.
  	var children = $(element).find(selector);
  	var attributes = [];
  	// Extract the attributes from the child elements.
  	children.each(function() {
  		attributes.push($(this).attr(attribute));
  	});
  	// Make the attributes unique.
  	attributes = uniquifyAttributes(attributes, attribute, 0);
  	// Update the child elements with the unique attributes.
  	for (x in attributes) {
  		$(children[x]).attr(attribute, attributes[x]);
  	}
  }
  
  /* Generates a hidden input field based off original input field data that instructs ActiveRecord to delete
   * a record based on the ID of the input field.
   * @input - The input element from which to build the deletion input element.
   */
  function generateDestroyInput(input) {
  	if (input == undefined || $(input).length) {
  		$(input).attr("id", stringChop($(input).attr("id")) + "__destroy");
  		$(input).attr("name", stringChop($(input).attr("name"), '[') + "[_destroy]");
  		$(input).val(1);
  		return input;
  	} else {
  		return null;
  	}
  }

  /* Animates the deletion of a DOM element. Defaults to simply fading and hiding the element from view but
   * can be forced to remove the element from the DOM completely.
   * @element - The element to destroy.
   * @remove - Boolean for whether to remove or just hide the element.
   */
  function animateDestroy(element, remove) {
  	if (remove == undefined) { remove = false; }
  	$(element).fadeOut(500, function() {
  		if (remove) { $(this).remove(); }
  	});
  }

  /* Executes the new action.
   * @element - The element that spawned the new action.
   */
  function newInputAction(element) {
    // Outer group ID
    var ogid = '#' + $(element).attr("data-ogid");
    // Inner group ID
    var igid = '#' + $(element).attr("data-igid");
    // Position
    var position = $(element).attr("data-position");

    if ($(ogid).hasClass("hidden")) {
      $(ogid).removeAttr("style").fadeIn(500, function() {
        $(this).removeClass("hidden");
      });
    	// Delete hidden metadata (if any).
    	$(settings.recordSelector + ":visible input:hidden[id$=__destroy]").remove();
    } else {
  		var count = $(igid).children(settings.recordSelector + ":visible").size();
    	var record = $(igid).children(settings.recordSelector + ":visible:last").clone(true);
    	record.attr("id", uniquifyAttributes([record.attr("id")], "id", 0).toString());
    	$(igid).append(record);
    	// Remove excess cloned records.
    	var records = $.makeArray($(record).find(settings.recordSelector));
    	if (records.length > 1) {
    	  records.shift();
    	  for (x in records) {
    	    $(records[x]).remove();
    	  }
    	}
    	// Ensure the cloned children are unique.
    	uniquifyChildren(record, "id", "[id]:not([id*=attributes])");
    	uniquifyChildren(record, "for", "[for]");
    	uniquifyChildren(record, "data-ogid", "[data-ogid]");
    	uniquifyChildren(record, "data-igid", "[data-igid]");

    	// Increment the cloned, nested children.
      $(record).find("[id*=attributes]").each(function() {
        if (position == 0) {
          $(this).attr("id", replaceNumber($(this).attr("id"), count));
        }
        $(this).attr("id", incrementNumber($(this).attr("id"), "last"));
        return this;
      });
      $(record).find("[name*=attributes]").each(function() {
        if (position == 0) {
          $(this).attr("name", replaceNumber($(this).attr("name"), count));
        }
        $(this).attr("name", incrementNumber($(this).attr("name"), "last"));
        return this;
      });

    	// Clear inputs.
    	$(record).find("input:visible").val('');
    	$(record).find("select:visible").val('');
    	// Delete hidden metadata as long as it is not marked for preservation (i.e. data-clone = true);
    	$(record).find("input:hidden[id$=_id]").each(function() {
				if ($(this).attr("data-clone") == undefined || $(this).attr("data-clone") == false) {
					$(this).remove();
				}
			});
    }

  	return false;	
  }
  
  // Executes the destroy action.
  function destroyAction(element, message) {
		var result = confirm(message);
		if (result) {
			$.post($(element).attr("href"), "_method=delete");
			animateDestroy($(element).closest(settings.recordSelector + ":visible"));
	  }
		return false;
  }

  // Executes the destroy input action.
  function destroyInputAction(element) {
    var group = $(element).closest(settings.groupSelector);
    var record = $(element).closest(settings.recordSelector);
    // Create hidden deletion input from original identifier input so Rails knows to delete the record.
    // NOTE: The following three lines are a workaround to a Safari and IE bug where the hidden input
    // is not found using: $(record).prev("input");
    var idName = stringChop($(record).attr("id"));
    var idNumber = getId($(record).attr("id"));
    var hiddenInput = $(group).find("input:hidden").filter("[name$=[id]][value=" + idNumber + ']');
    
    if (hiddenInput.length > 0) {
      $(record).prepend(generateDestroyInput($(hiddenInput).clone()));
    }
    // Remove the record from view or hide entire group if only one record left.
		if ($(group).find(".record:visible").size() > 1) {
      if (hiddenInput.length > 0) {
        animateDestroy(record);
      } else {
        animateDestroy(record, true);
      }      
		} else {
			$(record).find("input:visible").val('');
			$(record).find("select:visible").val('');
    	$(group).fadeOut(500, function() {
  			$(group).addClass("hidden");
    	});    	
		}
		return false;    
  } 
  
  // Plugin
  $.rest = function(options) {
    settings = $.extend(true, {}, $.rest.defaults, options);
    // New
    $(settings.newSelector).live("click", function() {
      return $(this).attr("data-type") == "input" ? newInputAction(this) : true;
    });
    // Edit
    $(settings.editSelector).live("click", function() {
      // TODO - Need to supply confirm/warning dialog here.
      return true;
    });
    // Destroy
    $(settings.destroySelector).live("click", function() {
      return $(this).attr("data-type") == "input" ? destroyInputAction(this) : destroyAction(this, settings.destroyConfirm);
    });
    return false;
  };
  	  
  // Plugin Public Defaults
  $.rest.defaults = {
    newSelector: "a.new",
    editSelector: "a.edit",
    destroySelector: "a.destroy",
    destroyConfirm: "Are you sure you want to delete this?",
    groupSelector: ".group",
    recordSelector: ".record"
  };
}) (jQuery);
