/**
 * Mootools enhancements
 *
 */

Number.extend({

	/**
	 * Returns a random number
	 * version: 1008.1718
	 * discuss at: http://phpjs.org/functions/rand    // +   original by: Leslie Hoare
	 *
	 */
	rand: function(min, max) {
		var argc = arguments.length;
		if (argc === 0) {
			min = 0;
			max = 2147483647;    } else if (argc === 1) {
			throw new Error('Warning: rand() expects exactly 2 parameters, 1 given');
		}
		return Math.floor(Math.random() * (max - min + 1)) + min;
	},

	round: function(num, dec) {
		var result = Math.round( Math.round( num * Math.pow( 10, dec + 1 ) ) / Math.pow( 10, 1 ) ) / Math.pow(10,dec);
		return result;
	},

	formatMoney: function(num, n)
	{
		var num = parseFloat(num);
		var sep = arguments[2] ? arguments[2] : ' ';
		var re = '\\d(?=(\\d{3})+' + (n > 0 ? '\\.' : '$') + ')';

		return num.toFixed(Math.max(0, ~~n)).replace(new RegExp(re, 'g'), '$&'+sep);
	}

});

Date.extend({

	/**
	 * Return current UNIX timestamp
	 * version: 1008.1718
	 * discuss at: http://phpjs.org/functions/time    // +   original by: GeekFG (http://geekfg.blogspot.com)
	 *
	 */
	time:function()
	{
		return Math.floor(new Date().getTime()/1000);
	},

	formatFromMySql: function(d, format)
	{
		var f = '';

		if (typeOf(d) != 'null')
		{
			// Split timestamp into [ Y, M, D, h, m, s ]
			var t = d.split(/[- :]/);

			if (t[0] != '0000')
			{
				// Apply each element to the Date function
				if (t.length < 6)
					var d = new Date(t[0], t[1]-1, t[2]);
				else
					var d = new Date(t[0], t[1]-1, t[2], t[3], t[4], t[5]);

				var h = d.getHours();
				var m = d.getMonth();

				for (var i = 0; i < format.length; i++) {
					switch(format.charAt(i)) {
						case '\\': i++; f+= format.charAt(i); break;
						case 'd': f += Date.leadZero(d.getDate()); break;
						case 'm': f += Date.leadZero(m + 1); break;
						case 'Y': f += d.getFullYear(); break;
						case 'y': f += (100 + t.getYear() + '').substring(1); break
						case 'H': f += Date.leadZero(h); break;
						case 'h': f += Date.leadZero(h % 12 ? h % 12 : 12); break;
						case 'i': f += Date.leadZero(d.getMinutes()); break;
						case 's': f += Date.leadZero(d.getSeconds()); break;
						case 'a': f += (h > 11 ? 'pm' : 'am'); break;
						case 'A': f += (h > 11 ? 'PM' : 'AM'); break;
						case '%': break;
						default:  f += format.charAt(i);
					}
				}
			}
		}
		return f;
	},

	leadZero: function(v) {
		return v < 10 ? '0'+v : v;
	},


	format: function(t, format) {
		var f = '';
		var h = t.getHours();
		var m = t.getMonth();

		for (var i = 0; i < format.length; i++) {
			switch(format.charAt(i)) {
				case '\\': i++; f+= format.charAt(i); break;
				case 'y': f += (100 + t.getYear() + '').substring(1); break
				case 'Y': f += t.getFullYear(); break;
				case 'm': f += this.leadZero(m + 1); break;
				case 'n': f += (m + 1); break;
				case 'M': f += this.options.months[m].substring(0,this.options.monthShort); break;
				case 'F': f += this.options.months[m]; break;
				case 'd': f += this.leadZero(t.getDate()); break;
				case 'j': f += t.getDate(); break;
				case 'D': f += this.options.days[t.getDay()].substring(0,this.options.dayShort); break;
				case 'l': f += this.options.days[t.getDay()]; break;
				case 'G': f += h; break;
				case 'H': f += this.leadZero(h); break;
				case 'g': f += (h % 12 ? h % 12 : 12); break;
				case 'h': f += this.leadZero(h % 12 ? h % 12 : 12); break;
				case 'a': f += (h > 11 ? 'pm' : 'am'); break;
				case 'A': f += (h > 11 ? 'PM' : 'AM'); break;
				case 'i': f += this.leadZero(t.getMinutes()); break;
				case 's': f += this.leadZero(t.getSeconds()); break;
				case 'U': f += Math.floor(t.valueOf() / 1000); break;
				default:  f += format.charAt(i);
			}
		}
		return f;
	},
	
	mysqlToUnix: function(date)
	{
		if (typeOf(date) != 'null')
			return Math.floor(new Date(date.replace(' ', 'T')).getTime() / 1000);
		else
			return Math.floor(new Date('1970-01-01T01:00:00').getTime() / 1000);
	},

	getNumberMonth: function(date)
	{
		return ("0" + (date.getMonth() + 1)).slice(-2);
	},
	
	diff: function(d1, d2, period)
	{
		var diff = 0;

		if(typeof(d1) == 'string') d1 = new Date(d1);
		if(typeof(d2) == 'string') d2 = new Date(d2);

		switch(period)
		{
			case 'days':
				var t2 = d2.getTime();
				var t1 = d1.getTime();
				diff = parseInt((t2-t1)/(24*3600*1000));
				break;

			case 'weeks':
				var t2 = d2.getTime();
				var t1 = d1.getTime();
				diff = parseInt((t2-t1)/(24*3600*1000*7));
				break;

			case 'months':
				var d1Y = d1.getFullYear();
				var d2Y = d2.getFullYear();
				var d1M = d1.getMonth();
				var d2M = d2.getMonth();
				diff =  (d2M+12*d2Y)-(d1M+12*d1Y);
				break;

			case 'years':
				diff = d2.getFullYear()-d1.getFullYear();
				break;
		}
		return diff;
	}

});


String.extend({

	htmlspecialchars_decode: function(text)
	{
		var tmp = new Element('span',{ 'html':text });
		var ret_val = tmp.get('text');
		delete tmp;
		return ret_val;
	},

	strip_tags: function(html)
	{
		if(arguments.length < 3)
		{
			html = html.replace(/<\/?(?!\!)[^>]*>/gi, '');
		}
		else
		{
			var allowed = arguments[1];
			var specified = eval("["+arguments[2]+"]");
			if(allowed)
			{
				html = html.replace(new RegExp('</?(?!(' + specified.join('|') + '))\b[^>]*>', 'gi'), '');
			}
			else
			{
				html = html.replace(new RegExp('</?(' + specified.join('|') + ')\b[^>]*>', 'gi'), '');
			}
		}

		return html;
	},

	capitalize: function(text)
	{
		return text.charAt(0).toUpperCase() + text.slice(1);
	},

	nl2br: function(str, is_xhtml) {
		var breakTag = (is_xhtml || typeof is_xhtml === 'undefined') ? '<br />' : '<br>';
		return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ breakTag +'$2');
	}
});

Array.prototype.insert = function(index) {
	this.splice.apply(this, [index, 0].concat(
		Array.prototype.slice.call(arguments, 1)));
	return this;
};

/*
 * @function
 * @name Object.prototype.inArray
 * @description Extend Object prototype within inArray function
 *
 * @param {mix}    needle       - Search-able needle
 * @param {bool}   searchInKey  - Search needle in keys?
 *
 */
Object.defineProperty(Object.prototype, 'containsObject',{
	value: function(needle, searchInKey){

		var object = this;

		if( Object.prototype.toString.call(needle) === '[object Object]' ||
			Object.prototype.toString.call(needle) === '[object Array]'){
			needle = JSON.stringify(needle);
		}

		return Object.keys(object).some(function(key){

			var value = object[key];

			if( Object.prototype.toString.call(value) === '[object Object]' ||
				Object.prototype.toString.call(value) === '[object Array]'){
				value = JSON.stringify(value);
			}

			if(searchInKey){
				if(value === needle || key === needle){
					return true;
				}
			}else{
				if(value === needle){
					return true;
				}
			}
		});
	},
	writable: true,
	configurable: true,
	enumerable: false
});




FocusTracker = {
    startFocusTracking: function() {
       this.store('hasFocus', false);
       this.addEvent('focus', function() { this.store('hasFocus', true); });
       this.addEvent('blur', function() { this.store('hasFocus', false); });
    },
	
    hasFocus: function() {
       return this.retrieve('hasFocus');
    }
};

Element.implement(FocusTracker);

/**
 * Transform one form into JSON object
 */
Element.implement(
{
	toJSON: function()
	{
		var json = {};

		this.getElements('input, select, textarea', true).each(function(el)
		{
			if (!el.name || el.disabled || el.type == 'submit' || el.type == 'reset' || el.type == 'file') return;
			var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){
				return opt.value;
			}) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value;

			// "." notation for input names
			var names = el.name.split('.'),
				obj = json;

			for (var i=0; i<names.length; i++)
			{
				if (names[i+1])
				{
					if ( ! obj[names[i]]) obj[names[i]] = {};
					obj = obj[names[i]];
				}
			}

			if (typeOf(value) == 'array')
			{
				if (el.type == 'select-one')
				{
					obj[el.name] = value[0];
				}
				else
				{
					obj[el.name] = [];
					Array.each(value, function(val)
					{
						obj[el.name].push(val);
					});
				}
			}
			else
			{
				obj[el.name] = value;
			}
		});

		return json;
	}
});

String.implement({
	toElement: (function() {
		var div = new Element('div');
		return function(oEv) {
			return div.set('html', this).getFirst().addEvents(oEv);
		}
	})()
});


Object.append(Element.NativeEvents, {
	dragenter: 2, dragleave: 2, dragover: 2, dragend: 2, drop: 2
});

(function(window) {
	var re = {
		not_string: /[^s]/,
		number: /[def]/,
		text: /^[^\x25]+/,
		modulo: /^\x25{2}/,
		placeholder: /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/,
		key: /^([a-z_][a-z_\d]*)/i,
		key_access: /^\.([a-z_][a-z_\d]*)/i,
		index_access: /^\[(\d+)\]/,
		sign: /^[\+\-]/
	};

	function sprintf() {
		var key = arguments[0], cache = sprintf.cache;
		if (!(cache[key] && cache.hasOwnProperty(key))) {
			cache[key] = sprintf.parse(key)
		}
		return sprintf.format.call(null, cache[key], arguments)
	}

	sprintf.format = function(parse_tree, argv) {
		var cursor = 1, tree_length = parse_tree.length, node_type = "", arg, output = [], i, k, match, pad, pad_character, pad_length, is_positive = true, sign = ""
		for (i = 0; i < tree_length; i++) {
			node_type = get_type(parse_tree[i]);
			if (node_type === "string") {
				output[output.length] = parse_tree[i]
			}
			else if (node_type === "array") {
				match = parse_tree[i]; // convenience purposes only
				if (match[2]) { // keyword argument
					arg = argv[cursor];
					for (k = 0; k < match[2].length; k++) {
						if (!arg.hasOwnProperty(match[2][k])) {
							throw new Error(sprintf("[sprintf] property '%s' does not exist", match[2][k]))
						}
						arg = arg[match[2][k]]
					}
				}
				else if (match[1]) { // positional argument (explicit)
					arg = argv[match[1]]
				}
				else { // positional argument (implicit)
					arg = argv[cursor++]
				}

				if (get_type(arg) == "function") {
					arg = arg()
				}

				if (re.not_string.test(match[8]) && (get_type(arg) != "number" && isNaN(arg))) {
					throw new TypeError(sprintf("[sprintf] expecting number but found %s", get_type(arg)))
				}

				if (re.number.test(match[8])) {
					is_positive = arg >= 0
				}

				switch (match[8]) {
					case "b":
						arg = arg.toString(2);
						break;

					case "c":
						arg = String.fromCharCode(arg);
						break;

					case "d":
						arg = parseInt(arg, 10);
						break;

					case "e":
						arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential();
						break;

					case "f":
						arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg);
						break;

					case "o":
						arg = arg.toString(8);
						break;

					case "s":
						arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg);
						break;

					case "u":
						arg = arg >>> 0;
						break;

					case "x":
						arg = arg.toString(16);
						break;

					case "X":
						arg = arg.toString(16).toUpperCase();
						break
				}
				if (!is_positive || (re.number.test(match[8]) && match[3])) {
					sign = is_positive ? "+" : "-";
					arg = arg.toString().replace(re.sign, "")
				}
				pad_character = match[4] ? match[4] == "0" ? "0" : match[4].charAt(1) : " ";
				pad_length = match[6] - (sign + arg).length;
				pad = match[6] ? str_repeat(pad_character, pad_length) : "";
				output[output.length] = match[5] ? sign + arg + pad : (pad_character == 0 ? sign + pad + arg : pad + sign + arg)
			}
		}
		return output.join("")
	};

	sprintf.cache = {};
	sprintf.parse = function(fmt) {
		var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
		while (_fmt) {
			if ((match = re.text.exec(_fmt)) !== null) {
				parse_tree[parse_tree.length] = match[0]
			}
			else if ((match = re.modulo.exec(_fmt)) !== null) {
				parse_tree[parse_tree.length] = "%"
			}
			else if ((match = re.placeholder.exec(_fmt)) !== null) {
				if (match[2]) {
					arg_names |= 1;
					var field_list = [], replacement_field = match[2], field_match = [];
					if ((field_match = re.key.exec(replacement_field)) !== null) {
						field_list[field_list.length] = field_match[1];
						while ((replacement_field = replacement_field.substring(field_match[0].length)) !== "") {
							if ((field_match = re.key_access.exec(replacement_field)) !== null) {
								field_list[field_list.length] = field_match[1];
							}
							else if ((field_match = re.index_access.exec(replacement_field)) !== null) {
								field_list[field_list.length] = field_match[1];
							}
							else {
								throw new SyntaxError("[sprintf] failed to parse named argument key");
							}
						}
					}
					else {
						throw new SyntaxError("[sprintf] failed to parse named argument key");
					}
					match[2] = field_list;
				}
				else {
					arg_names |= 2
				}
				if (arg_names === 3) {
					throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");
				}
				parse_tree[parse_tree.length] = match
			}
			else {
				throw new SyntaxError("[sprintf] unexpected placeholder");
			}
			_fmt = _fmt.substring(match[0].length);
		}
		return parse_tree
	};

	var vsprintf = function(fmt, argv, _argv) {
		_argv = (argv || []).slice(0);
		_argv.splice(0, 0, fmt);
		return sprintf.apply(null, _argv);
	};

	/**
	 * helpers
	 */
	function get_type(variable) {
		return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
	}

	function str_repeat(input, multiplier) {
		return Array(multiplier + 1).join(input);
	}

	/**
	 * export to either browser or node.js
	 */
	if (typeof exports !== "undefined") {
		exports.sprintf = sprintf;
		exports.vsprintf = vsprintf;
	}
	else {
		window.sprintf = sprintf;
		window.vsprintf = vsprintf;

		if (typeof define === "function" && define.amd) {
			define(function() {
				return {
					sprintf: sprintf,
					vsprintf: vsprintf
				}
			})
		}
	}
})(typeof window === "undefined" ? this : window);


String.prototype.trimLeft = function(charlist) {
	if (charlist === undefined)
		charlist = "\s";

	return this.replace(new RegExp("^[" + charlist + "]+"), "");
};
String.prototype.trimRight = function(charlist) {
	if (charlist === undefined)
		charlist = "\s";

	return this.replace(new RegExp("[" + charlist + "]+$"), "");
};
String.prototype.charTrim = function(charlist) {
	return this.trimLeft(charlist).trimRight(charlist);
};

/*
 ---

 script: Array.sortOn.js

 description: Adds Array.sortOn function and related constants that works like in ActionScript for sorting arrays of objects (applying all same strict rules)

 license: MIT-style license.

 authors:
 - gonchuki

 docs: http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/Array.html#sortOn()

 requires:
 - core/1.2.4: [Array]

 provides:
 - [Array.sortOn, Array.CASEINSENSITIVE, Array.DESCENDING, Array.UNIQUESORT, Array.RETURNINDEXEDARRAY, Array.NUMERIC]

 ...
 */

Array.CASEINSENSITIVE = 1;
Array.DESCENDING = 2;
Array.UNIQUESORT = 4;
Array.RETURNINDEXEDARRAY = 8;
Array.NUMERIC = 16;

(function() {
	var dup_fn = function(field, field_options) {
		var filtered = (field_options & Array.NUMERIC)
			? this.map(function(item) {return item[field].toFloat(); })
			: (field_options & Array.CASEINSENSITIVE)
			? this.map(function(item) {return item[field].toLowerCase(); })
			: this.map(function(item) {return item[field]; });
		return filtered.length !== [].combine(filtered).length;
	};

	var sort_fn = function(item_a, item_b, fields, options) {
		return (function sort_by(fields, options) {
			var ret, a, b,
				opts = options[0],
				sub_fields = fields[0].match(/[^.]+/g);

			(function get_values(s_fields, s_a, s_b) {
				var field = s_fields[0];
				if (s_fields.length > 1) {
					get_values(s_fields.slice(1), s_a[field], s_b[field]);
				} else {
					a = s_a[field].toString();
					b = s_b[field].toString();
				}
			})(sub_fields, item_a, item_b);

			if (opts & Array.NUMERIC) {
				ret = (a.toFloat() - b.toFloat());
			} else {
				if (opts & Array.CASEINSENSITIVE) { a = a.toLowerCase(); b = b.toLowerCase(); }

				ret = (a > b) ? 1 : (a < b) ? -1 : 0;
			}

			if ((ret === 0) && (fields.length > 1)) {
				ret = sort_by(fields.slice(1), options.slice(1));
			} else if (opts & Array.DESCENDING) {
				ret *= -1;
			}

			return ret;
		})(fields, options);
	};

	Array.implement({
		sortOn: function(fields, options) {
			fields = Array.from(fields);
			options = Array.from(options);

			if (options.length !== fields.length) options = [];

			if ((options[0] & Array.UNIQUESORT) && (fields.some(function(field, i){return dup_fn(field, options[i]);}))) return 0;

			var curry_sort = function(item_a, item_b) {
				return sort_fn(item_a, item_b, fields, options);
			};

			if (options[0] & Array.RETURNINDEXEDARRAY)
				return this.slice().sort(curry_sort);
			else
				this.sort(curry_sort);
		}
	});

})();


/**
 * Simple Pub/Sub pattern
 * Implementation of http://davidwalsh.name/pubsub-javascript
 *
 */
Events.topics = {};
Events.hOP = Events.topics.hasOwnProperty;

Events.subscribe= function(topic, listener)
{
	// Create the topic's object if not yet created
	if( ! Events.hOP.call(Events.topics, topic)) Events.topics[topic] = [];

	// Add listener to queue
	var index = Events.topics[topic].push(listener) -1;

	// Provide handle back for removal of a listener for a topic
	return {
		remove: function() {
			delete Events.topics[topic][index];
		}
	};
};

Events.publish= function(topic, info)
{
	// If the topic doesn't exist, or there's no listeners in queue, just leave
	if( ! Events.hOP.call(Events.topics, topic)) return;

	// Cycle through topics queue, fire!
	Events.topics[topic].forEach(function(item) {
		item(info||{});
	});
};

// Prevent enter to submit the form
$(window).addEvent('keypress',function(e)
{
	if(e.key == 'enter')
	{
		if(e.target.get('tag') == 'input')
		{
			e.stop();
			var form = e.target.getParent('form');
			if (form)
			{
				var inputs = form.getElements('input[type!="hidden"]');
				if (typeOf(inputs) != 'null')
				{
					var i = inputs.indexOf(e.target);
					if (inputs[i+1])
						inputs[i+1].focus();
					else
					{
						inputs[0].focus();
					}
				}
			}
		}
	}
});
