/**
* @license
* jQuery Tools Validator @VERSION - HTML5 is here. Now use it.
*
* NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
*
* http://flowplayer.org/tools/form/validator/
*
* Since: Mar 2010
* Date: @DATE
*/
/*jslint evil: true */
(function($) {

    $.tools = $.tools || {
        version: '@VERSION'
    };

    // globals
    var typeRe = /\[type=([a-z]+)\]/,
    numRe = /^-?[0-9]*(\.[0-9]+)?$/,
    dateInput = $.tools.dateinput,

    // http://net.tutsplus.com/tutorials/other/8-regular-expressions-you-should-know/
    emailRe = /^([a-z0-9_\.\-\+]+)@([\da-z\.\-]+)\.([a-z\.]{2,6})$/i,
    urlRe = /^(https?:\/\/)?[\da-z\.\-]+\.[a-z\.]{2,6}[#&+_\?\/\w \.\-=]*$/i,
    v;

    v = $.tools.validator = {

        conf: {
            grouped: false, // show all error messages at once inside the container
            effect: 'default', // show/hide effect for error message. only 'default' is built-in
            errorClass: 'invalid', // input field class name in case of validation error

            // when to check for validity?
            inputEvent: null, // change, blur, keyup, null
            errorInputEvent: 'keyup', // change, blur, keyup, null
            formEvent: 'submit', // submit, null

            lang: 'en', // default language for error messages
            message: '<div/>',
            messageAttr: 'data-message', // name of the attribute for overridden error message
            messageClass: 'error', // error message element's class name
            offset: [0, 0],
            position: 'center right',
            singleError: false, // validate all inputs at once
            speed: 'normal' // message's fade-in speed
        },


        /* The Error Messages */
        messages: {
            "*": {
                en: "Please correct this value"
            }
        },

        localize: function(lang, messages) {
            $.each(messages, function(key, msg) {
                v.messages[key] = v.messages[key] || {};
                v.messages[key][lang] = msg;
            });
        },

        localizeFn: function(key, messages) {
            v.messages[key] = v.messages[key] || {};
            $.extend(v.messages[key], messages);
        },

        /**
* Adds a new validator
*/
        fn: function(matcher, msg, fn) {

            // no message supplied
            if ($.isFunction(msg)) {
                fn = msg;

            // message(s) on second argument
            } else {
                if (typeof msg == 'string') {
                    msg = {
                        en: msg
                    };                
                }
                this.messages[matcher.key || matcher] = msg;
            }

            // check for "[type=xxx]" (not supported by jQuery)
            var test = typeRe.exec(matcher);
            if (test) {
                matcher = isType(test[1]);
            }

            // add validator to the arsenal
            fns.push([matcher, fn]);
        },

        /* Add new show/hide effect */
        addEffect: function(name, showFn, closeFn) {
            effects[name] = [showFn, closeFn];
        }

    };

    /* calculate error message position relative to the input */
    function getPosition(trigger, el, conf) {

        // get origin top/left position
        var top = trigger.offset().top,
        left = trigger.offset().left,
        pos = conf.position.split(/,?\s+/),
        y = pos[0],
        x = pos[1];

        top -= el.outerHeight() - conf.offset[0];
        left += trigger.outerWidth() + conf.offset[1];


        // iPad position fix
        if (/iPad/i.test(navigator.userAgent)) {
            top -= $(window).scrollTop();
        }

        // adjust Y
        var height = el.outerHeight() + trigger.outerHeight();
        if (y == 'center') {
            top += height / 2;
        }
        if (y == 'bottom') {
            top += height;
        }

        // adjust X
        var width = trigger.outerWidth();
        if (x == 'center') {
            left -= (width + el.outerWidth()) / 2;
        }
        if (x == 'left') {
            left -= width;
        }

        return {
            top: top, 
            left: left
        };
    }



    // $.is("[type=xxx]") or $.filter("[type=xxx]") not working in jQuery 1.3.2 or 1.4.2
    function isType(type) {
        function fn() {
            return this.getAttribute("type") == type;
        }
        fn.key = "[type=" + type + "]";
        return fn;
    }


    var fns = [], effects = {

        'default' : [

        // show errors function
        function(errs) {

            var conf = this.getConf();

            // loop errors
            $.each(errs, function(i, err) {

                // add error class
                var input = err.input;
                input.addClass(conf.errorClass);

                // get handle to the error container
                var msg = input.data("msg.el");

                // create it if not present
                if (!msg) {
                    msg = $(conf.message).addClass(conf.messageClass).appendTo(document.body);
                    input.data("msg.el", msg);
                }

                // clear the container
                msg.css({
                    visibility: 'hidden'
                }).find("p").remove();

                // populate messages
                $.each(err.messages, function(i, m) {
                    $("<p/>").html(m).appendTo(msg);
                });

                // make sure the width is not full body width so it can be positioned correctly
                if (msg.outerWidth() == msg.parent().width()) {
                    msg.add(msg.find("p")).css({
                        display: 'inline'
                    });
                }

                msg.css({
                    visibility: 'hidden', 
                    position: 'absolute'
                }).show();

                // insert into correct position (relative to the field)
                var pos = getPosition(input, msg, conf); 

                msg.css({
                    visibility: 'visible', 
                    top: pos.top, 
                    left: pos.left
                })
                .fadeIn(conf.speed);


                msg.css({
                    visibility: 'visible', 
                    position: 'absolute', 
                    top: pos.top, 
                    left: pos.left
                })
                .fadeIn(conf.speed);
            });


        // hide errors function
        }, function(inputs) {

            var conf = this.getConf();
            inputs.removeClass(conf.errorClass).each(function() {
                var msg = $(this).data("msg.el");
                if (msg) {
                    msg.css({
                        visibility: 'hidden'
                    });
                }
            });
        }
        ]
    };


    /* sperial selectors */
    $.each("email,url,number".split(","), function(i, key) {
        $.expr[':'][key] = function(el) {
            return el.getAttribute("type") === key;
        };
    });


    /*
oninvalid() jQuery plugin.
Usage: $("input:eq(2)").oninvalid(function() { ... });
*/
    $.fn.oninvalid = function( fn ){
        return this[fn ? "bind" : "trigger"]("OI", fn);
    };


    /******* built-in HTML5 standard validators *********/

    v.fn(":email", "Please enter a valid email address", function(el, v) {
        return !v || emailRe.test(v);
    });

    v.fn(":url", "Please enter a valid URL", function(el, v) {
        return !v || urlRe.test(v);
    });

    v.fn(":number", "Please enter a numeric value.", function(el, v) {
        return numRe.test(v);
    });

    v.fn("[max]", "Please enter a value smaller than $1", function(el, v) {

        // skip empty values and dateinputs
        if (v === '' || dateInput && el.is(":date")) {
            return true;
        }

        var max = el.attr("max");
        return parseFloat(v) <= parseFloat(max) ? true : [max];
    });

    v.fn("[min]", "Please enter a value larger than $1", function(el, v) {

        // skip empty values and dateinputs
        if (v === '' || dateInput && el.is(":date")) {
            return true;
        }

        var min = el.attr("min");
        return parseFloat(v) >= parseFloat(min) ? true : [min];
    });

    v.fn("[required]", "Please complete this mandatory field.", function(el, v) {
        if (el.is(":checkbox")) {
            return el.is(":checked");
        }
        return !!v;
    });

    v.fn("[pattern]", function(el) {
        var p = new RegExp("^" + el.attr("pattern") + "$");
        return p.test(el.val());
    });


    function Validator(inputs, form, conf) {

        // private variables
        var self = this,
        fire = form.add(self);

        // make sure there are input fields available
        inputs = inputs.not(":button, :image, :reset, :submit");

        // utility function
        function pushMessage(to, matcher, returnValue) {

            // only one message allowed
            if (!conf.grouped && to.length) {
                return;
            }

            // the error message
            var msg;

            // substitutions are returned
            if (returnValue === false || $.isArray(returnValue)) {
                msg = v.messages[matcher.key || matcher] || v.messages["*"];
                msg = msg[conf.lang] || v.messages["*"].en;

                // substitution
                var matches = msg.match(/\$\d/g);

                if (matches && $.isArray(returnValue)) {
                    $.each(matches, function(i) {
                        msg = msg.replace(this, returnValue[i]);
                    });
                }

            // error message is returned directly
            } else {
                msg = returnValue[conf.lang] || returnValue;
            }

            to.push(msg);
        }


        // API methods
        $.extend(self, {

            getConf: function() {
                return conf;
            },

            getForm: function() {
                return form;
            },

            getInputs: function() {
                return inputs;
            },

            reflow: function() {
                inputs.each(function() {
                    var input = $(this),
                    msg = input.data("msg.el");

                    if (msg) {
                        var pos = getPosition(input, msg, conf);
                        msg.css({
                            top: pos.top, 
                            left: pos.left
                        });
                    }
                });
                return self;
            },

            /* @param e - for internal use only */
            invalidate: function(errs, e) {

                // errors are given manually: { fieldName1: 'message1', fieldName2: 'message2' }
                if (!e) {
                    var errors = [];
                    $.each(errs, function(key, val) {
                        var input = inputs.filter("[name='" + key + "']");
                        if (input.length) {

                            // trigger HTML5 ininvalid event
                            input.trigger("OI", [val]);

                            errors.push({
                                input: input, 
                                messages: [val]
                            });
                        }
                    });

                    errs = errors;
                    e = $.Event();
                }

                // onFail callback
                e.type = "onFail";
                fire.trigger(e, [errs]);

                // call the effect
                if (!e.isDefaultPrevented()) {
                    effects[conf.effect][0].call(self, errs, e);
                }

                return self;
            },

            reset: function(els) {
                els = els || inputs;
                els.removeClass(conf.errorClass).each(function() {
                    var msg = $(this).data("msg.el");
                    if (msg) {
                        msg.remove();
                        $(this).data("msg.el", null);
                    }
                }).unbind(conf.errorInputEvent || '');
                return self;
            },

            destroy: function() {
                form.unbind(conf.formEvent + ".V").unbind("reset.V");
                inputs.unbind(conf.inputEvent + ".V").unbind("change.V");
                return self.reset();
            },


            //{{{ checkValidity() - flesh and bone of this tool

            /* @returns boolean */
            checkValidity: function(els, e) {

                els = els || inputs;
                els = els.not(":disabled");
                if (!els.length) {
                    return true;
                }

                e = e || $.Event();

                // onBeforeValidate
                e.type = "onBeforeValidate";
                fire.trigger(e, [els]);
                if (e.isDefaultPrevented()) {
                    return e.result;
                }

                // container for errors
                var errs = [];
 
                // loop trough the inputs
                els.not(":radio:not(:checked)").each(function() {

                    // field and it's error message container
                    var msgs = [],
                    el = $(this).data("messages", msgs),
                    event = dateInput && el.is(":date") ? "onHide.v" : conf.errorInputEvent + ".v";

                    // cleanup previous validation event
                    el.unbind(event);


                    // loop all validator functions
                    $.each(fns, function() {
                        var fn = this, match = fn[0];

                        // match found
                        if (el.filter(match).length) {

                            // execute a validator function
                            var returnValue = fn[1].call(self, el, el.val());


                            // validation failed. multiple substitutions can be returned with an array
                            if (returnValue !== true) {

                                // onBeforeFail
                                e.type = "onBeforeFail";
                                fire.trigger(e, [el, match]);
                                if (e.isDefaultPrevented()) {
                                    return false;
                                }

                                // overridden custom message
                                var msg = el.attr(conf.messageAttr);
                                if (msg) {
                                    msgs = [msg];
                                    return false;
                                } else {
                                    pushMessage(msgs, match, returnValue);
                                }
                            }
                        }
                    });

                    if (msgs.length) {

                        errs.push({
                            input: el, 
                            messages: msgs
                        });

                        // trigger HTML5 ininvalid event
                        el.trigger("OI", [msgs]);

                        // begin validating upon error event type (such as keyup)
                        if (conf.errorInputEvent) {
                            el.bind(event, function(e) {
                                self.checkValidity(el, e);
                            });
                        }
                    }

                    if (conf.singleError && errs.length) {
                        return false;
                    }

                });


                // validation done. now check that we have a proper effect at hand
                var eff = effects[conf.effect];
                if (!eff) {
                    throw "Validator: cannot find effect \"" + conf.effect + "\"";
                }

                // errors found
                if (errs.length) {
                    self.invalidate(errs, e);
                    return false;

                // no errors
                } else {

                    // call the effect
                    eff[1].call(self, els, e);

                    // onSuccess callback
                    e.type = "onSuccess";
                    fire.trigger(e, [els]);

                    els.unbind(conf.errorInputEvent + ".v");
                }

                return true;
            }
        //}}}

        });

        // callbacks
        $.each("onBeforeValidate,onBeforeFail,onFail,onSuccess".split(","), function(i, name) {

            // configuration
            if ($.isFunction(conf[name])) {
                $(self).bind(name, conf[name]);
            }

            // API methods
            self[name] = function(fn) {
                if (fn) {
                    $(self).bind(name, fn);
                }
                return self;
            };
        });


        // form validation
        if (conf.formEvent) {
            form.bind(conf.formEvent + ".V", function(e) {
                if (!self.checkValidity(null, e)) {
                    return e.preventDefault();
                }
            });
        }

        // form reset
        form.bind("reset.V", function() {
            self.reset();
        });

        // disable browser's default validation mechanism
        if (inputs[0] && inputs[0].validity) {
            inputs.each(function() {
                this.oninvalid = function() {
                    return false;
                };
            });
        }

        // Web Forms 2.0 compatibility
        if (form[0]) {
            form[0].checkValidity = self.checkValidity;
        }

        // input validation
        if (conf.inputEvent) {
            inputs.bind(conf.inputEvent + ".V", function(e) {
                self.checkValidity($(this), e);
            });
        }

        // checkboxes, selects and radios are checked separately
        inputs.filter(":checkbox, select").filter("[required]").bind("change.V", function(e) {
            var el = $(this);
            if (this.checked || (el.is("select") && $(this).val())) {
                effects[conf.effect][1].call(self, el, e);
            }
        });

        var radios = inputs.filter(":radio").change(function(e) {
            self.checkValidity(radios, e);
        });

        // reposition tooltips when window is resized
        $(window).resize(function() {
            self.reflow();
        });
    }


    // jQuery plugin initialization
    $.fn.validator = function(conf) {

        var instance = this.data("validator");

        // destroy existing instance
        if (instance) {
            instance.destroy();
            this.removeData("validator");
        }

        // configuration
        conf = $.extend(true, {}, v.conf, conf);

        // selector is a form
        if (this.is("form")) {
            return this.each(function() {
                var form = $(this);
                instance = new Validator(form.find(":input"), form, conf);
                form.data("validator", instance);
            });

        } else {
            instance = new Validator(this, this.eq(0).closest("form"), conf);
            return this.data("validator", instance);
        }

    };

})(jQuery);

(function() {
    var l_icon  =   '<img src="/_public/_static/imgs/invalid.png" alt="valid" width="26" height="28" align="absmiddle" />';
                    
$.tools.validator.localize("icon", {
    '*'			: l_icon,
    ':email'  	: l_icon,
    ':number' 	: l_icon,
    ':url' 		: l_icon,
    '[max]'	 	: l_icon,
    '[min]'		: l_icon,
    '[required]': l_icon
});
})();

