/**
* @license
* jQuery Tools Validator 1.2.5 - HTML5 is here. Now use it.
* Modified by kwicher(@gmail.com)
* NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
*
* http://flowplayer.org/tools/form/validator/
*
* Since: Mar 2010
* Date: Wed Sep 22 06:02:10 2010 +0000
*/
/*jslint evil: true */

function($) {

$.tools = $.tools || {version: '1.2.5'};

// 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];
}

};

/*KWICHER: OBSOLETE*/
/* 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) {

//KWICHER: attach the errormessage to the INPUT
msg = $(conf.message).addClass(conf.messageClass).insertAfter(input);
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);
});

//KWICHER: semms unnecessary
// // 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'});
// }

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

//KWICHER: positioning of the error message
msg.add(msg.find("p")).css({ display: 'inline'});
msg.css({visibility: 'visible', position: 'relative', top: '0px', left: '0px'})
.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); <- KWICHER: obsolete

//KWICHER: positioning
msg.css({ position:'relative', top: '0px', left: '0px' });
}
});
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 -- kwicher: As for me it does not do anything
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);
}

};

};
