/**
 * @fileOverview This file contains the static FormUtil class used to process forms for submission through ajax / dwr. 
 */

/**
 * @static
 * @namespace 
 * This FormUtil class provides helper methods for getting and setting form values.  This utility class
 * is built to work with DWR and its specific way of passing form data back and forth.
 */
m2.util.FormUtil = {
	/**
	 * The dwr string that represents a level within an object's structure.
	 * @type String
	 * @private
	 */
	_dataStructureIndicator : "$",

	/**
	 * Returns an array of elements matching the provided parameters.  If it's a string it checks if 
	 * there's an element matching the name first, then if there's a match by id.
	 * @method getElements
	 * @param {HTMLFormObject|String} el    the element or the name/id of the element we want to get
	 * @return {Array}                rarr  an array containing the elements matching the element
	 */
	getElements : function(el) {
		var els = document.getElementsByName(el);
		return (els && els.length > 0) ? els : [m2.$(el)];
	},
	
	/**
	 * Function will get and return the elements name or id if it exists
	 * @method getElementName
	 * @param  {HTMLObject}  el 
	 * @return {String|null}     the name/id value, or null if neither exists
	 */
	getElementName : function(el) {
		return (el.name != null && el.name != "") ? el.name : (el.id != null && el.id != "") ? el.id : null;
	},

	/**
	 * Checks to see if the passed in element is a form element that can be processed
	 * @method isValidFormElement
	 * @param {HTMLElement} el  the form element that we are processing
	 * @return {boolean}        true if a form element, false otherwise
	 */
	isValidFormElement : function(el) {
		return (m2.util.isHTMLElement(el,["select","textarea"]) || (m2.util.isHTMLElement(el,"input") && !(el.type == "button" || el.type == "submit")));
	},

	/**
	 * Gets the value of the passed in select box
	 * @method getSelectValue
	 * @param  {HTMLSelectElement}  el  the select we want the value of
	 * @return {String}                 the string value of the selected option
	 */
	getSelectValue : function(el) {
		var r = "";
		if (el.selectedIndex != -1) {
			r = el.options[el.selectedIndex].value;
		}
		return r || "";
	},

	/**
	 * Sets the value of the passed in select
	 * @method setSelectValue
	 * @param  {HTMLSelectElement}  el   the select element
	 * @param  {String}             val  the value we want to set in it
	 */
	setSelectValue : function(el,val) {
		if (!val || val == "") {return;}
		// cycle through the options and set the correct one to selected
		for (var i = 0; i < el.options.length; i++) {
			v = el.options[i].value;
			// use the option's value or check what the text of the option is
			v = (v == null || val == "") ? el.options[i].text : v;
			if (v == val) {
				el.options[i].selected = true;
				return;
			}
		} 
	},

	/**
	 * Gets the value of the passed in input
	 * @method getInputValue
	 * @param  {HTMLInputElement}  el  the input we want the value of
	 * @return {String}                the string value of the input
	 */
	getInputValue : function(el) {
		switch (el.type) {
			// if the element is a checkbox/radio return the value only if it's checked
			case "checkbox":
			case "check-box":
			case "radio":
				return el.checked ? el.value : "";
			default:
				return el.value || "";
		}
	},

	/**
	 * Sets the value of the passed in input
	 * @method setInputValue
	 * @param  {HTMLInputElement}  el   the input element
	 * @param  {String}            val  the value we want to set in it
	 */
	setInputValue : function(el,val) {
		if (!val && val != "") {return;}
		
		switch (el.type) {
			case "checkbox":
			case "check-box":
				el.checked = ((val == el.value) || ((m2.util.isArrayLike(val)) && (m2.util.indexOf(val,el.value) >= 0))) ? true : false;
				break;
			case "radio":
				el.checked = (el.value == val) ? true : false;
				break;
			default:
				el.value = (val || val == "") ? m2.util.unescapeHTML(val) : el.value;
				break;
		}
	},

	/**
	 * Gets value of the text area
	 * @method getTextAreaValue
	 * @param  {HTMLTextAreaElement}  el  the text area object
	 * @return {String}                   the string value of the text area 
	 */
	getTextAreaValue : function(el) {
		return el.value || "";
	},

	/**
	 * Sets the value of the text area
	 * @method setTextAreaValue
	 * @param  {HTMLTextAreaElement}  el  the text area object
	 * @param  {String}            val  the value we want to set in it
	 */
	setTextAreaValue : function(el,val) {
		el.value = (val || val == "") ? m2.util.unescapeHTML(val) : el.value;
	},

	/**
	 * Gets the value of the form element provided
	 * @method  getValue
	 * @param  {HTMLFormElement|String} el    the element or the name/id of the element we want to get the value/s of
	 * @return {String}                       the string value of the passed in el.  will be "" if it cannot find the value.
	 * 
	 * TODO: multi-select
	 */	
	getValue : function(el) {
		var els = this.getElements(el);
		if (!els) {return;}
		el = els[0];

		// the return value
		var rval = "";

		// if there are multiple elements that we need to get a value from (i.e. a name was passed in that matched
		// multiple elements, typically a radio button), process recursively to get the selected value.
		if (els.length > 1) {
			for (var i = 0; i < els.length ; i++) {
				rval = this.getValue(els[i]);
				if(rval != ""){break;}
			}
			return rval || "";
		}

		// get the value of the element if it's a valid form element
		if (m2.util.isHTMLElement(el,"select"))  {rval = this.getSelectValue(el);}
		if (m2.util.isHTMLElement(el,"input"))   {rval = this.getInputValue(el);} 
		if (m2.util.isHTMLElement(el,"textarea")){rval = this.getTextAreaValue(el);}
		
		return rval || "";
	},


	/**
	 * Sets the value of the element to the provided value
	 * @method setValue
	 * @param {HTMLFormElement|String} el     the element or the name/id of the element we want to get the value/s of
	 * @param {String|int}             value  the value we want to apply to the element
	 * 
	 * TODO: multi-select
	 */	
	setValue : function(el, value) {
		// get the elements matching the el passed in
		var els = this.getElements(el);
		if (!els) {return;}
		el = els[0];

		// if there are multiple elements that we need to set the value of (i.e. a name was passed in that matched
		// multiple entries), process recursively to set the value/s.
		if (els.length > 1) {
			for (var i = 0; i < els.length ; i++) {
				// recursive call
				this.setValue(els[i]);
			}
			return;
		}

		// set the value of the element if it's a valid form element
		if (m2.util.isHTMLElement(el,"select")) {this.setSelectValue(el,value);}
		if (m2.util.isHTMLElement(el,"input")){this.setInputValue(el,value);}
		if (m2.util.isHTMLElement(el,"textarea")){this.setTextAreaValue(el,value);}
	},
	
	
	/**
	 * Gets all the values in the form and puts it in a data object for processing
	 * @method getValues
	 * @param  {HTMLForm|String} form  the form element or form's id that we want to get the values out of
	 * @return {Object}                a singleton object represeting form data
	 */
	getValues : function(form) {
		// get the form
		var fel = m2.$(form);
		if (!fel || !m2.util.isHTMLElement(fel) || fel.elements == null) {
			return null;
		}

		// declare the temp values & return object
		var value,currentValue,key,keys,index,map,tempMap;
		var data = {};

		// loop through the form elements and process the values
		for (var i = 0; i < fel.elements.length; i++) {
			var el = fel.elements[i];
			// if not a valid form element skip to the next element in the loop
			if (!this.isValidFormElement(el)) {continue;}
			
			value = this.getValue(el);

			// get the name if it exists
			if ((key = this.getElementName(el)) == null) {
				key = "element" + i;
			}

			// names and ids might contain delimiters.  this indicates a nested data structure is
			// in place for this element.  
			keys = key.split(this._dataStructureIndicator);

			// Set the data map to the base level.  As the data structure replication proceeds this 
			// variable will keep our place as we move deeper into the data structure.
			map = data;

			// Replicate the data structure in the data object.
			if (keys.length > 1) {
				index = keys.length - 1;
				// loop through all the elements we need in the data structure
				for (var j = 0; j < index; ++j) {
					// get the first level of the data structure
					key = keys[j];
					tempMap = map[key];
					// if this next level in the structure doesn't exist, create it
					if (tempMap == null) {
						tempMap = {};
						map[key] = tempMap;
					}
					// move one level deeper into the data structure
					map = tempMap;
				}
				key = keys[index];
			}

			// get the current value set in the data structure
			currentValue = map[key];

			// if a current value exists and there is a new value, create an array of values for this data node
			if (currentValue != null && currentValue != "") {
				if (value != "") {
					// if the current value is not in an array, move it to an array
					if (! m2.util.isArray(currentValue)) {
						currentValue = [currentValue];
						map[key] = currentValue;
					}
					if (value != "") {
						// push the new value into the array
						currentValue.push(value);
					}
				}
			} else {
				// set the value of the 
				map[key] = value;
			}
		}
		return data;
	},


	/**
	 * Sets all the values in a form
	 * @method setValues
	 * @param {HTMLForm|String} form  the form element or form's id that we want to get the values out of
	 * @param {Object}          data  a singleton object represeting form data
	 */
	setValues : function(form,data) {
		// get the form
		var fel = m2.$(form);
		if (!fel || !m2.util.isHTMLElement(fel) || fel.elements == null) {return null;}

		var value,key,keys,index,map,tempMap;
		
		// loop through the form's elements and process
		for (var i = 0; i < fel.elements.length; i++) {
			var el = fel.elements[i];
			// if not a valid form element skip to the next element in the loop
			if (!this.isValidFormElement(el)) {continue;}

			// get the name/id if it exists
			if ((key = this.getElementName(el)) == null) {key = "element" + i;}
			
			// names and ids might contain slashes ("/").  this indicates a nested data structure is
			// in place for this element.  
			keys = key.split(this._dataStructureIndicator);
			
			// Set the data map to the base level.  As the data structure replication proceeds this 
			// variable will keep our place as we move deeper into the data structure.
			map = data;

			// move through the data structure to get to the correct value for this form element
			if (keys.length > 1) {
				index = keys.length - 1;
				for (var j = 0; j < keys.length; ++j) {
					key = keys[j];
					value = map[key];
					// if no value at this level, break
					if (value==null) {break;}
					// else move one level deeper into the data
					map = value;
				}
			} else {
				value = map[key];
			}

			// if there is a value set it in the form el
			if (value || value=="") {
				this.setValue(el, value);
			}
		}
	},
	
	
	/**
	 * Sets all the errors in the form with the error style
	 * @method setValues
	 * @param {Object}          errors      error object from dwr
	 * @param {HTMLForm|String} fel         the form element or form's id that we want to get the values out of
	 * @param {HTMLForm|String} msgWrapper  (optional) the id or element that will display the error message
	 * @param {String}          errorClass  (optional) the class that we will assign to show the errors
	 */
	markErrors : function(errors,fel,msgWrapper,errorClass) {
		// return if required stuff is missing
		if (!errors && !fel) {return;}
		
		// set the defaults
		errorClass = errorClass || "formError";
		fel = fel || document.body;

		// cycle through all the forms data and remove the previous errors
		var arr = m2.util.getElementsByClassName(errorClass,fel);
		for (var i = 0; i < arr.length; i++) {
			m2.util.removeClass(arr[i],errorClass);
		}
        
 		// build the error text and highlight the error
		var error,el,errorText = "";
        if (fel.elements) {
            // display errors in form field order
            for (var j = 0; j < errors.length; ++j) {
                // display errors with no field association
                field = errors[j].field;
                
                if (!field) {
                    errorText += errors[j].message + '<br/>';
                }
            }
            
            var els = fel.elements;
            var elName;
            var field;
            var fields = {};
            
            for (var i = 0; i < els.length; i++) {
                // display errors with field association
                el = els[i];
                elName = this.getElementName(el);
                
                for (var j = 0; j < errors.length; ++j) {
                    field = errors[j].field;
                    
                    // display error if...
                    if (field && // error is associated with field
                        (field == elName) && // field name matches
                        (!fields[field]) && // field does not already have an error displayed
                        (!el.style || el.style.display != "none") && // field node is not "hidden"
                        (!el.parentNode || !el.parentNode.style || el.parentNode.style.display != "none")) { // parent node (e.g., div) is not "hidden"
                        errorText += errors[j].message + '<br/>';
                        m2.util.addClass(el.parentNode, errorClass);
                        fields[field] = true;
                    }
                }
            }
        } else {
            // display errors in returned (random) order
            for (var i = 0; i < errors.length; i++) {
    			// add to the error text
                errorText += errors[i].message + '<br/>';
    
    			// set the class on the parent's parent node (most used for tr/td)
                if (errors[i].field) {
                    el = this.getElements(errors[i].field)[0];
                    m2.util.addClass(el.parentNode, errorClass);
                }
            }
        }

		// set the text
		if (msgWrapper) {
			m2.$(msgWrapper).innerHTML = errorText;
			m2.$(msgWrapper).className = errorClass;
		}
	},
    
    /**
     * Adds a hidden field to the given form.
     * @param  {HTMLForm} form  the form element that we want to get the values out of
     * @param {String} name     field name
     * @param {String} value    field value
     */
    addHiddenField : function(form, name, value) {
        var el = document.createElement("input");
        el.type = "hidden";
        el.name = name;   
        el.value = value;
        form.appendChild(el);
    },

    /**
     * Gets whether or not the given object is an HTML form.
     * 
     * @param  {HTMLForm} form  the form element that we want to get the values out of
     * @return true if the object is a form
     */
    isHTMLForm : function(obj) {
        var reset = typeof obj.reset;
        return ((reset == 'function') || (reset == 'object'));
    },
    	
    /**
     * @param  {HTMLForm|Object} form  the form element that we want to get the values out of or the form data itself
     * @return {Object}                a singleton object representing form data in the format expected by DWR services
     */
    getValuesForDWR : function(form) {
        if (this.isHTMLForm(form)) {
            // HTML form.
            return {name:form.name,data:dojo.toJson(this.getValues(form))};
        } else {
            // Form data.
            return {name:form.name,data:dojo.toJson(form.data)};
        }
    }
};
