'use strict';

angular.module('ffffng').factory('Validator', function (_, Strings, Logger) {
    // TODO: sanitize input for further processing as specified by constraints (correct types, trimming, etc.)

    function isValidBoolean(value) {
        return _.isBoolean(value) || value === 'true' || value === 'false';
    }

    function isValidNumber(constraint, value) {
        if (_.isString(value)) {
            value = Strings.parseInt(value);
        }

        if (!_.isNumber(value)) {
            return false;
        }

        if (_.isNaN(value) || !_.isFinite(value)) {
            return false;
        }

        if (_.isNumber(constraint.min) && value < constraint.min) {
            return false;
        }

        if (_.isNumber(constraint.max) && value > constraint.max) {
            return false;
        }

        return true;
    }

    function isValidEnum(constraint, value) {
        if (!_.isString(value)) {
            return false;
        }

        return _.indexOf(constraint.allowed, value) >= 0;
    }

    function isValidString(constraint, value) {
        if (!_.isString(value)) {
            return false;
        }

        var trimmed = value.trim();
        return (trimmed === '' && constraint.optional) || trimmed.match(constraint.regex);
    }

    function isValid(constraint, acceptUndefined, value) {
        if (value === undefined) {
            return acceptUndefined || constraint.optional;
        }

        switch (constraint.type) {
            case 'boolean':
                return isValidBoolean(value);

            case 'number':
                return isValidNumber(constraint, value);

            case 'enum':
                return isValidEnum(constraint, value);

            case 'string':
                return isValidString(constraint, value);
        }

        Logger.tag('validation').error('No validation method for constraint type: {}', constraint.type);
        return false;
    }

    function areValid(constraints, acceptUndefined, values) {
        var fields = Object.keys(constraints);
        for (var i = 0; i < fields.length; i ++) {
            var field = fields[i];
            if (!isValid(constraints[field], acceptUndefined, values[field])) {
                return false;
            }
        }
        return true;
    }

    return {
        forConstraint: function (constraint, acceptUndefined) {
            return _.partial(isValid, constraint, acceptUndefined);
        },
        forConstraints: function (constraints, acceptUndefined) {
            return _.partial(areValid, constraints, acceptUndefined);
        }
    };
});