$.extend(true, laravelValidation, {
    methods: {
        helpers: laravelValidation.helpers,

        jsRemoteTimer: 0,

        /**
         * "Validate" optional attributes.
         * Always returns true, just lets us put sometimes in rules.
         *
         * @return {boolean}
         */
        Sometimes: function () {
            return true
        },

        /**
         * Bail This is the default behaivour os JSValidation.
         * Always returns true, just lets us put sometimes in rules.
         *
         * @return {boolean}
         */
        Bail: function () {
            return true
        },

        /**
         * "Indicate" validation should pass if value is null.
         * Always returns true, just lets us put "nullable" in rules.
         *
         * @return {boolean}
         */
        Nullable: function () {
            return true
        },

        /**
         * Validate the given attribute is filled if it is present.
         */
        Filled: function (value, element) {
            return $.validator.methods.required.call(this, value, element, true)
        },

        /**
         * Validate that a required attribute exists.
         */
        Required: function (value, element) {
            return $.validator.methods.required.call(this, value, element)
        },

        /**
         * Validate that an attribute exists when any other attribute exists.
         *
         * @return {boolean}
         */
        RequiredWith: function (value, element, params) {
            var validator = this,
                required = false
            var currentObject = this

            $.each(params, function (i, param) {
                var target = laravelValidation.helpers.dependentElement(currentObject, element, param)
                required =
                    required ||
                    (target !== undefined &&
                        $.validator.methods.required.call(validator, currentObject.elementValue(target), target, true))
            })

            if (required) {
                return $.validator.methods.required.call(this, value, element, true)
            }
            return true
        },

        /**
         * Validate that an attribute exists when all other attribute exists.
         *
         * @return {boolean}
         */
        RequiredWithAll: function (value, element, params) {
            var validator = this,
                required = true
            var currentObject = this

            $.each(params, function (i, param) {
                var target = laravelValidation.helpers.dependentElement(currentObject, element, param)
                required =
                    required &&
                    target !== undefined &&
                    $.validator.methods.required.call(validator, currentObject.elementValue(target), target, true)
            })

            if (required) {
                return $.validator.methods.required.call(this, value, element, true)
            }
            return true
        },

        /**
         * Validate that an attribute exists when any other attribute does not exists.
         *
         * @return {boolean}
         */
        RequiredWithout: function (value, element, params) {
            var validator = this,
                required = false
            var currentObject = this

            $.each(params, function (i, param) {
                var target = laravelValidation.helpers.dependentElement(currentObject, element, param)
                required =
                    required ||
                    target === undefined ||
                    !$.validator.methods.required.call(validator, currentObject.elementValue(target), target, true)
            })

            if (required) {
                return $.validator.methods.required.call(this, value, element, true)
            }
            return true
        },

        /**
         * Validate that an attribute exists when all other attribute does not exists.
         *
         * @return {boolean}
         */
        RequiredWithoutAll: function (value, element, params) {
            var validator = this,
                required = true,
                currentObject = this

            $.each(params, function (i, param) {
                var target = laravelValidation.helpers.dependentElement(currentObject, element, param)
                required =
                    required &&
                    (target === undefined ||
                        !$.validator.methods.required.call(validator, currentObject.elementValue(target), target, true))
            })

            if (required) {
                return $.validator.methods.required.call(this, value, element, true)
            }
            return true
        },

        /**
         * Validate that an attribute exists when another attribute has a given value.
         *
         * @return {boolean}
         */
        RequiredIf: function (value, element, params) {
            var target = laravelValidation.helpers.dependentElement(this, element, params[0])

            if (target !== undefined) {
                var val = String(this.elementValue(target))
                if (typeof val !== 'undefined') {
                    var data = params.slice(1)
                    if ($.inArray(val, data) !== -1) {
                        return $.validator.methods.required.call(this, value, element, true)
                    }
                }
            }

            return true
        },

        /**
         * Validate that an attribute exists when another
         * attribute does not have a given value.
         *
         * @return {boolean}
         */
        RequiredUnless: function (value, element, params) {
            var target = laravelValidation.helpers.dependentElement(this, element, params[0])

            if (target !== undefined) {
                var val = String(this.elementValue(target))
                if (typeof val !== 'undefined') {
                    var data = params.slice(1)
                    if ($.inArray(val, data) !== -1) {
                        return true
                    }
                }
            }

            return $.validator.methods.required.call(this, value, element, true)
        },

        /**
         * Validate that an attribute has a matching confirmation.
         *
         * @return {boolean}
         */
        Confirmed: function (value, element, params) {
            return laravelValidation.methods.Same.call(this, value, element, params)
        },

        /**
         * Validate that two attributes match.
         *
         * @return {boolean}
         */
        Same: function (value, element, params) {
            var target = laravelValidation.helpers.dependentElement(this, element, params[0])

            if (target !== undefined) {
                return String(value) === String(this.elementValue(target))
            }
            return false
        },

        /**
         * Validate that the values of an attribute is in another attribute.
         *
         * @param value
         * @param element
         * @param params
         * @returns {boolean}
         * @constructor
         */
        InArray: function (value, element, params) {
            if (typeof params[0] === 'undefined') {
                return false
            }
            var elements = this.elements()
            var found = false
            var nameRegExp = laravelValidation.helpers.regexFromWildcard(params[0])

            for (var i = 0; i < elements.length; i++) {
                var targetName = elements[i].name
                if (targetName.match(nameRegExp)) {
                    var equals = laravelValidation.methods.Same.call(this, value, element, [targetName])
                    found = found || equals
                }
            }

            return found
        },

        /**
         * Validate an attribute is unique among other values.
         *
         * @param value
         * @param element
         * @param params
         * @returns {boolean}
         */
        Distinct: function (value, element, params) {
            if (typeof params[0] === 'undefined') {
                return false
            }

            var elements = this.elements()
            var found = false
            var nameRegExp = laravelValidation.helpers.regexFromWildcard(params[0])

            for (var i = 0; i < elements.length; i++) {
                var targetName = elements[i].name
                if (targetName !== element.name && targetName.match(nameRegExp)) {
                    var equals = laravelValidation.methods.Same.call(this, value, element, [targetName])
                    found = found || equals
                }
            }

            return !found
        },

        /**
         * Validate that an attribute is different from another attribute.
         *
         * @return {boolean}
         */
        Different: function (value, element, params) {
            return !laravelValidation.methods.Same.call(this, value, element, params)
        },

        /**
         * Validate that an attribute was "accepted".
         * This validation rule implies the attribute is "required".
         *
         * @return {boolean}
         */
        Accepted: function (value) {
            var regex = new RegExp('^(?:(yes|on|1|true))$', 'i')
            return regex.test(value)
        },

        /**
         * Validate that an attribute is an array.
         *
         * @param value
         * @param element
         */
        Array: function (value, element) {
            if (element.name.indexOf('[') !== -1 && element.name.indexOf(']') !== -1) {
                return true
            }

            return $.isArray(value)
        },

        /**
         * Validate that an attribute is a boolean.
         *
         * @return {boolean}
         */
        Boolean: function (value) {
            var regex = new RegExp('^(?:(true|false|1|0))$', 'i')
            return regex.test(value)
        },

        /**
         * Validate that an attribute is an integer.
         *
         * @return {boolean}
         */
        Integer: function (value) {
            var regex = new RegExp('^(?:-?\\d+)$', 'i')
            return regex.test(value)
        },

        /**
         * Validate that an attribute is numeric.
         */
        Numeric: function (value, element) {
            return $.validator.methods.number.call(this, value, element, true)
        },

        /**
         * Validate that an attribute is a string.
         *
         * @return {boolean}
         */
        String: function (value) {
            return typeof value === 'string'
        },

        /**
         * The field under validation must be numeric and must have an exact length of value.
         */
        Digits: function (value, element, params) {
            return $.validator.methods.number.call(this, value, element, true) && value.length === parseInt(params, 10)
        },

        /**
         * The field under validation must have a length between the given min and max.
         */
        DigitsBetween: function (value, element, params) {
            return (
                $.validator.methods.number.call(this, value, element, true) &&
                value.length >= parseFloat(params[0]) &&
                value.length <= parseFloat(params[1])
            )
        },

        /**
         * Validate the size of an attribute.
         *
         * @return {boolean}
         */
        Size: function (value, element, params) {
            return laravelValidation.helpers.getSize(this, element, value) === parseFloat(params[0])
        },

        /**
         * Validate the size of an attribute is between a set of values.
         *
         * @return {boolean}
         */
        Between: function (value, element, params) {
            return (
                laravelValidation.helpers.getSize(this, element, value) >= parseFloat(params[0]) &&
                laravelValidation.helpers.getSize(this, element, value) <= parseFloat(params[1])
            )
        },

        /**
         * Validate the size of an attribute is greater than a minimum value.
         *
         * @return {boolean}
         */
        Min: function (value, element, params) {
            return laravelValidation.helpers.getSize(this, element, value) >= parseFloat(params[0])
        },

        /**
         * Validate the size of an attribute is less than a maximum value.
         *
         * @return {boolean}
         */
        Max: function (value, element, params) {
            return laravelValidation.helpers.getSize(this, element, value) <= parseFloat(params[0])
        },

        /**
         * Validate an attribute is contained within a list of values.
         *
         * @return {boolean}
         */
        In: function (value, element, params) {
            if ($.isArray(value) && laravelValidation.helpers.hasRules(element, 'Array')) {
                var diff = laravelValidation.helpers.arrayDiff(value, params)
                return Object.keys(diff).length === 0
            }
            return params.indexOf(value.toString()) !== -1
        },

        /**
         * Validate an attribute is not contained within a list of values.
         *
         * @return {boolean}
         */
        NotIn: function (value, element, params) {
            return params.indexOf(value.toString()) === -1
        },

        /**
         * Validate that an attribute is a valid IP.
         *
         * @return {boolean}
         */
        Ip: function (value) {
            return (
                /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i.test(
                    value
                ) ||
                /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i.test(
                    value
                )
            )
        },

        /**
         * Validate that an attribute is a valid e-mail address.
         */
        Email: function (value, element) {
            return $.validator.methods.email.call(this, value, element, true)
        },

        /**
         * Validate that an attribute is a valid URL.
         */
        Url: function (value, element) {
            return $.validator.methods.url.call(this, value, element, true)
        },

        /**
         * The field under validation must be a successfully uploaded file.
         *
         * @return {boolean}
         */
        File: function (value, element) {
            if (!window.File || !window.FileReader || !window.FileList || !window.Blob) {
                return true
            }
            if ('files' in element) {
                return element.files.length > 0
            }
            return false
        },

        /**
         * Validate the MIME type of a file upload attribute is in a set of MIME types.
         *
         * @return {boolean}
         */
        Mimes: function (value, element, params) {
            if (!window.File || !window.FileReader || !window.FileList || !window.Blob) {
                return true
            }
            var lowerParams = $.map(params, function (item) {
                return item.toLowerCase()
            })

            var fileinfo = laravelValidation.helpers.fileinfo(element)
            return fileinfo !== false && lowerParams.indexOf(fileinfo.extension.toLowerCase()) !== -1
        },

        /**
         * The file under validation must match one of the given MIME types.
         *
         * @return {boolean}
         */
        Mimetypes: function (value, element, params) {
            if (!window.File || !window.FileReader || !window.FileList || !window.Blob) {
                return true
            }
            var lowerParams = $.map(params, function (item) {
                return item.toLowerCase()
            })

            var fileinfo = laravelValidation.helpers.fileinfo(element)

            if (fileinfo === false) {
                return false
            }
            return lowerParams.indexOf(fileinfo.type.toLowerCase()) !== -1
        },

        /**
         * Validate the MIME type of a file upload attribute is in a set of MIME types.
         */
        Image: function (value, element) {
            return laravelValidation.methods.Mimes.call(this, value, element, [
                'jpg',
                'png',
                'gif',
                'bmp',
                'svg',
                'jpeg',
            ])
        },

        /**
         * Validate dimensions of Image.
         *
         * @return {boolean|string}
         */
        Dimensions: function (value, element, params, callback) {
            if (!window.File || !window.FileReader || !window.FileList || !window.Blob) {
                return true
            }
            if (element.files === null || typeof element.files[0] === 'undefined') {
                return false
            }

            var fr = new FileReader()
            fr.onload = function () {
                var img = new Image()
                img.onload = function () {
                    var height = parseFloat(img.naturalHeight)
                    var width = parseFloat(img.naturalWidth)
                    var ratio = width / height
                    var notValid =
                        (params['width'] && parseFloat(params['width'] !== width)) ||
                        (params['min_width'] && parseFloat(params['min_width']) > width) ||
                        (params['max_width'] && parseFloat(params['max_width']) < width) ||
                        (params['height'] && parseFloat(params['height']) !== height) ||
                        (params['min_height'] && parseFloat(params['min_height']) > height) ||
                        (params['max_height'] && parseFloat(params['max_height']) < height) ||
                        (params['ratio'] && ratio !== parseFloat(eval(params['ratio'])))
                    callback(!notValid)
                }
                img.onerror = function () {
                    callback(false)
                }
                img.src = fr.result
            }
            fr.readAsDataURL(element.files[0])

            return 'pending'
        },

        /**
         * Validate that an attribute contains only alphabetic characters.
         *
         * @return {boolean}
         */
        Alpha: function (value) {
            if (typeof value !== 'string') {
                return false
            }

            var regex = new RegExp('^(?:^[a-z\u00E0-\u00FC]+$)$', 'i')
            return regex.test(value)
        },

        /**
         * Validate that an attribute contains only alpha-numeric characters.
         *
         * @return {boolean}
         */
        AlphaNum: function (value) {
            if (typeof value !== 'string') {
                return false
            }
            var regex = new RegExp('^(?:^[a-z0-9\u00E0-\u00FC]+$)$', 'i')
            return regex.test(value)
        },

        /**
         * Validate that an attribute contains only alphabetic characters.
         *
         * @return {boolean}
         */
        AlphaDash: function (value) {
            if (typeof value !== 'string') {
                return false
            }
            var regex = new RegExp('^(?:^[a-z0-9\u00E0-\u00FC_-]+$)$', 'i')
            return regex.test(value)
        },

        /**
         * Validate that an attribute passes a regular expression check.
         *
         * @return {boolean}
         */
        Regex: function (value, element, params) {
            var invalidModifiers = ['x', 's', 'u', 'X', 'U', 'A']
            // Converting php regular expression
            var phpReg = new RegExp('^(?:/)(.*\\/?[^/]*|[^/]*)(?:/)([gmixXsuUAJ]*)?$')
            var matches = params[0].match(phpReg)
            if (matches === null) {
                return false
            }
            // checking modifiers
            var php_modifiers = []
            if (matches[2] !== undefined) {
                php_modifiers = matches[2].split('')
                for (var i = 0; i < php_modifiers.length < i; i++) {
                    if (invalidModifiers.indexOf(php_modifiers[i]) !== -1) {
                        return true
                    }
                }
            }
            var regex = new RegExp('^(?:' + matches[1] + ')$', php_modifiers.join())
            return regex.test(value)
        },

        /**
         * Validate that an attribute is a valid date.
         *
         * @return {boolean}
         */
        Date: function (value) {
            return laravelValidation.helpers.strtotime(value) !== false
        },

        /**
         * Validate that an attribute matches a date format.
         *
         * @return {boolean}
         */
        DateFormat: function (value, element, params) {
            return laravelValidation.helpers.parseTime(value, params[0]) !== false
        },

        /**
         * Validate the date is before a given date.
         *
         * @return {boolean}
         */
        Before: function (value, element, params) {
            var timeCompare = parseFloat(params)
            if (isNaN(timeCompare)) {
                var target = laravelValidation.helpers.dependentElement(this, element, params)
                if (target === undefined) {
                    return false
                }
                timeCompare = laravelValidation.helpers.parseTime(this.elementValue(target), target)
            }

            var timeValue = laravelValidation.helpers.parseTime(value, element)
            return timeValue !== false && timeValue < timeCompare
        },

        /**
         * Validate the date is after a given date.
         *
         * @return {boolean}
         */
        After: function (value, element, params) {
            var timeCompare = parseFloat(params)
            if (isNaN(timeCompare)) {
                var target = laravelValidation.helpers.dependentElement(this, element, params)
                if (target === undefined) {
                    return false
                }
                timeCompare = laravelValidation.helpers.parseTime(this.elementValue(target), target)
            }

            var timeValue = laravelValidation.helpers.parseTime(value, element)
            return timeValue !== false && timeValue > timeCompare
        },

        /**
         * Validate that an attribute is a valid date.
         */
        Timezone: function (value) {
            return laravelValidation.helpers.isTimezone(value)
        },

        /**
         * Validate the attribute is a valid JSON string.
         *
         * @param  value
         * @return bool
         */
        Json: function (value) {
            var result = true
            try {
                JSON.parse(value)
            } catch (e) {
                result = false
            }
            return result
        },
    },
})
