﻿// The Error Message factory generates an event handler function per event per element
var ErrorMessageFactory = (function() {
    function generateGeneralFieldErrorEventHandler() {
        return function(eventName, error) {

            var divMessage = $(this.id + eventName);

            if (!divMessage) {
                var divMessage = $div({
                    id: this.id + eventName
                });
            }

            divMessage.update(error.message);

            divMessage.setStyle({
                display: 'block',
                cssFloat: 'right',
                color: '#FF0000',
                textAlign: 'left',
                visibility: 'visible'
            });

            this.parentNode.appendChild(divMessage);
        }
    }

    function generateRequiredFieldErrorEventHandler() {
        return function(eventName, error) {
            var divMessage = $(this.id + eventName);

            if (!divMessage) {
                var divMessage = $div({
                    id: this.id + eventName
                });
            }

            divMessage.update(error.message);

            divMessage.setStyle({
                display: 'block',
                cssFloat: 'right',
                color: '#FF0000',
                textAlign: 'left',
                visibility: 'visible'
            });
            if (this.type == 'checkbox' || this.type == 'radio' || this.type == 'combobox') {
                this.parentNode.parentNode.appendChild(divMessage);
            }
            else {
                this.parentNode.appendChild(divMessage);
            }
        };
    }

    return {
        GenerateGeneralErrorEventHandler: generateGeneralFieldErrorEventHandler,
        GenerateRequiredFieldErrorEventHandler: generateRequiredFieldErrorEventHandler
    };
})();


// The ValidationRuleFactory uses the DTO (fieldMap) to generate a validation rule for each
// element in the form
var ValidationRuleFactory = (function() {
    function generateValidationRuleFor(fieldMap) {
        if (!Object.isUndefined(fieldMap.type)) {
            var formElement = $(fieldMap['inputName']);

            if (Object.isUndefined(formElement) || (formElement == null)) {
                formElement.parentNode.fireEvent('ElementIsUndefined', {
                    message: 'No element ' + fieldMap['inputName'] + ' exist. Please check your spelling'
                });
            }

            switch (fieldMap.type) {
                case 'integer':
                    return ValidateInteger(formElement, fieldMap);
                case 'double':
                    return ValidateDouble(formElement, fieldMap);
                case 'date':
                    return ValidateDate(formElement, fieldMap);
                case 'text':
                    return ValidateText(formElement, fieldMap);
                case 'string':
                    return ValidateString(formElement, fieldMap);
                default:
                    return function() {
                    };
            }
        }

        return function() {
        };
    }

    function generateRequiredRuleFor() {
        //===
        // This fuction is ran under the FORM context...not the element context
        // Extract all the input elements from the form

        var inputElements = $$('#' + this.container.id + ' [id]');

        // Count how many validation error we encounter
        var erroCount = 0;

        // Cycle through all the fields in the form looking to confirm a value has been entered
        this.query.dataConnector.dto.fieldSet.each(function(fieldMap) {
            if (fieldMap.required) {
                switch (fieldMap.map) {
                    case 'checkbox':
                        var checkBoxElements = $$('#' + fieldMap.inputName);
                        var checkedCount = 0;

                        checkBoxElements.each(function(checkbox) {
                            if (checkbox.checked)
                                checkedCount++;
                        });

                        if (checkedCount == 0) {
                            // We require to fire the form-level event..
                            this.container.fireEvent('RequiredFieldException', {
                                message: fieldMap.inputName + ' is required. Please select the value'
                            }, checkBoxElements[checkBoxElements.length - 1]);

                            erroCount++;

                        } else {
                            checkBoxElements[checkBoxElements.length - 1].removeErrorMessage('RequiredFieldException');
                        }

                        break;
                    case 'combobox':
                        var thisElement = inputElements.find(function(el) {
                            return el.id.capitalize() == fieldMap.inputName.capitalize();
                        });

                        if (thisElement.selectedIndex == 0) {
                            // We require to fire the form-level event..
                            this.container.fireEvent('RequiredFieldException', {
                                message: thisElement.id + ' is required. Please select the value'
                            }, thisElement);

                            erroCount++;

                        }
                        else {
                            thisElement.removeErrorMessage('RequiredFieldException');
                        }

                        break;
                    case 'radiooption':
                        var radioOptionElements = $$('#' + fieldMap.inputName);
                        var checkedCount = 0;

                        radioOptionElements.each(function(radiooption) {
                            if (radiooption.checked)
                                checkedCount++;
                        });

                        if (checkedCount == 0) {
                            // We require to fire the form-level event..
                            this.container.fireEvent('RequiredFieldException', {
                                message: fieldMap.inputName + ' is required. Please select the value'
                            }, radioOptionElements[radioOptionElements.length - 1]);
                        } else {
                            radioOptionElements[radioOptionElements.length - 1].removeErrorMessage('RequiredFieldException');
                        }


                        break;
                    default:
                        {
                            //*****
                            // The fieldMap correspond to a field in the form --this function returns the
                            // element within the form with that name
                            var thisElement = inputElements.find(function(el) {
                                return el.id.capitalize() == fieldMap.inputName.capitalize();
                            });

                            // Since this is a required field and we found no input field in the form that
                            // collects that piece of data then we must tell the user there is a problem
                            // with the form missing that field
                            if (!thisElement) {
                                this.container.fireEvent('RequiredFieldException', {
                                    message: fieldMap.inputName + ' is required. This form doesnt seem to provide you with that field. Please Contact your Administrator'
                                }, thisElement);

                                erroCount++;

                            } else if (thisElement.getValue().strip().length == 0) {
                                // We require to fire the form-level event..
                                this.container.fireEvent('RequiredFieldException', {
                                    message: thisElement.id + ' is required. Please enter a valid value'
                                }, thisElement);

                                erroCount++;

                            }
                            else {
                                thisElement.removeErrorMessage('RequiredFieldException');
                            }

                            // Since the field exists in the form and it is required we then check that it has a 
                            // vlaue

                            //*****
                            break;
                        }
                }
            }
        }, this);

        //===

        if (erroCount > 0) return false; else return true;
    }

    function generateFormLevelValidationRuleFor() {
        //===
        // This fuction run under the FORM context...not the element context
        // Extract all the input elements from the form
        var inputElements = $$('#' + this.container.id + ' [id]');

        // Count how many validation error we encounter
        var errorCount = 0;

        // Create reusable default validation function
        var defaultValidation = function(fieldMap) {
            var formElement = $(fieldMap['inputName']);

            if (formElement != null) {
                switch (fieldMap.type) {
                    case 'integer':
                        isValid = ValidateInteger(formElement, fieldMap)(); //.call(this, null);
                        break;
                    case 'double':
                        isValid = ValidateDouble(formElement, fieldMap)();  //.call(this, null);
                        break;
                    case 'date':
                        isValid = ValidateDate(formElement, fieldMap)();  //.call(this, null);
                        break;
                    case 'text':
                        isValid = ValidateText(formElement, fieldMap)();  //.call(this, null);
                        break;
                    case 'string':
                        isValid = ValidateString(formElement, fieldMap)();  //.call(this, null);
                        break;
                    default:
                        isValid = false;
                        break;
                }

                return isValid;
            }
        }

        // Cycle through all the fields in the form looking to confirm a value has been entered
        this.query.dataConnector.dto.fieldSet.each(function(fieldMap) {
            if (!Object.isUndefined(fieldMap.map)) {
                // Different input elements require different validation techniques
                switch (fieldMap.map) {
                    case 'checkbox':
                        isValid = true;
                        break;
                    case 'radiooption':
                        isValid = true;
                        break;
                    case 'image':
                        isValid = true;
                        break;
                    case 'singlecheckbox':
                        isValid = true;
                        break;
                    default:
                        isValid = defaultValidation.call(this, fieldMap);
                }

                if (!isValid)
                    errorCount++;

            } else {
                isValid = defaultValidation.call(this, fieldMap);

                if (!Object.isUndefined(isValid) && !isValid)
                    errorCount++;

            }
        }, this);

        if (errorCount > 0) return false; else return true;
    }

    function generateDependenciesRuleFor(element, fieldMap, dependencies) {
        var ruleSpec = dependencies.find(function(rule) {
            return (fieldMap.inputName == rule.inputName);
        });

        if (ruleSpec) {
            if (!Object.isUndefined(ruleSpec.type)) {
                if (Object.isUndefined(element) || (element == null)) {
                    formElement.parentNode.fireEvent('ElementIsUndefined', {
                        message: 'No element ' + fieldMap['inputName'] + ' exist. Please check your spelling'
                    });
                }

                switch (ruleSpec.type) {
                    case 'groupEither':
                        return ValidateGroupEither(element, ruleSpec);
                    default:
                        return function() {
                        };
                        break;
                }
            }
        }

        return function() {
        };
    }

    function ValidateInteger(element, fieldMap) {
        // Type: Is it an integer?
        // Range: Greater than 'lowerLimit' and Less than 'upperLimit'
        return function() {
            // Validate only if a value was entered
            if (this.getValue) {
                if (this.getValue() == '')
                    return;

                // Is the entered value an integer?
                if (isNaN(parseInt(this.getValue()))) {
                    // if value is not an integer then raise validationError
                    this.fireEvent('InvalidCastException', {
                        message: this.id + ' should be an integer'
                    });
                }
                else {
                    this.removeErrorMessage('InvalidCastException');
                }

            } else {
                if (Object.isElement(this)) {
                    if (this.innerHTML.strip() == '') return;
                }

                // Is the entered value an integer?
                if (isNaN(parseInt(this.innerHTML.strip()))) {
                    // if value is not an integer then raise validationError
                    this.fireEvent('InvalidCastException', {
                        message: this.id + ' should be an integer'
                    });
                }
                else {
                    this.removeErrorMessage('InvalidCastException');
                }
            }

            // If a range is required
            if (!Object.isUndefined(fieldMap.range)) {
                // Check lower limit
                try {
                    if (!Object.isUndefined(fieldMap.range.lowerLimit)) {
                        var lowerLimit = parseInt(fieldMap.range.lowerLimit);

                        // If the value is not numeric simply ignore validation rule
                        if (lowerLimit != 'NaN') {
                            if (this.getValue() < lowerLimit) {
                                this.fireEvent('InvalidRange', {
                                    message: 'Entered value should not be less than ' + lowerLimit
                                });
                            }
                            else {
                                this.removeErrorMessage('InvalidRange');
                            }
                        }
                    }
                }
                catch (e) {
                    this.fireEvent('ValidationRuleGenerationError', {
                        message: 'Error while generating validation rule for lower range limit of element' + this.id
                    });
                }

                // Check upper limit
                try {
                    if (!Object.isUndefined(fieldMap.range.upperLimit)) {
                        var upperLimit = parseInt(fieldMap.range.upperLimit);

                        // If the value is not numeric simply ignore validation rule
                        if (upperLimit != 'NaN') {
                            if (this.getValue() < upperLimit) {
                                this.fireEvent('InvalidRange', {
                                    message: 'Entered value should not be greater than ' + upperLimit
                                });
                            }
                            else {
                                this.removeErrorMessage('InvalidRange');
                            }
                        }
                    }
                }
                catch (e) {
                    this.fireEvent('ValidationRuleGenerationError', {
                        message: 'Error while generating validation rule for upper range limit of element' + this.id
                    });
                }
            }

            return true;

        } .bind(element);
    }

    function Double(element, fieldMap) {
        // Type: Is it double?
        // Decimals: Number of decimals
        return function() {
            // Is the entered value a double?
            if (parseFloat(this.getValue()) == 'NaN') {
                // if value is not an integer then raise validationError
                this.fireEvent('InvalidCastException');
            }
            else {
                this.removeErrorMessage('InvalidCastException');
            }

            // Decimals
            if (!Object.isUndefined(fieldMap.Decimals)) {
                // TODO            
            }
        } .bind(element);
    }

    function ValidateText(element, fieldMap) {
        // Max : Max number of characters -- html markup stripped
        // Min : Min number of characters -- like 'required' but it can be more than 1 char        
        return function() {
            // Max
            if (!Object.isUndefined(fieldMap.max)) {
                if (this.getValue() > parseInt(fieldMap.max)) this.fireEvent('InvalidFieldSize', {
                    message: 'Field size limited to:' + fieldMap.Max
                });
            }
            else {
                this.removeErrorMessage('InvalidFieldSize');
            }

            // Min
            if (!Object.isUndefined(fieldMap.min)) {
                if (this.getValue() > parseInt(fieldMap.min)) this.fireEvent('InvalidFieldSize', {
                    message: 'You must type at least ' + fieldMap.Min + ' characters in this field'
                });
            }
            else {
                this.removeErrorMessage('InvalidFieldSize');
            }
        } .bind(element);
    }

    function ValidateString(element, fieldMap) {
        // Lookup : Value of string must be in the supplied lookup list
        // Max : Max number of characters -- html markup considered a string
        element.FieldMap = fieldMap;

        return function() {
            if (!Object.isUndefined(this.FieldMap.lookup)) {
                if (this.FieldMap.lookup.indexOf(this.getValue()) == -1) {
                    this.fireEvent('InvalidFieldValue', {
                        message: 'Field value must be within this list:  ' + this.FieldMap.lookup
                    });

                    return false;
                }
                else {
                    this.removeErrorMessage('InvalidFieldValue');
                }
            }

            // Max
            if (!Object.isUndefined(this.FieldMap.max)) {
                if (this.getValue() > parseInt(this.FieldMap.max)) {
                    this.fireEvent('InvalidFieldSize', {
                        message: 'Field size limited to:' + fieldMap.max
                    });

                    return false;
                }
                else {
                    this.removeErrorMessage('InvalidFieldSize');
                }
            }

            // Regular Expression Patterns
            if (this.FieldMap.mask) {

                var validateMaskFunction = this.FieldMap.mask(this.getValue());

                if (validateMaskFunction == false) {
                    this.fireEvent('InvalidValue', {
                        message: 'Value entered for field ' + this.FieldMap.inputName + ' is invalid.'
                    });

                    return false;

                } else {
                    this.removeErrorMessage('InvalidValue');
                }
            }

            return true;

        } .bind(element);
    }

    function ValidateDate(element, fieldMap) {
        // Range: Greater than 'lowerLimit' and Less than 'upperLimit'
        // Between: Greater than or equal to 'lowerLimit' and Less than or equal to 'upperLimit'
        return function() {
            // Range & Between are mutually exclusive options            
            // Range
            if (!Object.isUndefined(fieldMap.range)) {
                if (!Object.isUndefined(fieldMap.range.fromDate)) {
                    var lowerDate = new Date(fieldMap.range.fromDate);
                    var fieldDate = new Date(this.getValue());

                    if (fieldDate <= lowerDate) {
                        this.fireEvent('InvalidDateFrom', {
                            message: 'Date must be greater than ' + fieldMap.range.fromDate
                        });
                    } else {
                        this.removeErrorMessage('InvalidDateFrom');
                    }
                }
                else {
                    this.removeErrorMessage('InvalidDateFrom');
                }

                if (!Object.isUndefined(fieldMap.range.toDate)) {
                    var upperDate = new Date(fieldMap.range.toDate);
                    var fieldDate = new Date(this.getValue());

                    if (fieldDate >= upperDate) {
                        this.fireEvent('InvalidDateTo', {
                            message: 'Date must be less than ' + fieldMap.range.toDate
                        });
                    }
                    else {
                        this.removeErrorMessage('InvalidDateTo');
                    }
                }
                else {
                    this.removeErrorMessage('InvalidDateTo');
                }

            }

            // Between
            if (!Object.isUndefined(fieldMap.between)) {
                // Both from and to dates are required
                if (!fieldMap.between.fromDate || !fieldMap.between.toDate) {
                    if (this.getValue() < fieldMap.between.fromDate || this.getValue() > fieldMap.between.toDate) {
                        this.fireEvent('InvalidDateBetween', {
                            message: 'Date must be between ' + fieldMap.between.fromDate + ' and ' + fieldMap.between.toDate
                        });
                    }
                    else {
                        this.removeErrorMessage('InvalidDateBetween');
                    }
                } else {
                    this.removeErrorMessage('InvalidDateBetween');
                }
            }

        } .bind(element);
    }

    function ValidateGroupEither(element, ruleSpec) {
        return function() {
            // GroupEither verifies that the values given to ALL the elements that carry the same ID as the formElement
            // are valid

            // #1:: Get collection of elements to evaluate
            elements = $$(ruleSpec.inputName);

            validSet = ruleSpec.validvalues;

            validSet.each(function(validPair) {
                result = $A();

                elements.each(function(element) {

                    if (validPair.include(element.getValue())) {
                        result.push(validPair);
                    }

                }, this);

                if (result.length == 0)
                    return false;
                else
                    validSet = result;

            }, this);
        } .bind(element);
    }

    return {
        GenerateValidationRule: generateValidationRuleFor,
        GenerateRequiredRuleValidation: generateRequiredRuleFor,
        GenerateDependenciesRule: generateDependenciesRuleFor,
        GenerateFormLevelValidationRule: generateFormLevelValidationRuleFor
    };
})();





