/*
 * This file is part of Phraseanet
 *
 * (c) 2005-2016 Alchemy
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

define([
    "jquery",
    "underscore",
    "backbone"
], function ($, _, Backbone) {
    var FormValidator = function (rules, handlers) {
        // rules setted by user
        this.rules = rules || [];
        // custom callbacks
        this.handlers = handlers || [];
        // final fields to validate
        this.fields = [];

        var self = this;

        _.each(this.rules, function (field) {
            if ("name" in field && "rules" in field) {
                self._addField(field);
            }
        });
    };

    // Validate method, argument is the serialize form
    FormValidator.prototype.validate = function (inputs) {
        var self = this;
        // possible errors
        this.errors = [];
        // inputs present in form
        this.inputs = {};

        _.each(_.groupBy(inputs, function (input) {
            return input.name;
        }), function (fields, name) {
            self.inputs[name] = fields;
        });

        this._validateForm();

        return this;
    };

    FormValidator.prototype.getErrors = function () {
        return this.errors;
    };

    FormValidator.prototype.hasErrors = function () {
        return this.errors.length > 0;
    };

    FormValidator.prototype.getRules = function () {
        return this.rules;
    };

    FormValidator.prototype._addField = function (field) {
        this.fields.push({
            name: field.name,
            rules: field.rules,
            message: field.message || "An error ocurred on input[name=" + field.name + "], you can edit this message by setting a 'message' property in your rule definition object",
            value: null,
            type: field.type || "text"
        });
    };

    FormValidator.prototype._validateForm = function () {
        var self = this;
        this.errors = [];
        _.each(this.fields, function (field) {
            if (_.has(self.inputs, field.name)) {
                // values can be multiple
                var values = [];

                _.each(self.inputs[field.name], function (field) {
                    return values.push(field.value);
                });

                field.value = values.join(',');

                self._validateField(field);
            } else if (field.type === "checkbox" || field.type === "radio" || field.type === "select" || field.type === "multiple") {
                field.value = '';
                self._validateField(field);
            }
        });
    };

    FormValidator.prototype._validateField = function (field) {
        var self = this;
        var ruleRegex = /^(.+?)\[(.+)\]$/;
        var rules = field.rules.split('|');

        // Run through the rules and execute the validation methods as needed
        _.every(rules, function (method) {
            var param = null;
            var failed = false;
            var parts = ruleRegex.exec(method);

            // If the rule has a parameter (i.e. matches[param]) split it out
            if (parts) {
                method = parts[1];
                param = parts[2];
            }

            // If the hook is defined, run it to find any validation errors
            if (typeof self._hooks[method] === 'function') {
                if (!self._hooks[method].apply(self, [field, param])) {
                    failed = true;
                }
            } else if (method.substring(0, 9) === 'callback_') {
                // Custom method. Execute the handler if it was registered
                method = method.substring(9, method.length);

                if (typeof self.handlers[method] === 'function') {
                    if (this.handlers[method].apply(self, [field.value, param]) === false) {
                        failed = true;
                    }
                }
            }

            // If the hook failed, add a message to the errors array
            if (failed) {
                self.errors.push({
                    name: field.name,
                    value: field.value,
                    message: field.message,
                    rule: method
                });
            }

            // Breaks loop iteration
            return failed;
        });
    };

    FormValidator.prototype.Regexp = {
        numericRegex: /^[0-9]+$/,
        integerRegex: /^\-?[0-9]+$/,
        decimalRegex: /^\-?[0-9]*\.?[0-9]+$/,
        emailRegex: /^[^@]+@[^@]+\.[^@]+$/,
        alphaRegex: /^[a-z]+$/i,
        alphaNumericRegex: /^[a-z0-9]+$/i,
        alphaDashRegex: /^[a-z0-9_\-]+$/i,
        naturalRegex: /^[0-9]+$/i,
        naturalNoZeroRegex: /^[1-9][0-9]*$/i,
        // IP v6 a v4 or hostname, by Mikulas Dite see http://stackoverflow.com/a/9209720/96656
        ipRegex: /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$|^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$|^(?:(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-fA-F]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,1}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,2}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,3}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:[0-9a-fA-F]{1,4})):)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,4}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,5}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,6}(?:(?:[0-9a-fA-F]{1,4})))?::))))$/i,
        numericDashRegex: /^[\d\-\s]+$/,
        urlRegex: /^((http|https):\/\/(\w+:{0,1}\w*@)?(\S+)|)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/
    };

    // Object containing all of the validation hooks
    FormValidator.prototype._hooks = {
        "required": function (field) {
            var value = field.value;

            return (value !== null && value !== '');
        },
        "equal": function (field, defaultName) {
            return field.value === defaultName;
        },
        "matches": function (field, matchName) {
            if (typeof this.inputs[matchName] === "undefined") {
                return false;
            }

            var el = this.inputs[matchName].shift();

            if (el) {
                return field.value === el.value;
            }

            return false;
        },
        "valid_email": function (field) {
            return this.Regexp.emailRegex.test(field.value);
        },
        "valid_emails": function (field) {
            var result = field.value.split(",");

            for (var i = 0; i < result.length; i++) {
                if (!this.Regexp.emailRegex.test($.trim(result[i]))) {
                    return false;
                }
            }

            return true;
        },
        "min_length": function (field, length) {
            if (field.type === "multiple") {
                return _.filter(field.value.split(","),function (value) {
                    return value !== "";
                }).length >= parseInt(length, 10)
            }

            if (!this.Regexp.numericRegex.test(length)) {
                return false;
            }

            return (field.value.length >= parseInt(length, 10));
        },
        "max_length": function (field, length) {
            if (field.type === "multiple") {
                return _.filter(field.value.split(","),function (value) {
                    return value !== "";
                }).length <= parseInt(length, 10)
            }

            if (!this.Regexp.numericRegex.test(length)) {
                return false;
            }

            return (field.value.length <= parseInt(length, 10));
        },
        "exact_length": function (field, length) {
            if (field.type === "multiple") {
                return _.filter(field.value.split(","),function (value) {
                    return value !== "";
                }).length === parseInt(length, 10)
            }

            if (!this.Regexp.numericRegex.test(length)) {
                return false;
            }

            return (field.value.length === parseInt(length, 10));
        },
        "greater_than": function (field, param) {
            if (!this.Regexp.decimalRegex.test(field.value)) {
                return false;
            }

            return (parseFloat(field.value) > parseFloat(param));
        },
        "less_than": function (field, param) {
            if (!this.Regexp.decimalRegex.test(field.value)) {
                return false;
            }

            return (parseFloat(field.value) < parseFloat(param));
        },
        "alpha": function (field) {
            return (this.Regexp.alphaRegex.test(field.value));
        },
        "alpha_numeric": function (field) {
            return (this.Regexp.alphaNumericRegex.test(field.value));
        },
        "alpha_dash": function (field) {
            return (this.Regexp.alphaDashRegex.test(field.value));
        },
        "numeric": function (field) {
            return (this.Regexp.numericRegex.test(field.value));
        },
        "integer": function (field) {
            return (this.Regexp.integerRegex.test(field.value));
        },
        "decimal": function (field) {
            return (this.Regexp.decimalRegex.test(field.value));
        },
        "is_natural": function (field) {
            return (this.Regexp.naturalRegex.test(field.value));
        },
        "is_natural_no_zero": function (field) {
            return (this.Regexp.naturalNoZeroRegex.test(field.value));
        },
        "valid_ip": function (field) {
            return (this.Regexp.ipRegex.test(field.value));
        },
        "valid_url": function (field) {
            return (this.Regexp.urlRegex.test(field.value));
        }
    };

    return FormValidator;
});
