Number.prototype.clip = function(min, max, floor) {
	try {
	return Math.min(Math.max(floor ? Math.floor(this) : this, min), max);
	} catch(e) {alert("Error Number.prototype.clip");alert(e);}
};

Number.prototype.mapInterval = function(a,b,x,y) {
	return x + ((this-a) / (b-a) * (y-x));
}

function dichotomy(start, isBigger) {
	try {
	var i = 0, min = 0, max, half;
	for (max = start || 1; ++i < 10 && !isBigger(max); max *= 2);
	for (half = start; Math.abs(min-max) > 0.1; half = (min + max) / 2) {
		if (!isBigger(half)) min = half;
		else                 max = half;
	}
	while (half > 1 && isBigger(half)) { --half; }
	return half;
	} catch(e) {alert("Error dichotomy");alert(e);throw(e);}
}

$.fn.fold = function(acc, fn) {
	try {
	this.each(function(i,e) { acc = fn(acc, i, e); });
	return acc;
	} catch(e) {alert("Error $.fn.fold");alert(e);}
};

$.fn.maxWidth = function() { return this.fold(0, function(acc,i,e){ return Math.max(acc, $(e).width()); }); };
$.fn.maxHeight = function() { return this.fold(0, function(acc,i,e){ return Math.max(acc, $(e).height()); }); };
$.fn.sumHeight = function() { return this.fold(0, function(acc,i,e){ return acc + $(e).height(); }); };
$.fn.sumOuterHeight = function() { return this.fold(0, function(acc,i,e){ return acc + $(e).outerHeight(); }); };

$.fn.hasScroll = function() {
	var e = this.get(0);
	return (e.clientHeight != e.scrollHeight) || (e.clientWidth != e.scrollWidth);
}

$.fn.fitFont = function() {
	try {
	var that = this;
	this.css({overflow: 'auto'});
	var setFont = this.find('.setFont').andSelf();
	this.find('.center').css({top:0, left:0}); // Petit hack pour que ça ne déborde pas à cause de l'offset mis par .center().
	var size = dichotomy(parseInt(this.css("font-size"), 10), function(x) {
		setFont.css("fontSize", x);
		var ret = false;
		that.$each(function(i,e) { return !(ret = e.hasScroll()); }); // ormap
		return ret;
	});
	this.css({overflow: 'hidden'});
	this.css("font-size", Math.max(0, size));
	return this;
	} catch(e) {alert("Error $.fn.fitFont");alert(e);throw(e);}
};

$.fn.$each = function(fn) {
	this.each(function(i,e) { return fn(i, $(e)); });
};

function queueize(method) {
	try {
	return function() {
		var $this = this;
		var args = Array.prototype.slice.call(arguments); // cast Arguments → Array
		return this.queue(function(next) {
			$this[method].apply($this,args);
			next();
		});
	};
	} catch(e) {alert("Error queueize");alert(e);}
}

$.fn.qAddClass = queueize("addClass");
$.fn.qRemoveClass = queueize("removeClass");
$.fn.qRemove = queueize("remove");
$.fn.qShow = queueize("show");
$.fn.qHide = queueize("hide");
$.fn.qCss = queueize("css");

$.fn.wh = function(w, h) {
	try {
		if (isNaN(+w) && w && typeof w.width != 'undefined') { h = w.height; w = w.width; }
		if (typeof w == 'undefined') return {width: this.width(), height:this.height()};
		return this.width(w).height(isNaN(+h) ? w : h);
	} catch(e) {alert("Error $.fn.wh");alert(e);}
};

$.fn.center = function(to, justCss) {
	if (to) this.css("position", "absolute");
	var deltaX = this.outerWidth()/2;
	var deltaY = this.outerHeight()/2;

	if (to) {
		var css = {
			left: to.left - deltaX,
			top:  to.top  - deltaY
		};
		return (justCss ? css : this.offset(css));
	} else {
		var pos = this.offset();
		pos.left += deltaX;
		pos.top  += deltaY;
		return pos;
	}
};

/**
* startcolor et endcolor sont de la forme {r:0,g:255,b:127}
*/
$.fn.goodBad = function(min, max, startcolor, endcolor) {
	var val = parseInt(this.text(), 10);
	if (isNaN(val)) return this;
	this.css("color","rgb("
						  +(val.mapInterval(min,max,startcolor.r,endcolor.r).clip(0, 255, true))+","
						  +(val.mapInterval(min,max,startcolor.g,endcolor.g).clip(0, 255, true))+","
						  +(val.mapInterval(min,max,startcolor.b,endcolor.b).clip(0, 255, true))+")");
	return this;
};

$.debounce = function debounce(fn, interval) {
	var wait = false;
	var delayedFn = false;
	interval = interval || 200;
	return function() {
		var that = this;
		var args = Array.prototype.slice.call(arguments); // cast Arguments → Array
		delayedFn = function() { delayedFn = false; fn.apply(that, args); }
		if (!wait) {
			wait = true;
			delayedFn();
			var loop = function() {
				if (delayedFn) {
					delayedFn();
					window.setTimeout(loop, interval);
				} else {
					wait = false;
				}
			}
			window.setTimeout(loop, interval);
		}
	};
};

/** Zoom from small center of startElem to big center of endElem, or do the reverse animation (from big end to small start). */
$.fn.zoom = function(startElem, endElem, reverse) {
	var that = this;
	startElem = $(startElem);
	endElem = $(endElem);
	return this.queue(function(next) {
		if (that.size() == 0) return next();
		that.removeClass('transition'); // disable animations
		window.setTimeout(function() {
			// Calcul de la taille de police pour end().
			var endbox = $('<div style="text-align:center;"/>').appendTo('body').wh(endElem.wh());
			that.appendTo(endbox);
			that.css({position:'', top:'', left:''});
			endbox.fitFont();
			var BFontSize = endbox.css('fontSize');
			var APos = endbox.css('fontSize', 0).center(startElem.center()).offset();
			var BPos = endbox.css('fontSize', BFontSize).center(endElem.center()).offset();
			var A = function() { endbox.css('fontSize', 0).offset(APos); };
			var B = function() { endbox.css('fontSize', BFontSize).offset(BPos); };
			(reverse ? B : A)();
			window.setTimeout(function() {
				endbox.addClass('transition'); // enable animations
				(reverse ? A : B)();
				endbox.delay(700).qRemoveClass('transition');
				if (reverse) endbox.qRemove();
				next();
			}, 0);
		}, 0);
	});
};
	
function decodeHash(hash) {
	/* hash.match(/^#([a-z]+(\/[0-9]+(\/-?[0-9]+(,-?[0-9]+)*)?)?)?$/) */
	hash = hash.substring(1).split('/');
	return {
		screen:hash[0] || 'splash',
		pgid:hash[1] || 0,
		answers:(hash[2] ? hash[2].split(',') : [])
	};
}

function appendAnswer(data, answer) {
	return $.extend({}, data, { answers: data.answers.concat([answer]) });
}

function encodeHash(data) {
	var hash = "#";
	if (data.screen == '') return hash;
	hash += data.screen
	if (data.pgid == 0) return hash;
	hash += '/'+data.pgid;
	if (data.answers.length == 0) return hash;
	hash += '/'+data.answers.join(',');
	return hash;
}

function Cache(resolver) {
	var cache = {};
	this.get = function(k, arg) {
		return cache[k] = cache[k] || $.Deferred(function(dfd) { resolver(k, dfd, arg); }).fail(function() { cache[k] = false; }).promise();
	};
}
