/* ---------------------------------
  @ CingoKit.Form.Manager
  
  Registers a form with the CingoKitKit form library.  When the form is registered, it will inherit
  functions to control validation, highlighting, and packaging of data to prep for Ajax handling
  
  Purposes of this object:
  - register the form
  - register each form element
  - set up functions to allow for custom form validation
----------------------------------- */

// dialog constructor
CingoKit.Form.Manager = function ( properties )
{
	// register baseclass
	this._class = CingoKit.Form.Manager;
	
	// initialize
	this.init(properties);
};


// static vars
CingoKit.Form.Manager.validNodeNames = new Array(
'input',
'select',
'textarea'
);


// prototype structure
CingoKit.Form.Manager.prototype = {

	init : function( properties )
	{
		// defaults
		this.registration = new Array;
		
		// store properties for this dialog
		this.applyObject(properties);	

		// store obj ref in parent element
		this.elem._obj = this;
		
		// create a validation error function in the mgr if it's not there now
		this.mgr.onValidateError = isUndefinedOrNull(this.mgr.onValidateError) ? new Function() : this.mgr.onValidateError;
		
		// create a pre-submission custom validation routine if not present
		this.mgr.customValidation = isUndefinedOrNull(this.mgr.customValidation) ? function() { return true } : this.mgr.customValidation;
				
		// mark as available by default
		this.markAsAvailable();
		
		// set up comparisions validation array
		this._compareValidations = new Array();
		
		// determine if this form is inside a CingoKit.modal (extra functionality needs to be applied for validation)
		this.inmodal = !isUndefinedOrNull(this._modal);
		
		// if hidden elements, create those
		if (!isUndefinedOrNull(this.hidden)) {
			for (var i in this.hidden) {
				
				// create the hidden field based on the incoming data
				this.createHiddenField(i, this.hidden[i]);
				
			};
		};
		
		// register elements in this form
		this.registerChildren();

		// set up submission
		this.allowMultipleSubmissions();
		this.setSubmissionMethod(this.submitmethod);
		
		// deny cancel disabling on valid submission
		this.denyCancelDisablingOnSubmit();

		// by default, deny clearing on ajax submissions
		this.denyClearOnAjaxSubmit();
	},
	
	refresh : function()
	{
		this.registerChildren();
	},
	
	// recursive registration of elements
	registerChildren : function()
	{ 
		
		for (var i=0; i<=this._class.validNodeNames.length; i++) {
			
			var nodeName = this._class.validNodeNames[i];
			var matches = this.elem.getElementsByTagName(nodeName);

			for (var j=0; j<matches.length; j++) {

				// get the node
				var node = matches[j]
				
				// set register flag
				var valid =  !(node.name == prev && node.type == "radio");
				
				if (valid) {

					// keep duplicate radio button entries from being registered
					var prev = node.name;

					// test the node
					if (!this.nodeIsRegistered(node)) {

						// register the element...
						var element = this.register (
							new CingoKit.Form.Element ({
								'elem' : node,
								'mgr' : this,
								'initiatingObj' : this.mgr,
								'_modal' : (this.inmodal ? this._modal : null)
							})
						);	
						
						// if the type is input and is manually set as the submit button, set it
						if (nodeName == "input") {
							if ( !isNull(getNodeAttribute(node, 'submitbutton')) ) {
								this.setSubmitButton(element);
							};
						};
						
					};
				};
			};
		};
	},
	
	// registration setting/access functions
	nodeIsRegistered : function( node )
	{
		var returnValue = (node._registered == undefined) ? false : node._registered;
		return returnValue;
	},
	
	register : function ( obj )
	{
		this.registration.push(obj);
		return obj;
	},
	
	unregister : function( obj )
	{
		// set up a temporary array
		var temp = new Array();

		// loop through reg and find the one to remove and skip adding that to the temp
		while (isNotEmpty(this._registration))
		{
			var testObj = this._registration.shift();
			if (obj != testObj) { temp.push(testObj) };
		};

		// clone _registration to temp and delete temp
		return temp;
	},
	
	destroyElement : function( obj )
	{
		this.unregister(obj);
		delete(obj);
		if (obj) { obj = null };
	},
	
	// create hidden fields to store data for action-based form submission
	createHiddenField : function( name, value )
	{
		appendChildNodes( this.elem, INPUT({ 'type' : 'hidden', 'name' : name, 'value' : value }) );
	},
	
	
	
	// reset and clearing
	clearAllElements : function()
	{
		for (var i=0; i<this.registration.length; i++) {
			var elem = this.registration[i];
			elem.clearAndResetElement();
		};
	},
	
	unerrorAllElements : function()
	{
		for (var i=0; i<this.registration.length; i++) {
			var elem = this.registration[i];
			elem.unerrorElement();
		};
	},
	
	// before form submission, erase all elements that still have original values set
	convertOriginalValues : function()
	{
		for (var i=0; i<this.registration.length; i++) {
			var elem = this.registration[i];
			elem.convertOriginalValue();
		};
	},
	
	
	// function to map JSON data to fields
	mapJSONDataToFields : function(json)
	{
		for (i in json) {
			if (typeof(i) == "string") {
			
				// extract json data
				var name = i;
				var value = json[i];
				var type = typeof(json[i]);
				
				// test for matching field
				var member = this.getMemberByName(name);
				
				if (!isNull(member) && !isNull(value)) {
				
					// apply the value to the form element
					member.setElementValue( value );
					
					// make sure it's active
					member.activateElement();
					
				};
				
			};
		};
	},
	
	
	// cancel button
	setCancelButton : function( obj ) { this._cancelButton = obj; },
	getCancelButton : function() { return this._cancelButton },
	
	// submit button
	setSubmitButton : function( obj ) { this._submitButton = obj },
	getSubmitButton : function() { return this._submitButton },
	
	
	// enable cancellation of the cancel button on form submission
	allowCancelDisablingOnSubmit : function() { this._allowCancelDisabling = true },
	denyCancelDisablingOnSubmit : function() { this._allowCancelDisabling = false },
	
	// cancel the double-form-submission check
	allowMultipleSubmissions : function() { this._allowMultipleSubmissions = true },
	denyMultipleSubmissions : function() { this._allowMultipleSubmissions = false },
	
	// clear the form on ajax submissions
	denyClearOnAjaxSubmit : function() { this._clearOnAjaxSubmit = false },
	allowClearOnAjaxSubmit : function() { this._clearOnAjaxSubmit = true },
	clearOnSubmit : function()
	{
		if (this._clearOnAjaxSubmit) {
			this.clearAllElements();
		};
	},
	
	
	// form submission handling
	setSubmissionMethod : function( method )
	{
		switch(method)
		{
			// action - just validate the form, and return to the action url if approved...
			case 'action':
				
				this.elem.onsubmit = function()
				{
					if (this._obj.isSubmitEnabled()) {
					
						// do standard validation
						var validated = this._obj.validate();

						// run custom validation if present
						if (validated) {
							var validated = this._obj.mgr.customValidation();
						};
						
						if (validated) {
							// convert all originalvalues to blank
							this._obj.convertOriginalValues();
						};
					
					} else {
						var validated = false;
					};
					
					return validated;
				};
				break;
			
			// ajax - validate the form, package up necessary values into querystring, and return them
			case 'ajax':
			
				// first, manually set the method attribute on the form back to "post"
				setNodeAttribute(this.elem, 'method', 'post');

				this.elem.onsubmit = function()
				{
					// do standard validation
					var validated = this._obj.validate();
					
					// run custom validation if present
					if (validated) {
						var validated = this._obj.mgr.customValidation();
					};
					
					if (validated) {
						// convert all originalvalues to blank
						this._obj.convertOriginalValues();
						
						// call the ajax function, passing along the querystring
						this._obj.submitAjax();
						
						// clear all fields if set to do so
						this._obj.clearOnSubmit();
					};
					
					// always return false on ajax functions so the form action is never called
					return false;
				};
				break;
				
			default:
				break;
		};
	},
	
	submit : function()
	{
		// reroute function to submit the form element
		if (!isUndefinedOrNull(this.elem)) {
			this.elem.onsubmit();
		};
	},
	
	submitAjax : function()
	{
		// build the querystring
		var qs = this.buildQuerystring();
		
		// build the func
		this._callbackfunc = !isNull(this.callback) ? this.callback.src[this.callback.func] : null;
		
		if (!isNull(this.ajax)) {
			
			// run ajax submission externally, passing on the querystring...
			this.ajax.src[this.ajax.func]( this.elem.action, qs );
			
		} else {
			
			// call directly the action as an ajax call
			var url = this.elem.action;

			// -- begin ajax call ----------------
			var self = this;
			var d = loadXMLDoc( url, qs );
			d.addErrback( function( err ) { if (err instanceof CancelledError) { return };  logError(err); });
			// -- end ajax call ---------------
			
		};
	},
	
	validate : function()
	{
		if (this.isAvailable() || this._allowMultipleSubmissions) {
			
			this.markAsBusy();
			
			var validated = true;
			
			// loop through all fields to test their validation status
			for (var i=0; i<this.registration.length; i++) {
				var elem = this.registration[i];
				var elemIsValid = elem.validateElement();

				// if element does not return valid, then set validated status to false, but keep checking so all invalid forms are highlighted
				if (!elemIsValid) {
					validated = false;
					break;
				};
			};
			
			// if still validated, then run compare validations
			if (validated) {
				
				//logDebug("----------------------------------");
				//logDebug("Run compare validations: " + this._compareValidations);
					
				if (this._compareValidations.length == 0) {
					//log("No compare validations");
				};
			
				for (var i=0; i<this._compareValidations.length; i++) {
					
					// test for match and reveal error appropriately
					var validated = this.evaluateCompareValidation( this._compareValidations[i] );
					if (!validated) { break };
					
				};
			};
			
			if (!validated) {
				
				// at form-submission level, mark the form as available if any element is not validated
				this.markAsAvailable();
				
			} else {
				
				// disable the cancel button
				if (this._allowCancelDisabling && !isUndefined(this.getCancelButton())) {
					this.getCancelButton().disableElement();
				}
				
			};
			
			// fire off onValidateError in mgr if not validated
			if (!validated) {
				this.mgr.onValidateError();
			};

			return validated;
			
		} else {
			alert("Form is currently submitting.  Please wait.");
			return false;
		}
	},
	
	
	// custom validation
	addCompareValidation : function( element1, element2, msg )
	{
		this._compareValidations.push({
			'element1' : element1,
			'element2' : element2,
			'msg' : msg
		});
	},
	
	evaluateCompareValidation : function( comparison )
	{
		var matched = true;
		
		// extract out parts
		var elem1 = comparison.element1;
		var elem2 = comparison.element2;
		var msg = comparison.msg;
		logDebug("     Compare " + elem1.getElementValue() + " - TO - " + elem1.getElementValue());
			
		// only compare if both are visible...
		if (elem1.isElementVisible() && elem2.isElementVisible()) {
			if (elem1.getElementValue() != elem2.getElementValue()) {
			
				// error out each element
				elem1.errorElement();
				elem2.errorElement();
				
				// reveal the message based on the latest...
				elem1.revealMessage( msg, 'error' );
				
				// set the matched flag to false
				matched = false;
			};
		};
				
		return matched;
	},
	
	
	
	// function build querystring from each member's value
	buildQuerystring : function()
	{
		var qs = new Object();
		
		// loop through each element, getting its value
		for (var i=0; i<this.registration.length; i++) {
			var element = this.registration[i];
			
			if (!element.isButtonElement()) {
				var name = element.getElementName();
				var value = element.getEscapedElementValue();
				qs[name] = value;	
			};
			
		};
		return qs;
	},
	
	
	// batch apply functions
	setTextHandlers : function( elements )
	{
		for (var i=0; i<elements.length; i++) {
			elements[i].setTextHandlers();
		}
	},
	
	
	
	
	// access functions to get objects
	markAsBusy : function()
	{
		this.issubmitting = true;
	},
	
	markAsAvailable : function()
	{
		this.issubmitting = false;
	},
	
	isAvailable : function()
	{
		return !this.issubmitting;
	},
	
	isSubmitEnabled : function()
	{
		var enabled = true;
		if (!isUndefinedOrNull( this.getSubmitButton() )) {
			enabled = this.getSubmitButton().isElementEnabled();
		}	
		return enabled;
	},
	
	getMemberByName : function( name )
	{
		var matched = null;
		// search through element objects
		for (var i=0; i<this.registration.length; i++) {
		
			var element = this.registration[i];
			if (element.getElementName() == name) {
				matched = element;
				break;
			}
			
		}
		return matched;
	},
	
	getMembersByType : function( type )
	{
		var matched = new Array;
		// search through element objects
		for (var i=0; i<this.registration.length; i++) {
		
			var element = this.registration[i];
			if (element.getElementType() == type) {
				matched.push(element);
			}
			
		}
		return matched;
	},
	
	getMembersByTag : function( tag )
	{
		var matched = new Array;
		// search through element objects
		for (var i=0; i<this.registration.length; i++) {
		
			var element = this.registration[i];
			if (element.getElementTag() == tag) {
				matched.push(element);
			}
			
		}
		return matched;
	},
	
	memberExists : function( name )
	{
		return !isUndefinedOrNull(this.getMemberByName(name));
	},
	
	hideAllMessages : function()
	{
		var messages = getElementsByTagAndClassName('h1', 'error', this.elem);
		for (var i=0; i<messages.length; i++) {
			var message = messages[i];
			hideElement(message);
		};
	}

}

// Inherit from CingoKit.Base
setdefault(CingoKit.Form.Manager.prototype, CingoKit);
