var validationStateByField = {};

function IsTemplateField(field) {
    return (field.id.indexOf("#{") >= 0);
}

function ResetForm(form) {
    var fields = GetFields(form);

    for (var i = 0; i < fields.length; i++) {
        var field = fields[i];
        ResetField(field);
    }
}

function ResetField(field) {    
    var validField = $(field.id + '_Valid');
    var invalidField = $(field.id + '_Invalid');
    
    validationStateByField[field.id] = undefined;
    
    if (field.type === 'checkbox') 
        field.checked = false;
    else 
        field.value = '';
    
    if(validField)
        validField.style.display = 'none';
        
    if(invalidField) 
        invalidField.style.display = 'none';    
}

function ValidationContext(form, callback)
{
	this.Form = form;
	this.Callback = callback;
	this.Fields = Array();
	this.FieldById = Array();
	this.Unvalidated = Array();
	this.LeftToValidate = 0;
	var self = this;

	this.AddField = function(field, isValidated) {
		// Only validate each field once
		if (this.FieldById[field.id] !== undefined)
			return;
		
		this.FieldById[field.id] = field;
    	this.Fields[this.Fields.length] = field;

		if (!isValidated)
	    	this.Unvalidated[this.Unvalidated.length] = field;
	};
	
	this.Process = function(serializedForm) {
		// If we don't have any async validation requests to process, process now
		if (this.Unvalidated.length == 0) {
			this.OnAsyncValidationCompleted();
			return;
		}
		
		// Setup how many fields we need to validate
		this.LeftToValidate = this.Unvalidated.length;
		
		// Otherwise, spawn validation requests for each field
		for(var i = 0; i < this.Unvalidated.length; i++)
			ValidateField(this.Unvalidated[i], this.Form, serializedForm, function(field) {
				self.OnFieldValidated(field);
			});
	},
	
	this.OnFieldValidated = function(field) {
		// Decrement the amount of fields left to validate
		this.LeftToValidate--;
		
		// If we have finished validation, process our form
		if (this.LeftToValidate <= 0)
			this.OnAsyncValidationCompleted();
	};
	
	this.OnAsyncValidationCompleted = function() {
		// Check each of our (now validated) fields and perform the final validation logic
		var formValid = true;
		for(var i = 0; i < this.Fields.length; i++) {
			var field = this.Fields[i];
			
			formValid &= validationStateByField[field.id].valid;
		}
		
		// Raise any validation errors if necessary
		var errors = [];
	    if (!formValid) {
	        for (var i = 0; i < this.Fields.length; i++) {
	        	var field = this.Fields[i];
	        	var validationState = validationStateByField[field.id];
	        	if (!validationState)
	        		continue;
	        	
	            if (!validationState.valid)
	                errors.push(validationState.title + ": " + validationState.message);                
	        }
	
	        ShowValidationErrors(errors);
	    }

	    // Validation finished, execute our callback function
	    if (self.Callback !== undefined)
		    self.Callback(formValid == true);
	};
}

function ValidateFormAsync(form, callback)
{    
	// Ensure each of our fields have been validated OK
	// If they have not yet been validated, validate them now
    var fields = GetFields(form);

    // Prepare our validation context to validate this form
    var context = new ValidationContext(form, callback);
    
	for(var i = 0; i < fields.length; i++) {
	    var field = fields[i];

	    if (!IsTemplateField(field)) {
	        // Sometimes we don't want all fields to be validated
	        var validateonpost = field.readAttribute('validateonpost');
	        if (validateonpost !== undefined) {
	            if (validateonpost == 'false') continue;
	        }

			// Add this field to our validation queue if necessary
	        var isValidated = validationStateByField[field.id] !== undefined;
        	context.AddField(field, isValidated);
	    }
    }
    
    // Serialize our form before its disabled during validation
    var serializedForm = Form.serialize(form);

    // Mark our form as busy processing
    SetFormBusy(form, true);
    
    // Perform validation on each field
    context.Process(serializedForm);
}

function ValidateField(field, form, serializedForm, callback)
{
	field = $(field);

	if (IsTemplateField(field)) {
	    if (field.getValue() != null && field.getValue() != "") {
	        CreateFormTemplateInstance(field.up(".FormTemplate"), field.id, field.getValue());
	    }
	    return;
	}

	var value = field.value;

	if (field.type == "select-one") {	    
	    value = field.options.length > 0 ? field.options[field.selectedIndex].value : null;	    
	}
		
	var fieldToValidate = escape(field.name);

	if (field.getAttribute("fieldToValidate") !== null)
	    fieldToValidate = escape(field.getAttribute("fieldToValidate"));
	    
    // Sometimes we don't want all fields to be validated
	var validateonpost = field.readAttribute('validateonpost');
    if(validateonpost !== undefined) {
        if (validateonpost == 'false') return;
    }
	
	// Trim the trailing controller action from the url and replace it with 'validate'
	var url = form.action.substr(0, form.action.lastIndexOf("/")) + "/validate" + form.id;
	
	// Populate Serialized Form if it is unset
	if (serializedForm === undefined)
		serializedForm = Form.serialize(form);

	var valid = false;
	var myAjax = new Ajax.Request(
		url,
		{
		    method: 'POST',
		    asynchronous: true,
		    parameters: serializedForm + '&fieldToValidate=' + fieldToValidate,
		    onSuccess: function(req) {
		        var text = '';
		        var valid = false;

		        try {
		            // Retrieve response text		    
		            var response = req.responseText.evalJSON();

		            if (response.ResponseType == "NONE") { return; }

		            text = response.ResponseType == "ERROR" ? response.Data : '';
		            valid = text == '';
		        } catch (err) {
		            return;
		        }

		        var name = fieldToValidate.substring(fieldToValidate.indexOf('.') + 1);

		        // Remember our validation result for this field
		        validationStateByField[field.id] = {
		            valid: valid,
		            name: name,
		            message: response.Data,
		            title: field.getAttribute("validationTitle") != null ? field.getAttribute("validationTitle") : SeperateWords(name)
		        };

		        // Retrieve references to our supporting validation fields
		        var validField = $(field.id + '_Valid');
		        var invalidField = $(field.id + '_Invalid');

		        // Should we suppress the Success/Failure validation messages for this field?
		        var hideFeedback = field.attributes.hidefeedback !== undefined && field.attributes.hidefeedback;
		        var hideIfMessageEmpty = field.attributes.hideifmessageempty !== undefined && field.attributes.hideifmessageempty;

		        // Hide/show the appropriate fields based on the validation result
		        if (!hideFeedback) {
		        	if (invalidField) {
			            invalidField.style.display = valid ? 'none' : '';
			            if (!valid)
			                invalidField.innerHTML = text;
					}

		        	if (validField) {
			            validField.style.display = valid ? '' : 'none';
	
			            validField.innerHTML = response.Data || '';
	
			            if (valid && response.Data == null && hideIfMessageEmpty) {
			                validField.style.display = 'none';
			            }
					}
		        }
		        
		        // Also validate another field?
		        if (field.attributes.alsovalidate) {
		        	var otherFieldName = field.attributes.alsovalidate.value;
		        	var otherField = $(otherFieldName);
		        	if (otherField) {
			            ValidateField(otherField, form, serializedForm, callback);
				        return;
					} else {
			        	console.error("Field " + field.id + " has an alsovalidate attribute that points to a missing input field: " + otherFieldName);
					}
				}

		        // Raise callback if it has been set, and we don't have another field to validate
		        if (callback !== undefined)
		        	callback(field);
		    }
		});
}

function PostField(field, url, successFunction) {
    var myAjax = new Ajax.Request(
	url,
	{
	    method: 'POST',
	    asynchronous: true,
	    parameters: field.name + "=" + escape(field.value),
	    onSuccess: successFunction,
	    onFailure: function() {
	        return;
	    }
	})
}

function GetFields(form) {    
    return $$('#' + form.id + ' .AjaxField input[type!=hidden], #' + form.id + ' .AjaxField select, #' + form.id + ' .AjaxField textarea');    
}
