(function(define) {
    'use strict';
    define([
        'jquery',
        'underscore',
        'underscore.string',
        'gettext'
    ],
        function($, _, _s, gettext) {
            var utils;

        /* Mix non-conflicting functions from underscore.string
         * (all but include, contains, and reverse) into the
         * Underscore namespace. In practice, this mixin is done
         * by the access view, but doing it here helps keep the
         * utility self-contained.
         */
            _.mixin(_s.exports());

            utils = (function() {
                var _fn = {
                    validate: {

                        template: _.template('<li><%= content %></li>'),

                        msg: {
                            email: gettext("The email address you've provided isn't formatted correctly."),
                            min: gettext('%(field)s must have at least %(count)d characters.'),
                            max: gettext('%(field)s can only contain up to %(count)d characters.'),
                            required: gettext('Please enter your %(field)s.')
                        },

                        field: function(el) {
                            var $el = $(el),
                                required = true,
                                min = true,
                                max = true,
                                email = true,
                                response = {},
                                isBlank = _fn.validate.isBlank($el);

                            if (_fn.validate.isRequired($el)) {
                                if (isBlank) {
                                    required = false;
                                } else {
                                    min = _fn.validate.str.minlength($el);
                                    max = _fn.validate.str.maxlength($el);
                                    email = _fn.validate.email.valid($el);
                                }
                            } else if (!isBlank) {
                                min = _fn.validate.str.minlength($el);
                                max = _fn.validate.str.maxlength($el);
                                email = _fn.validate.email.valid($el);
                            }

                            response.isValid = required && min && max && email;

                            if (!response.isValid) {
                                _fn.validate.removeDefault($el);

                                response.message = _fn.validate.getMessage($el, {
                                    required: required,
                                    min: min,
                                    max: max,
                                    email: email
                                });
                            }

                            return response;
                        },

                        str: {
                            minlength: function($el) {
                                var min = $el.attr('minlength') || 0;

                                return min <= $el.val().length;
                            },

                            maxlength: function($el) {
                                var max = $el.attr('maxlength') || false;

                                return (!!max) ? max >= $el.val().length : true;
                            }
                        },

                        isRequired: function($el) {
                            return $el.attr('required');
                        },

                        isBlank: function($el) {
                            var type = $el.attr('type'),
                                isBlank;

                            if (type === 'checkbox') {
                                isBlank = !$el.prop('checked');
                            } else if (type === 'select') {
                                isBlank = ($el.data('isdefault') === true);
                            } else {
                                isBlank = !$el.val();
                            }

                            return isBlank;
                        },

                        email: {
                        // This is the same regex used to validate email addresses in Django 1.4
                            regex: new RegExp(
                            [
                                '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*',
                                '|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"',
                                ')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+[A-Z]{2,6}\\.?$)',
                                '|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$'
                            ].join(''), 'i'
                        ),

                            valid: function($el) {
                                return $el.attr('type') === 'email' ? _fn.validate.email.format($el.val()) : true;
                            },

                            format: function(str) {
                                return _fn.validate.email.regex.test(str);
                            }
                        },

                        getLabel: function(id) {
                        // Extract the field label, remove the asterisk (if it appears) and any extra whitespace
                            return $('label[for=' + id + ']').text().split('*')[0].trim();
                        },

                        getMessage: function($el, tests) {
                            var txt = [],
                                label,
                                context,
                                content,
                                customMsg;

                            _.each(tests, function(value, key) {
                                if (!value) {
                                    label = _fn.validate.getLabel($el.attr('id'));
                                    customMsg = $el.data('errormsg-' + key) || false;

                                // If the field has a custom error msg attached, use it
                                    if (customMsg) {
                                        content = customMsg;
                                    } else {
                                        context = {field: label};

                                        if (key === 'min') {
                                            context.count = parseInt($el.attr('minlength'), 10);
                                        } else if (key === 'max') {
                                            context.count = parseInt($el.attr('maxlength'), 10);
                                        }

                                        content = _.sprintf(_fn.validate.msg[key], context);
                                    }

                                    txt.push(_fn.validate.template({content: content}));
                                }
                            });

                            return txt.join(' ');
                        },

                    // Removes the default HTML5 validation pop-up
                        removeDefault: function($el) {
                            if ($el.setCustomValidity) {
                                $el.setCustomValidity(' ');
                            }
                        }
                    }
                };

                return {
                    validate: _fn.validate.field
                };
            })();

            return utils;
        });
}).call(this, define || RequireJS.define);
