/* -*- coding: utf-8 tab-width: 3 -*- */

/****************************************************************************\
* Extensions to native classes      fWMKq,           sg                      *
* (prototype.*), plus "the" $       @N  'H|          W&                      *
*                                   Mj  ;W: ,gwq.s;  M|     d7^"Yb,  ,vgbk,y *
*                                  ,#EMWV',W/` 7MP' :Q;    jML_,jQ' lW`  7P  *
*                                  ;M'    WK  ,jL.  |M\_   VK^""'` :$#omvM'  *
*                                  jM     'QMKV HK  #HKEMX? "QDnsY  ?3xc;,   *
*                                                                  G7,__.J)  *
*                                                                   '"*""'   *
\****************************************************************************/



/*****************************************************************************
 * NATIVE CLASSES (pre)
 */


/* Returns this, permanently bound to the specified arguments. Any further
 * arguments passed during calls, will be appended to those specified here.
 *
 * var ...
 *   Arguments to be bound.
 * Function return
 *   Function that will invoke this with additional arguments.
 */
Function.prototype.bind = function(o/*, ...*/) {
	var fn = this;
	if (arguments.length == 1)
		/* This is why the first argument is actually named: since that would be
		 * the this argument, the bound call can be greatly simplified to
		 * this. */
		return function(/*...*/) {
			return fn.apply(o, arguments);
		};
	var arrArgs = [];
	arrArgs.length = arguments.length - 1;
	for (var i = 1; i < arguments.length; ++i)
		arrArgs[i - 1] = arguments[i];
	return function(/*...*/) {
		if (!arguments.length)
			return fn.apply(o, arrArgs);
		var i, arr = [];
		arr.length = arrArgs.length + arguments.length;
		for (i = 0; i < arrArgs.length; ++i)
			arr[i] = arrArgs[i];
		for (var j = 0; j < arguments.length; ++i, ++j)
			arr[i] = arguments[j];
		return fn.apply(o, arr);
	};
};


/* Returns the specified non-Function, permanently bound to the specified
 * arguments. Any further arguments passed during calls, will be appended to
 * those specified here.
 * Alternate version of bind() for browsers that don't consider native methods
 * as Function's (IE, maybe others: it's not mandated as per DOM).
 *
 * NativeFunction fn
 *   Method to be invoked.
 * var ...
 *   Arguments to be bound.
 * Function return
 *   Function that will invoke fn with additional arguments.
 */
Function.bindNonFunction = function(fn, o/*, ...*/) {
	var arrArgs = [];
	arrArgs.length = arguments.length - 2;
	for (var i = 2; i < arguments.length; ++i)
		arrArgs[i - 2] = arguments[i];
	/* Must have an actual object, so get a wrapper for integral types. */
	switch (typeof(o)) {
		case "string":  o = new String(o); break;
		case "number":  o = new Number(o); break;
		case "boolean": o = new Boolean(o); break;
		default:        o = window; break;
	}
	return function(/*...*/) {
		var arr,
			mRet,
			sProp = "_pl_bNF_apply_" + Function.bindNonFunction._cProp++;
		if (arguments.length) {
			var i;
			arr = [];
			arr.length = arrArgs.length + arguments.length;
			for (i = 0; i < arrArgs.length; ++i)
				arr[i] = arrArgs[i];
			for (var j = 0; j < arguments.length; ++i, ++j)
				arr[i] = arguments[j];
		} else
			arr = arrArgs;
		o[sProp] = fn;
		switch (arr.length) {
			case 0: mRet = o[sProp](); break;
			case 1: mRet = o[sProp](arr[0]); break;
			case 2: mRet = o[sProp](arr[0], arr[1]); break;
			case 3: mRet = o[sProp](arr[0], arr[1], arr[2]); break;
			case 4: mRet = o[sProp](arr[0], arr[1], arr[2], arr[3]); break;
			case 5: mRet = o[sProp](arr[0], arr[1], arr[2], arr[3], arr[4]); break;
			default:
				var sArgs = "arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]";
				for (var i = 6; i < arr.length; ++i)
					sArgs += ", arr[" + i + "]";
				mRet = eval("o[sProp](" + sArgs + ")");
		}
		if (o.valueOf)
			delete o[sProp];
		else
			/* IE BUG: delete on a COM object's member. */
			o[sProp] = undefined;
		return mRet;
	};
};
Function.bindNonFunction._cProp = 1;


// Definisce un evento.
// Nota: in realtà non fa assolutamente nulla; serve solo a rendere nota
// l'esistenza dell'evento.
//
Function.prototype.declareEvent = function(sEventName) {
};


// Sostituisce un metodo di un oggetto. Il nuovo metodo potrà eseguire quello
// sostituito invocando arguments.callee.overriddenMethod().
//
Object.overrideMethod = function(o, sName, fn) {
	// IE bug: non considera come Function una funzione nativa, che quindi non
	// ha bind().
	fn.overriddenMethod = (o[sName].bind ? o[sName].bind(o) : Function.bindNonFunction(o[sName], o));
	o[sName] = fn;
	return o;
};



/*****************************************************************************
 * NATIVE CLASSES
 */


// Restituisce una copia superficiale dell'array.
//
Array.prototype.clone = function() {
	return [].concat(this);
};


// Crea un array associativo usando this come chiavi, e arrValues come valori.
//
Array.prototype.combine = function(arrValues) {
	var aa = {};
	for (var c = Math.min(this.length, arrValues.length), i = 0; i < c; ++i)
		aa[this[i]] = arrValues[i];
	return aa;
};


// Implementazione di Array.every() per i browser che non ce l'hanno.
//
if (!Array.prototype.every)
	Array.prototype.every = function(fnCallback, oContext /*= undefined*/) {
		for (var i = 0; i < this.length; ++i)
			if (!fnCallback.call(oContext, this[i], i, this))
				return false;
		return true;
	};
if (!Array.every)
	Array.every = function(arr, fnCallback, oContext /*= undefined*/) {
		return Array.prototype.every.call(arr, fnCallback, oContext);
	};


// Crea un array generato da mFiller.
//
Array.fill = function(c, mFiller) {
	var arr = [];
	arr.length = c;
	if (typeof(mFiller) == "function")
		for (var i = 0; i < c; ++i)
			arr[i] = mFiller(i, c);
	else
		for (var i = 0; i < c; ++i)
			arr[i] = mFiller;
	return arr;
};


// Crea un array di valori [0; 1] per animazioni, secondo la funzione
// specificata.
//
Array.fillAnimSteps = function(sAnim, c) {
	var arr = [];
	arr.length = c--;
	switch (sAnim) {
		case "linear":
			// Lineare: asse (0; 0)-(1; 1).
			var fScale = 1 / c;
			for (var i = 0; i < c; ++i)
				arr[i] = i * fScale;
			break;

		case "accelerated":
			// Accelerazione: quadratica [0; 1].
			var fScale = 1 / (c * c);
			for (var i = 0; i < c; ++i)
				arr[i] = (i * i) * fScale;
			break;

		case "braked":
			// Frenata: quadratica [0; 1] rovesciata lungo y = 0,5.
			var fScale = 1 / (c * c),
				 iU = c - 1;
			for (var i = 0; i < c; ++i)
				arr[iU - i] = 1 - (i * i) * fScale;
			break;

		case "smoothscroll":
			// Scrorrimento elastico: cubica [0; 0,5] riflessa lungo l'asse
			// (1; 0)-(0; 1).
			var fScale = 4 / (c * c * c),
				 iH = (c + 1) >> 1,
				 iU = c - 1;
			for (var i = 0; i < iH; ++i)
				arr[iU - i] = 1 - (arr[i] = (i * i * i) * fScale);
			break;
	}
	// Gli arrotondamenti non farebbero mai venire 1 all'ultimo passo; questo
	// lo evita completamente.
	arr[c] = 1;
	return arr;
};


// Implementazione di Array.filter() per i browser che non ce l'hanno.
//
if (!Array.prototype.filter)
	Array.prototype.filter = function(fnFilter, oContext /*= undefined*/) {
		var arr = [];
		for (var i = 0, j = 0; i < this.length; ++i)
			if (fnFilter.call(oContext, this[i], i, this))
				arr[j++] = this[i];
		return arr;
	};
if (!Array.filter)
	Array.filter = function(arr, fnFilter, oContext /*= undefined*/) {
		return Array.prototype.filter.call(arr, fnFilter, oContext);
	};


// Rovescia un array (associativo) scambiando le chiavi con i valori.
//
Array.prototype.flip = function() {
	var aa = {};
	for (var i = 0; i < this.length; ++i)
		aa[this[i]] = i;
	return aa;
};
Array.flip = function(aa) {
	var aaFlipped = {};
	for (var sProp in aa)
		aaFlipped[aa[sProp]] = sProp;
	return aaFlipped;
};


// Implementazione di Array.forEach() per i browser che non ce l'hanno.
//
if (!Array.prototype.forEach)
	Array.prototype.forEach = function(fnCallback, oContext /*= undefined*/) {
		for (var i = 0; i < this.length; ++i)
			fnCallback.call(oContext, this[i], i, this);
	};
if (!Array.forEach)
	Array.forEach = function(arr, fnCallback, oContext) {
		Array.prototype.forEach.call(arr, fnCallback, oContext);
	};


// Crea un array da uno pseudo-array.
//
Array.from = function(o) {
	var arr = [];
	arr.length = o.length;
	for (var i = 0; i < o.length; ++i)
		arr[i] = o[i];
	return arr;
};


// Crea un array dalle chiavi di un oggetto.
//
Array.fromKeys = function(o) {
	var arr = [],
		 i = 0;
	for (var s in o)
		arr[i++] = s;
	return arr;
};


// Implementazione di Array.indexOf() per i browser che non ce l'hanno.
//
if (!Array.prototype.indexOf)
	Array.prototype.indexOf = function(m, iStart /*= 0*/) {
		if (iStart === undefined)
			iStart = 0;
		else if (iStart < 0)
			iStart = this.length + iStart;
		for (var i = iStart; i < this.length; ++i)
			if (this[i] === m)
				return i;
		return -1;
	};
if (!Array.indexOf)
	Array.indexOf = function(arr, m, iStart /*= 0*/) {
		return Array.prototype.indexOf.call(arr, m, iStart);
	};


// Cerca in un array di oggetti in base ad una particolare proprietà.
//
Array.prototype.indexOfByProp = function(mProp, m, iStart /*= 0*/) {
	if (iStart === undefined)
		iStart = 0;
	else if (iStart < 0)
		iStart = this.length + iStart;
	for (var i = iStart; i < this.length; ++i)
		if (this[i][mProp] === m)
			return i;
	return -1;
};


// Aggiunge un elemento all'array, in una determinata posizione.
//
Array.prototype.insertAt = function(i, m) {
	this.splice(i, 0, m);
	return this;
};


// Aggiunge un elemento all'array, mantenendo l'ordine (che deve essere già
// consolidato).
//
Array.prototype.insertSorted = function(m, fnCompare /*= undefined*/) {
	if (!fnCompare)
		fnCompare = function(v1, v2) {
			return v1 <= v2 ? v1 < v2 ? -1 : 0 : 1;
		};
	var iL = 0,
		 iU = this.length,
		 i = null;
	while (iL < iU) {
		var iH = (iL + iU) >> 1;
			 iRes = fnCompare(m, this[iH]);
		if (iRes > 0)
			iL = iH + 1;
		else if (iRes < 0)
			iU = iH;
		else {
			i = iH;
			break;
		}
	}
	if (i === null)
		i = iL;
	this.insertAt(i, m);
	return i;
};


// Implementazione di Array.lastIndexOf() per i browser che non ce l'hanno.
//
if (!Array.prototype.lastIndexOf)
	Array.prototype.lastIndexOf = function(m, iStart /*= this.length - 1*/) {
		if (iStart === undefined)
			iStart = this.length - 1;
		else if (iStart < 0)
			iStart = this.length + iStart;
		for (var i = iStart; i >= 0; --i)
			if (this[i] === m)
				return i;
		return -1;
	};
if (!Array.lastIndexOf)
	Array.lastIndexOf = function(arr, m, iStart) {
		return Array.prototype.lastIndexOf.call(arr, m, iStart);
	};


// Implementazione di Array.map() per i browser che non ce l'hanno.
//
if (!Array.prototype.map)
	Array.prototype.map = function(fnMap, oContext /*= undefined*/) {
		var arr = [];
		arr.length = this.length;
		for (var i = 0; i < this.length; ++i)
			arr[i] = fnMap.call(oContext, this[i], i, this);
		return arr;
	};
if (!Array.map)
	Array.map = function(arr, fnMap, oContext /*= undefined*/) {
		return Array.prototype.map.call(arr, fnMap, oContext);
	};


// Rimuove un elemento dall'array e lo restituisce.
//
Array.prototype.remove = function(m) {
	var i = this.indexOf(m);
	if (i != -1)
		return this.removeAt(i);
	return undefined;
};


// Rimuove un elemento dall'array, in base all'indice.
//
Array.prototype.removeAt = function(i) {
	return this.splice(i, 1)[0];
};


// Cerca in un array di oggetti in base ad una particolare proprietà.
//
Array.prototype.removeByProp = function(mProp, m) {
	var i = this.indexOfByProp(mProp, m);
	return i != -1 ? this.removeAt(i) : undefined;
};


// Scompiglia l'array.
//
Array.prototype.shuffle = function() {
	var c = this.length, i, t;
	while (c) {
		i = Math.floor(Math.random() * c--);
		t = this[c];
		this[c] = this[i];
		this[i] = t;
	}
	return this;
};


// Implementazione di Array.some() per i browser che non ce l'hanno.
//
if (!Array.prototype.some)
	Array.prototype.some = function(fnCallback, oContext /*= undefined*/) {
		for (var i = 0; i < this.length; ++i)
			if (fnCallback.call(oContext, this[i], i, this))
				return true;
		return false;
	};
if (!Array.some)
	Array.some = function(arr, fnCallback, oContext /*= undefined*/) {
		return Array.prototype.some.call(arr, fnCallback, oContext);
	};


// Formatta una data/ora. Uguale a pl_format_timestamp() PHP.
//
Date.prototype.format = function(sFormat) {
	return sFormat.replace(/%[%crRxX]/g, (function($0, ich, s) {
		switch ($0) {
			case "%%": return "%%";
			case "%c": return Loc.PL_LOC_FMT_TS_DATE_S + " " + Loc.PL_LOC_FMT_TS_TIME_S;
			case "%r": return "%i" + Loc.PL_LOC_FMT_TS_TIME_S.substr(2, 1) + "%M %p";
			case "%R": return "%h" + Loc.PL_LOC_FMT_TS_TIME_S.substr(2, 1) + "%M";
			case "%x": return Loc.PL_LOC_FMT_TS_DATE_S;
			case "%X": return Loc.PL_LOC_FMT_TS_TIME_S;
		}
	}).bind(this)).replace(/%[%aAbBdefFhHiImMnpPSuwWYzZ]/g, (function($0, ich, s) {
		switch ($0) {
			case "%%": return "%";
			case "%a": var iWeekDay = this.getDay(); return Loc["PL_LOC_WEEKDAY_S" + (iWeekDay > 0 ? iWeekDay : 7)];
			case "%A": var iWeekDay = this.getDay(); return Loc["PL_LOC_WEEKDAY_L" + (iWeekDay > 0 ? iWeekDay : 7)];
			case "%b": return Loc["PL_LOC_MONTH_S" + (this.getMonth() + 1)];
			case "%B": return Loc["PL_LOC_MONTH_L" + (this.getMonth() + 1)];
			case "%d": return this.getDate().toString().pad(2, "0", -1);
			case "%e": return this.getDate();
			case "%f": return (this.getTime() % 1000).toString().pad(3, "0", -1) + "000";
			case "%F": return (this.getTime() % 1000).toString().pad(3, "0", -1);
			case "%h": return this.getHours();
			case "%H": return this.getHours().toString().pad(2, "0", -1);
			case "%i": var iHour = this.getHours() % 12; return iHour > 0 ? iHour : 12;
			case "%I": var iHour = this.getHours() % 12; return (iHour > 0 ? iHour : 12).toString().pad(2, "0", -1);
			case "%m": return (this.getMonth() + 1).toString().pad(2, "0", -1);
			case "%M": return this.getMinutes().toString().pad(2, "0", -1);
			case "%n": return this.getMonth() + 1;
			case "%p": var iHour = this.getHours(); return iHour > 0 && iHour <= 12 ? "AM" : "PM";
//			case "%P":
			case "%S": return this.getSeconds().toString().pad(2, "0", -1);
			case "%u": var iWeekDay = this.getDay(); return iWeekDay > 0 ? iWeekDay : 7;
			case "%w": var iWeekDay = this.getDay(); return iWeekDay > 6 ? 0 : iWeekDay;
//			case "%W":
			case "%Y": return this.getFullYear();
//			case "%z":
//			case "%Z":
		}
	}).bind(this));
};


// Genera una rappresentazione leggibile di una durata numerica. Simile a
// pl_format_duration() PHP.
//
Date.formatDuration = function(iTD) {
	var s = "";
	for (var sUnit in arguments.callee._arrSteps) {
		var arrStep = arguments.callee._arrSteps[sUnit],
			 i = Math.floor(iTD % arrStep[0]);
		if (i)
			s = i + " " + Loc["PL_LOC_TIMELEN_" + sUnit + (i == 1 ? "_S" : "S_S")] + " " + s;
		iTD = Math.floor(iTD / arrStep[0]);
	}
	return s.substr(0, s.length - 1);
}
Date.formatDuration._arrSteps = {
	"MS":    [   1000],
	"SEC":   [     60],
	"MIN":   [     60],
	"HOUR":  [     24],
	"DAY":   [     30],
	"MONTH": [     12],
	"YEAR":  [1000000]
};


// Costanti dei nodi HTML DOM.
//
if (!document.ELEMENT_NODE) {
	document.ELEMENT_NODE                =  1;
	document.ATTRIBUTE_NODE              =  2;
	document.TEXT_NODE                   =  3;
	document.CDATA_SECTION_NODE          =  4;
	document.ENTITY_REFERENCE_NODE       =  5;
	document.ENTITY_NODE                 =  6;
	document.PROCESSING_INSTRUCTION_NODE =  7;
	document.COMMENT_NODE                =  8;
	document.DOCUMENT_NODE               =  9;
	document.DOCUMENT_TYPE_NODE          = 10;
	document.DOCUMENT_FRAGMENT_NODE      = 11;
	document.NOTATION_NODE               = 12;
}


// Restituisce un elemento potenziato con $().
//
Object.overrideMethod(document, "createElement", function(sElemName) {
	var o;
	if (sElemName.substr(0, 6) == "input ") {
		o = arguments.callee.overriddenMethod("input");
		o.setAttribute("type", sElemName.substr(6));
	} else
		o = arguments.callee.overriddenMethod(sElemName);
	o = $(o);
	if (sElemName in $._aaElemInits)
		$._aaElemInits[sElemName].call(o);
	return o;
});


// Crea un elemento del tipo specificato con le proprietà richeste ed i nodi
// figli specificati (dal 3° parametro in poi). Al posto di un nome di
// elemento, è possibile specificarne uno già esistente, o indicare una classe
// Javascript da istanziare.
//
document.createSubtree = function(mElem, aaProps /*= {}, ...*/) {
	if (typeof(mElem) == "string")
		mElem = this.createElement(mElem);
	else if (typeof(mElem) == "function")
		mElem = new mElem;
	if (aaProps)
		for (var sProp in aaProps)
			if (sProp == "style")
				mElem.style.cssText = aaProps[sProp];
			else if (typeof(aaProps[sProp]) == "function")
				mElem.addEventListener(sProp, aaProps[sProp], false);
			else
				mElem.setAttribute(sProp, aaProps[sProp]);
	for (var i = 2; i < arguments.length; ++i) {
		var mChild = arguments[i];
		if (mChild != null)
			mElem.appendChild(mChild instanceof Array ? this.createSubtree.apply(this, mChild) : this.createTextNode(mChild));
	}
	return mElem;
};


// Definisce un evento.
//
document.declareEvent = Function.prototype.declareEvent;


// Segnala il completato caricamento del DOM della pagina e dei moduli PaLeg.
//
document.declareEvent("earlyload");

// Segnala il completato caricamento di tutti i contenuti della pagina.
//
document.declareEvent("lateload");


/* Empty (no-op) function.
 */
Function.Empty = function() {
};


/* False function.
 *
 * bool return
 *   false.
 */
Function.False = function() {
	return false;
};


// Eredita da una classe ( function() ) o da una classe virtuale ( {} ).
// TODO: un modo per creare una sottoclasse di una classe-tag, senza invocarne
// il costruttore.
//
Function.prototype.inheritFrom = function(mParent) {
	if (typeof(mParent) == "function") {
		this.prototype = new mParent;
		this.prototype.constructor = this;
		this.prototype.parentClass = mParent.prototype;
	} else {
		this.prototype = mParent;
		this.prototype.constructor = this;
		this.prototype.parentClass = mParent;
	}
	return this;
};


/* True function.
 *
 * bool return
 *   true.
 */
Function.True = function() {
	return true;
};


// Costanti per attese di sincronizzazione.
Math.MIN_TIMEOUT = 10;
// Costanti per attese dell'interfaccia.
Math.UI_SHORT_DELAY  =  400;
Math.UI_MEDIUM_DELAY = 1500;
Math.UI_LONG_DELAY   = 3000;
Math.UI_SMOOTH_RATE  =   40;


/* Calculates the distance between the specified 2D points.
 *
 * float xa
 *   Abscissa of the first point.
 * float ya
 *   Ordinate of the first point.
 * float xb
 *   Abscissa of the second point.
 * float yb
 *   Ordinate of the second point.
 * float return
 *   Distance between the points.
 */
Math.distance = function(xa, ya, xb, yb) {
	return Math.sqrt((xb - xa) * (xb - xa) + (yb - ya) * (yb - ya));
};


// Funzioni per il calcolo dell'hash MD5.
Math.md5_cmn = function(q, a, b, x, s, t) {
	var i = Math.uadd(Math.uadd(a, q), Math.uadd(x, t));
	return Math.uadd((i << s) | (i >>> (32 - s)), b);
};
Math.md5_ff = function(a, b, c, d, x, s, t) {
	return Math.md5_cmn((b & c) | (~b & d), a, b, x, s, t);
};
Math.md5_gg = function(a, b, c, d, x, s, t) {
	return Math.md5_cmn((b & d) | (c & ~d), a, b, x, s, t);
};
Math.md5_hh = function(a, b, c, d, x, s, t) {
	return Math.md5_cmn(b ^ c ^ d, a, b, x, s, t);
};
Math.md5_ii = function(a, b, c, d, x, s, t) {
	return Math.md5_cmn(c ^ (b | ~d), a, b, x, s, t);
};


// Somma due interi a 32 bit senza segno.
//
Math.uadd = function(x, y) {
	return ((x & 0x7fffffff) + (y & 0x7fffffff)) ^ (x & 0x80000000) ^ (y & 0x80000000);
};


/* Formats a number for display.
 *
 * var v
 *   Number to be formatted, or a string that will be converted to a number.
 * [int cDecs]
 *   Decimal digits to include in the returned string; defaults to 0.
 * String return
 *   String representation of the number.
 */
Number.format = function(v, cDecs /*= 0*/) {
	if (typeof(v) != "number")
		v = parseFloat(v);
	var s;
	/* Round the number to the closest position. */
	if (cDecs > 0) {
		v = parseFloat(v) + Math.pow(10, -cDecs) * 0.5;
		s = Math.floor(v).toString();
	} else {
		v = Math.round(v);
		s = v.toString();
	}
	for (var i = s.length - 3; i > 0; i -= 3)
		s = s.substr(0, i) + Loc.PL_LOC_NUM_THOUSEP + s.substr(i);
	if (cDecs > 0)
		s += Loc.PL_LOC_NUM_DECSEP + (v.toString().match(new RegExp("\\.(\\d{0," + cDecs + "})")) || {1: ""})[1].pad(cDecs, "0", 1);
	return s;
};


// Converte un numero in una dimensione in multipli di byte.
//
Number.formatByteSize = function(m, iMaxMultiple /*= undefined*/) {
	if (iMaxMultiple === undefined)
		iMaxMultiple = 10;
	if (m < 1024 || iMaxMultiple <= 0)
		return m + ' B';
	if (m < 1048576 || iMaxMultiple <= 1)
		return Number.format(m / 1024, 2) + ' KiB';
	if (m < 1073741824 || iMaxMultiple <= 2)
		return Number.format(m / 1048576, 2) + ' MiB';
	if (m < 1099511627776 || iMaxMultiple <= 3)
		return Number.format(m / 1073741824, 2) + ' GiB';
	if (m < 1125899906842624 || iMaxMultiple <= 4)
		return Number.format(m / 1099511627776, 2) + ' TiB';
	return Number.format(m / 1125899906842624, 2) + ' PiB';
};


// Formatta un importo.
//
Number.formatCy = function(m) {
	return Number.format(m, Loc.PL_LOC_CUR_DECS);
};


// Converte in notazione esadecimale un numero intero a 32 bit senza segno.
//
Number.uhex32 = function(i) {
	var s = "",
		 sHex = "0123456789abcdef";
	for(var j = 0; j <= 3; j++)
		s += sHex.charAt((i >> ((j << 3) + 4)) & 0x0f) +
			  sHex.charAt((i >>  (j << 3)     ) & 0x0f);
	return s;
};


// Simile a Array.concat().
//
Object.merge = function(aaDst, aaSrc) {
	for (var sKey in aaSrc)
		aaDst[sKey] = aaSrc[sKey];
	return aaDst;
};


// Converte una stringa in un frammento di RegExp, senza interpretare gli
// escape PCRE.
//
RegExp.escape = function(s) {
	return s.replace(/([$()*+.\/?[\\\]^{|}])/g, "\\$1");
};


// Converte una stringa in un frammento di RegExp, permettendo solo asterischi
// e punti interrogativi, convertiti negli equivalenti PCRE.
//
RegExp.escapeLike = function(s) {
	return s.replace(/([$()+.\/[\\\]^{|}])/g, "\\$1").replacePairs({
		"?": ".",
		"*": ".*"
	});
};


// Restituisce true se l'espressione regolare è poco più che uno strcmp().
//
RegExp.prototype.isSimple = function() {
	if (this.ignoreCase)
		return false;
	var s = this.source.replace(/\\\\/g, "").replace(/\\[$()*+.\/?[\]^{|}]/g, "");
	return /^\^?[^$()*+.\/?[\\\]^{|}]+\$?$/.test(s);
};


/* Converts a RegExp fragment into a String, interpreting any PCRE escaped
 * characters. The result can only make sense if the RegExp was "simple"
 * (see RegExp.isSimple).
 *
 * String return
 *   String with escape sequences
 */
RegExp.unescape = function(s) {
	return s.replace(/\\(.)/g, "$1");
};



/* Returns a clone of the string.
 *
 * String return
 *   Clone string.
 */
String.prototype.clone = function() {
	return "".concat(this);
};


/* Performs a locale-dependant, case-insensitive string comparison.
 *
 * String s2
 *   Right side of the comparison.
 * int return
 *   Comparison result.
 */
String.prototype.localeCompareNoCase = function(s2) {
	return this.toUpperCase().localeCompare(s2.toUpperCase());
}


/* Uses the string as a format specifier for the provided arguments. Similar to
 * a very simplified sprintf(),
 *
 * var ...
 *   Arguments to format.
 * String return
 *   Resulting string.
 */
String.prototype.asFormat = function(/*...*/) {
	var arrArgs = arguments,
		 cUsedArgs = 0;
	return this.replace(/%(?:%|(?:(\d+)\$)?([difs]))/g, function($0, $1, $2, ich, s) {
		if ($0 == "%%")
			return "%";
		var mArg;
		if ($1)
			mArg = arrArgs[$1 - 1];
		else
			mArg = arrArgs[cUsedArgs++];
		switch ($2) {
			case "d":
			case "i":
				return parseInt(mArg);
			case "f":
				return parseFloat(mArg);
			case "s":
				return mArg;
		}
	});
};


/* Truncates the string at the required length, adding ellipsis.
 *
 * String return
 *   Truncated string.
 */
String.prototype.ellipsis = function(cch) {
	return this.length > cch ? this.substr(0, cch - 1) + "…" : this;
} 


/* Decodes a JSON string into a variabile.
 *
 * var return
 *   Rendered value.
 */
String.prototype.jsonDecode = function() {
	if (this == "")
		return undefined;
	try {
		return eval("(" + this + ")");
	} catch (x) {
		PaLeg.logException(x, "String.jsonDecode", {
			"this": this
		});
		throw x;
	}
};


/* Returns a JSON representation of the argument.
 *
 * String return
 *   JSON representation.
 */
String.jsonEncode = function(o, oParserStatus /*= null*/) {
	/* NOT 100% COMPATIBLE! Only use to diagnose special-case doubts. */
//	if (o.toSource) return o.toSource();
	if (o === undefined)
		return "undefined";
	else if (o === null)
		return "null";
	else switch (o.constructor) {
		case Number:
			if (!isFinite(o))
				return "null";
		case Boolean:
			return o.toString();

		case String:
			o = o.replacePairs({
				"\\"  : "\\\\",
				"\""  : "\\\"",
				"\x08": "\\b",
				"\x09": "\\t",
				"\x0A": "\\n",
				"\x0B": "\\v",
				"\x0C": "\\f",
				"\x0D": "\\r"
			});
			o = o.replace("/[\x00-\x1F]/g", function($0, ich, s) {
				return "\\u" + $0.charCodeAt().toString(16).toUpperCase().pad(4, "0", -1);
			});
			return "\"" + o + "\"";

		case Date:
			return "new Date(" + o.getTime() + ")";

		case RegExp:
			o = o.toString().match(/^\/(.*)\/([a-z]*)$/);
			return "new RegExp(" + String.jsonEncode(o[1]) + (o[2] ? ",\"" + o[2] + "\"" : "") + ")";

		default:
			if (typeof(o) == "function")
				return o.toString().match(/^[^{]*/)[0].rTrim() + "{/*code*/}";

			var sOpen, sClose;
			if (o instanceof Array) {
				sOpen  = "[";
				sClose = "]";
			} else {
				sOpen  = "{";
				sClose = "}";
			}
			if (oParserStatus) {
				if (oParserStatus.arrParsedObjs.indexOf(o) != -1)
					return sOpen + "_jsonErr: \"recursion detected\"" + sClose;
				oParserStatus.arrParsedObjs.push(o);
				++oParserStatus.cLevels;
			} else
				oParserStatus = {
					cLevels:       0,
					arrParsedObjs: [o]
				};
			var arr = [],
				 i;
			if (o instanceof Array) {
				arr.length = o.length;
				for (i = 0; i < o.length; ++i)
					arr[i] = String.jsonEncode(o[i], oParserStatus);
			} else
				try {
					i = 0;
					for (var sKey in o)
						if (o[sKey] !== undefined && sKey != "constructor") {
							var m = o[sKey];
							if (m === null)
								m = "null";
							else if (m.nodeType != undefined)
								/* Markup elements have far too many attributes. */
								m = "{ _jsonErr: \"XML node\" }";
							else
								m = String.jsonEncode(m, oParserStatus);
							arr[i++] = String.jsonEncode(sKey) + ":" + m;
						}
				} catch (x) {
					arr = ["_jsonErr: \"native object\""];
				}
			--oParserStatus.cLevels;
			return sOpen + arr.join(",") + sClose;
	}
};


/* Removes whitespace from the beginning (left) of the string.
 *
 * String return
 *   Left-trimmed string.
 */
String.prototype.lTrim = function() {
	return this.replace(/^\s+/, "");
};


/* Computes the string's MD5 hash.
 *
 * String return
 *   MD5 hash.
 */
String.prototype.md5 = function() {
	var i,
		 x = Array.fill((((this.length + 8) >> 6) + 1) << 4, 0),
		 a = 0x67452301,
		 b = 0xEFCDAB89,
		 c = 0x98BADCFE,
		 d = 0x10325476;
	for (i = 0; i < this.length; ++i)
		x[i >> 2] |= this.charCodeAt(i) << ((i & 3) << 3);
	x[i >> 2] |= 0x80 << ((i & 3) << 3);
	x[x.length - 2] = this.length << 3;
	for (i = 0; i < x.length; i += 16) {
		var olda = a,
			 oldb = b,
			 oldc = c,
			 oldd = d;

		a = Math.md5_ff(a, b, c, d, x[i +  0],  7, 0xD76AA478);
		d = Math.md5_ff(d, a, b, c, x[i +  1], 12, 0xE8C7B756);
		c = Math.md5_ff(c, d, a, b, x[i +  2], 17, 0x242070DB);
		b = Math.md5_ff(b, c, d, a, x[i +  3], 22, 0xC1BDCEEE);
		a = Math.md5_ff(a, b, c, d, x[i +  4],  7, 0xF57C0FAF);
		d = Math.md5_ff(d, a, b, c, x[i +  5], 12, 0x4787C62A);
		c = Math.md5_ff(c, d, a, b, x[i +  6], 17, 0xA8304613);
		b = Math.md5_ff(b, c, d, a, x[i +  7], 22, 0xFD469501);
		a = Math.md5_ff(a, b, c, d, x[i +  8],  7, 0x698098D8);
		d = Math.md5_ff(d, a, b, c, x[i +  9], 12, 0x8B44F7AF);
		c = Math.md5_ff(c, d, a, b, x[i + 10], 17, 0xFFFF5BB1);
		b = Math.md5_ff(b, c, d, a, x[i + 11], 22, 0x895CD7BE);
		a = Math.md5_ff(a, b, c, d, x[i + 12],  7, 0x6B901122);
		d = Math.md5_ff(d, a, b, c, x[i + 13], 12, 0xFD987193);
		c = Math.md5_ff(c, d, a, b, x[i + 14], 17, 0xA679438E);
		b = Math.md5_ff(b, c, d, a, x[i + 15], 22, 0x49B40821);

		a = Math.md5_gg(a, b, c, d, x[i +  1],  5, 0xF61E2562);
		d = Math.md5_gg(d, a, b, c, x[i +  6],  9, 0xC040B340);
		c = Math.md5_gg(c, d, a, b, x[i + 11], 14, 0x265E5A51);
		b = Math.md5_gg(b, c, d, a, x[i +  0], 20, 0xE9B6C7AA);
		a = Math.md5_gg(a, b, c, d, x[i +  5],  5, 0xD62F105D);
		d = Math.md5_gg(d, a, b, c, x[i + 10],  9, 0x02441453);
		c = Math.md5_gg(c, d, a, b, x[i + 15], 14, 0xD8A1E681);
		b = Math.md5_gg(b, c, d, a, x[i +  4], 20, 0xE7D3FBC8);
		a = Math.md5_gg(a, b, c, d, x[i +  9],  5, 0x21E1CDE6);
		d = Math.md5_gg(d, a, b, c, x[i + 14],  9, 0xC33707D6);
		c = Math.md5_gg(c, d, a, b, x[i +  3], 14, 0xF4D50D87);
		b = Math.md5_gg(b, c, d, a, x[i +  8], 20, 0x455A14ED);
		a = Math.md5_gg(a, b, c, d, x[i + 13],  5, 0xA9E3E905);
		d = Math.md5_gg(d, a, b, c, x[i +  2],  9, 0xFCEFA3F8);
		c = Math.md5_gg(c, d, a, b, x[i +  7], 14, 0x676F02D9);
		b = Math.md5_gg(b, c, d, a, x[i + 12], 20, 0x8D2A4C8A);

		a = Math.md5_hh(a, b, c, d, x[i +  5],  4, 0xFFFA3942);
		d = Math.md5_hh(d, a, b, c, x[i +  8], 11, 0x8771F681);
		c = Math.md5_hh(c, d, a, b, x[i + 11], 16, 0x6D9D6122);
		b = Math.md5_hh(b, c, d, a, x[i + 14], 23, 0xFDE5380C);
		a = Math.md5_hh(a, b, c, d, x[i +  1],  4, 0xA4BEEA44);
		d = Math.md5_hh(d, a, b, c, x[i +  4], 11, 0x4BDECFA9);
		c = Math.md5_hh(c, d, a, b, x[i +  7], 16, 0xF6BB4B60);
		b = Math.md5_hh(b, c, d, a, x[i + 10], 23, 0xBEBFBC70);
		a = Math.md5_hh(a, b, c, d, x[i + 13],  4, 0x289B7EC6);
		d = Math.md5_hh(d, a, b, c, x[i +  0], 11, 0xEAA127FA);
		c = Math.md5_hh(c, d, a, b, x[i +  3], 16, 0xD4EF3085);
		b = Math.md5_hh(b, c, d, a, x[i +  6], 23, 0x04881D05);
		a = Math.md5_hh(a, b, c, d, x[i +  9],  4, 0xD9D4D039);
		d = Math.md5_hh(d, a, b, c, x[i + 12], 11, 0xE6DB99E5);
		c = Math.md5_hh(c, d, a, b, x[i + 15], 16, 0x1FA27CF8);
		b = Math.md5_hh(b, c, d, a, x[i +  2], 23, 0xC4AC5665);

		a = Math.md5_ii(a, b, c, d, x[i +  0],  6, 0xF4292244);
		d = Math.md5_ii(d, a, b, c, x[i +  7], 10, 0x432AFF97);
		c = Math.md5_ii(c, d, a, b, x[i + 14], 15, 0xAB9423A7);
		b = Math.md5_ii(b, c, d, a, x[i +  5], 21, 0xFC93A039);
		a = Math.md5_ii(a, b, c, d, x[i + 12],  6, 0x655B59C3);
		d = Math.md5_ii(d, a, b, c, x[i +  3], 10, 0x8F0CCC92);
		c = Math.md5_ii(c, d, a, b, x[i + 10], 15, 0xFFEFF47D);
		b = Math.md5_ii(b, c, d, a, x[i +  1], 21, 0x85845DD1);
		a = Math.md5_ii(a, b, c, d, x[i +  8],  6, 0x6FA87E4F);
		d = Math.md5_ii(d, a, b, c, x[i + 15], 10, 0xFE2CE6E0);
		c = Math.md5_ii(c, d, a, b, x[i +  6], 15, 0xA3014314);
		b = Math.md5_ii(b, c, d, a, x[i + 13], 21, 0x4E0811A1);
		a = Math.md5_ii(a, b, c, d, x[i +  4],  6, 0xF7537E82);
		d = Math.md5_ii(d, a, b, c, x[i + 11], 10, 0xBD3AF235);
		c = Math.md5_ii(c, d, a, b, x[i +  2], 15, 0x2AD7D2BB);
		b = Math.md5_ii(b, c, d, a, x[i +  9], 21, 0xEB86D391);

		a = Math.uadd(a, olda);
		b = Math.uadd(b, oldb);
		c = Math.uadd(c, oldc);
		d = Math.uadd(d, oldd);
	}
	return Number.uhex32(a) + Number.uhex32(b) + Number.uhex32(c) + Number.uhex32(d);
};


// Effettua un confronto fra stringhe con ordinamento naturale (numerico)
// binario.
//
String.prototype.natCompare = function(s2, bNoCase /*= false*/) {
	var s1 = this;
	for (var ich1 = 0, ich2 = 0; ; ++ich1, ++ich2) {
		var ch1 = s1.charCodeAt(ich1),
			 ch2 = s2.charCodeAt(ich2);
		// Comprime eventuali spazi.
		while (ch1 <= 0x20)
			ch1 = s1.charCodeAt(++ich1);
		while (ch2 <= 0x20)
			ch2 = s2.charCodeAt(++ich2);
		// Analizza una coppia di numeri.
		if ((ch1 >= 0x30 && ch1 <= 0x39) && (ch2 >= 0x30 && ch2 <= 0x39)) {
			var iRes = arguments.callee[ch1 == 0x30 || ch2 == 0x30 ? "_cmpL" : "_cmpR"](s1, ich1, s2, ich2);
			if (iRes != 0)
				return iRes;
		}
		if (!ch1 || !ch2)
			// Una delle due stringhe è finita; esegue il passo finale di un
			// confronto binario.
			return (ch1 || 0) - (ch2 || 0);
		if (bNoCase) {
			ch1 = s1.charAt(ich1).toUpperCase();
			ch2 = s2.charAt(ich2).toUpperCase();
		}
		if (ch1 < ch2)
			return -1;
		if (ch1 > ch2)
			return +1;
	}
}


// Analizza due numeri ipoteticamente allineati a sinistra; finisce alla prima
// cifra diversa.
//
String.prototype.natCompare._cmpL = function(s1, ich1, s2, ich2) {
	for (;; ++ich1, ++ich2) {
		var ch1 = s1.charCodeAt(ich1),
			 ch2 = s2.charCodeAt(ich2);
		// Occhio: scritto in questo modo, tratta il null come 0.
		var bNonDigit1 = !(ch1 >= 0x30 && ch1 <= 0x39),
			 bNonDigit2 = !(ch2 >= 0x30 && ch2 <= 0x39);
		if (bNonDigit1 && bNonDigit2)
			return 0;
		if (bNonDigit1)
			return -1;
		if (bNonDigit2)
			return +1;
		if (ch1 < ch2)
			return -1;
		if (ch1 > ch2)
			return +1;
	}
	return 0;
};


// Analizza due numeri ipoteticamente allineati a destra; finisce solo dopo
// aver analizzato tutti e due i numeri, e vince il più lungo o il valore
// maggiore.
//
String.prototype.natCompare._cmpR = function(s1, ich1, s2, ich2) {
	// Risultato del confronto fra l'ordine di grandezza dei due numeri.
	var iBias = 0,
		 chThouSep = Loc.PL_LOC_NUM_THOUSEP.charCodeAt(0);
	for (;; ++ich1, ++ich2) {
		var ch1 = s1.charCodeAt(ich1),
			 ch2 = s2.charCodeAt(ich2);
		// Salta eventuali separatori di migliaia.
		if (ch1 == chThouSep)
			ch1 = s1.charCodeAt(++ich1);
		if (ch2 == chThouSep)
			ch2 = s2.charCodeAt(++ich2);
		// Occhio: scritto in questo modo, tratta il null come 0.
		var bNonDigit1 = !(ch1 >= 0x30 && ch1 <= 0x39),
			 bNonDigit2 = !(ch2 >= 0x30 && ch2 <= 0x39);
		if (bNonDigit1 && bNonDigit2)
			return iBias;
		if (bNonDigit1)
			return -1;
		if (bNonDigit2)
			return +1;
		if (ch1 < ch2) {
			if (!iBias)
				iBias = -1;
		} else if (ch1 > ch2) {
			if (!iBias)
				iBias = +1;
		}
		if (!ch1 && !ch2)
			return iBias;
	}
	return 0;
};


// Effettua un confronto fra stringhe con ordinamento naturale (numerico).
//
String.prototype.natCompareNoCase = function(s2) {
	return this.natCompare(s2, true);
}


// Rende la stringa lunga almeno cchPad caratteri, con riempimento sPadder
// nella direzione iDir.
//
String.prototype.pad = function(cchPad, sPadder, iDir) {
	var s;
	if (this.length >= cchPad)
		s = this.clone();
	else if (iDir == 0)
		s = this.pad(this.length + ((cchPad - this.length) >> 1), sPadder, -1).pad(cchPad, sPadder, 1);
	else {
		var sPad = (sPadder.length ? sPadder.repeat(Math.ceil((cchPad - this.length) / sPadder.length)) : "");
		if (iDir < 0)
			s = sPad.substr(sPad.length - (cchPad - this.length)) + this;
		else
			s = this + sPad.substr(0, cchPad - this.length);
	}
	return s;
};


// Restituisce la stringa ripetuta iMult volte.
//
String.prototype.repeat = function(iMult) {
	var s = "";
	while (iMult-- > 0)
		s += this;
	return s;
};


// Restituisce la stringa invertita.
//
String.prototype.reverse = function(iMult) {
	var s = "",
		 i = this.length;
	while (i--)
		s += this.charAt(i);
	return s;
};


// Sostituisce in this le chiavi di aa con i valori di aa.
//
String.prototype.replacePairs = function(aa) {
	var s = this.clone();
	for (var sSearch in aa)
		// Una RegExp non si può usare come chiave di un array associativo,
		// quindi al suo posto c'è i: [reSearch, sReplace].
		if (aa[sSearch] instanceof Array)
			s = s.replace(aa[sSearch][0], aa[sSearch][1]);
		else
			s = s.replace(new RegExp(RegExp.escape(sSearch), "g"), aa[sSearch]);
	return s;
};


// Rimuove gli spazi dalla fine.
//
String.prototype.rTrim = function() {
	return this.replace(/\s+$/, "");
};


// Conta le occorrenze della sottostringa specificata.
//
String.prototype.substrCount = function(s) {
	return (this.match(s instanceof RegExp ? s : new RegExp(RegExp.escape(s), "g")) || []).length;
};


// Converte "una-proprietà" in "unaProprietà".
//
String.prototype.toCamelCase = function() {
	return this.replace(/-[a-z]/g, function($0, ich, s) {
		return $0.charAt(1).toUpperCase();
	});
};


// Rimuove gli spazi dall'inizio e dalla fine.
//
String.prototype.trim = function() {
	return this.lTrim().rTrim();
};


// Aggiunge uno o più parametri ad un URL. Uguale a pl_url_addqs() PHP.
//
String.prototype.urlAddQA = function(s) {
	return this + (this.indexOf("?") != -1 ? "&" : "?") + s;
};


// Restituisce una stringa composta dai parametri di query string codificati.
//
String.urlEncodeQS = function(aaArgs) {
	var s = "";
	for (var sName in aaArgs)
		s += encodeURIComponent(sName) + "=" + encodeURIComponent(aaArgs[sName]) + "&";
	return s == "" ? "" : s.substr(0, s.length - 1);
};


// Restituisce una stringa composta dalle componenti di URL nell'oggetto
// specificato.
//
String.urlJoin = function(aaUrl) {
	var s = "";
	if (aaUrl.scheme)
		s += aaUrl.scheme + "://";
	if (aaUrl.user) {
		s += aaUrl.user;
		if (aaUrl.pass)
			s += ":" + aaUrl.pass;
		s += "@";
	}
	if (aaUrl.host) {
		s += aaUrl.host
		if (aaUrl.port)
			s += ":" + aaUrl.port;
	}
	if (aaUrl.path) {
		s += aaUrl.path;
		if (aaUrl.query)
			if (typeof(aaUrl.query) == "object") {
				s += "?";
				for (var sName in aaUrl.query)
					s += sName + "=" + aaUrl.query[sName] + "&";
				s = s.substr(0, s.length - 1);
			} else
				s += "?" + aaUrl.query;
		if (aaUrl.fragment)
			s += "#" + aaUrl.fragment;
	}
	return s;
};


// Sostituisce un parametro in una query string.
//
String.prototype.urlReplaceQA = function(sArg, sNewVal) {
	var sQA, sNew;
	if (sNewVal == undefined) {
		sQA = sArg;
		sArg = sArg.match(/^[^=]+/)[0];
	} else {
		sArg = encodeURIComponent(sArg);
		sQA = sArg + "=" + encodeURIComponent(sNewVal);
	}
	var sNew = this.replace(new RegExp("([?&])" + RegExp.escape(sArg) + "=.*?(&|$)"), "$1" + sQA + "$2");
	return sNew != this ? sNew : this.urlAddQA(sQA);
};


// Codifica alcuni caratteri di un indirizzo fittizio perché sia un URL valido.
// Uguale a pl_url_safe() PHP.
//
String.prototype.urlSafe = function() {
	return this.replace(/[ "#%&<>?_]+/g, "_");
};


// Restituisce in un oggetto le componenti dell'URL. Molto simile a parse_url()
// PHP.
//
String.prototype.urlSplit = function(bSplitQA) {
	var o = {},
		 s = this.clone(),
		 arr;
	if (arr = s.match(/^([a-z]+):\/\//)) {
		o.scheme = arr[1];
		s = s.substr(arr[0].length);
	}
	if (arr = s.match(/^(.+?)(?::(.*?))?@/)) {
		o.user = arr[1];
		o.pass = (arr[2] || "");
		s = s.substr(arr[0].length);
	}
	if (arr = s.match(/^((?:[0-9a-z](?:[0-9a-z_\-]*[0-9a-z])?\.)+[a-z]{2,6}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?::(\d+))?/)) {
		o.host = arr[1];
		if (arr[2])
			o.port = parseInt(arr[2]);
		s = s.substr(arr[0].length);
	}
	if (arr = s.match(/^(.+?)(?:[?#]|$)/)) {
		o.path = arr[1];
		s = s.substr(arr[0].length);

		if (arr = s.match(/^(.+?)(?:\#|$)/)) {
			o.query = (bSplitQA ? arr[1].urlSplitQA() : arr[1]);
			s = s.substr(arr[0].length);
		}
		if (s != "")
			o.fragment = s;
	}
	return o;
};


// Restituisce in un oggetto i parametri da query string. Molto simile a
// parse_str() PHP.
//
String.prototype.urlSplitQA = function() {
	var o = {},
		 arr = this.match(/\?(.*)(?:#.*)?$/);
	if (arr)
		arr[1].split("&").forEach(function(sQA) {
			var i = sQA.indexOf("=");
			if (i != -1)
				o[sQA.substr(0, i)] = sQA.substr(i + 1);
			else
				o[sQA] = true;
		});
	return o;
};


// Rimuove il percorso del file di un URL. Uguale a basename() PHP.
//
String.prototype.urlStripPath = function() {
	return (this.match(/(?:^|[\/\\])([^\/\\]+)[\/\\]?$/) || {1:this})[1];
};



/*****************************************************************************
 * CLASSES
 */


/******
 * $()
 *
 * Returns a DOM node, with extra attributes.
 *
 * var v
 *   If String, it specifies an element ID; otherwise, it's directly the
 *   element to be enhanced.
 * Element return
 *   Enhanced Element object.
 */
var $ = function(v) {
	if (typeof(v) == "string")
		v = document.getElementById(v);
	/* Avoid FX error message for browser-owned (chrome) elements:
	 * Permission denied to access property 'pl_$' from a non-chrome context */
	try {
		bInit = !v.pl_$;
	} catch (x) {
		/* Couldn't read one property, modifying is out of question. */
		bInit = false;
	}
	if (v && bInit) {
		$eN(v);
		Object.merge(v, $._aaMethods);
		var sElemName = v.elemName;
		if (sElemName == "input")
			sElemName += " " + v.getAttribute("type");
		if (sElemName in $._aaElemMethods)
			Object.merge(v, $._aaElemMethods[sElemName]);
	}
	return v;
};


/* Methods common to every node element.
 */
$._aaMethods = {

	pl_$: true,


	/* Applies a CSS class to the element.
	 *
	 * String s
	 *   CSS class name.
	 */
	addCssClass: function(s) {
		var sClass;
		if (this.hasAttribute("class")) {
			sClass = this.getAttribute("class");
			if ((" " + sClass + " ").indexOf(" " + s + " ") == -1)
				sClass += " " + s;
		}
		else
			sClass = s;
		this.setAttribute("class", sClass);
		return sClass;
	},


	// Aggiunge un array di elementi, un elemento DOM singolo, o qualsiasi cosa
	// sia convertibile in String.
	//
	appendChildM: function(mChild) {
		if (mChild.nodeType !== undefined)
			// Nodo DOM già pronto.
			this.appendChild(mChild);
		else if (mChild.length !== undefined && mChild.constructor != String)
			// È un array o simile: sposta tutti gli elementi.
			for (var i = 0; i < mChild.length; ++i)
				this.appendChild(mChild[i]);
		else
			// Non è un nodo DOM né un array o simile: testo semplice.
			this.appendChild(document.createTextNode(mChild.toString()));
	},


	// Copia da o gli stili specificati.
	//
	copyStylesFrom: function(o, bForce, arrStyles) {
		var oSrcStyle = (bForce && document.defaultView && document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(o, null) : o.style),
			 oDstStyle = this.style;
		for (var i = 0; i < arrStyles.length; ++i) {
			var sName = arrStyles[i],
				 sVal = oSrcStyle.getPropertyValue(sName);
			if (bForce || sVal)
				oDstStyle.setProperty(sName, sVal, "");
		}
		return this;
	},


	// Restituisce il primo nodo avo del tipo specificato, opzionalmente
	// filtrando per l'attributo specificato.
	//
	getAncestor: function(sElemName, sAttr /*= null*/, mFilter /*= null*/) {
		var o = this.parentNode;
		while (o && $eN(o).elemName != sElemName)
			o = o.parentNode;
		return $(o);

		if (sAttr) {
			if (sAttr == "class")
				if (mFilter instanceof RegExp) {
					mFilter = mFilter.toString().match(/^\/(.*)\/([a-z]*)$/);
					mFilter = new RegExp("(^|\\s)" + mFilter[1] + "(\\s|$)", mFilter[2]);
				}
				else
					mFilter = new RegExp("(^|\\s)" + mFilter + "(\\s|$)");

			// Converte mFilter in una funzione.
			mFilter = (function(mFilter) {
				return mFilter instanceof RegExp
					? function(o) {
						return $(o).hasAttribute(sAttr) && mFilter.test(o.getAttribute(sAttr));
					}
					: function(o) {
						return $(o).hasAttribute(sAttr) && o.getAttribute(sAttr) == mFilter;
					};
			})(mFilter);
		} else
			// $() restituisce sempre non-null per non-null, quindi
			// Array.filter() convertirà la HTMLCollection in array.
			mFilter = $;

		var bAny = (sElemName == "*");
		var o = this.parentNode;
		while (o && ((!bAny && $eN(o).elemName != sElemName) || !mFilter(o)))
			o = o.parentNode;
		return o;

	},


	// Restituisce tutta la catena di nodi antenati di this, dal più vicino al
	// più remoto.
	//
	getAncestors: function() {
		var arr = [];
		for (var i = 0, o = this.parentNode; o; o = o.parentNode)
			arr[i++] = $(o);
		return arr;
	},


	// Restituisce i nodi diretti discendenti, opzionalmente filtrandoli per
	// l'attributo specificato.
	//
	getChildren: function(sElemName, sAttr /*= null*/, mFilter /*= null*/) {
		return PaLeg._getEltsBy(this, sElemName, sAttr, mFilter, true);
	},


	// Restituisce lo stile CSS istantaneo.
	//
	getCurrentStyle: function(sName) {
		return (document.defaultView && document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(this, null) : this.style).getPropertyValue(sName);
	},


	// Restituisce i nodi discententi, opzionamente filtrandoli per l'attributo
	// specificato.
	//
	getDescendants: function(sElemName, sAttr /*= null*/, mFilter /*= null*/) {
		return PaLeg._getEltsBy(this, sElemName, sAttr, mFilter, false);
	},


	// Restituisce le coordinate di un elemento, più un terzo valore che è true
	// se l'oggetto o uno dei suoi contenitori ha position: fixed. Usare non
	// prima di window.onload().
	//
	getPosition: function(oRelTo /*= null*/) {
		var x = this.offsetLeft,
			 y = this.offsetTop,
			 o = this,
			 bFixed = this.isCssClass("fixedpos");
		if (oRelTo)
			while ($(o = o.offsetParent) && o.isAncestor(oRelTo)) {
				x += o.offsetLeft;
				y += o.offsetTop;
				if (o.isCssClass("fixedpos"))
					bFixed = true;
			}
		return [x, y, bFixed];
	},


	// Restituisce lo stile CSS specificato. Normalmente è solo una
	// scorciatoia, ma diventa indispensabile nella versione per IE.
	//
	getStyle: function(sName) {
		return this.style.getPropertyValue(sName);
	},


	// Simile alla proprietà textContent DOM3, ma più completo: analizza i nodi
	// di testo e i tag di input, anche non elementari.
	//
	getTextContent: function(bIncludeGraphical /*= false*/) {
		var s = "";
		for (var o = this.firstChild; o; o = o.nextSibling)
			if (o.nodeType == document.TEXT_NODE)
				s += o.nodeValue;
			else if (o.nodeType == document.ELEMENT_NODE) {
				// Supporta tag non elementari.
				var input = (["input", "textarea", "select"].indexOf($(o).elemName) == -1 && o.getValue ? o.getValue() : o);
				switch (input.elemName) {
					case "input":
						switch (input.getAttribute("type")) {
							case "hidden":
								break;
							case "checkbox":
							case "radio":
								if (bIncludeGraphical)
									s += (input.checked ? "[X]" : "[ ]");
								break;
							default:
								s += input.value;
								break;
						}
						break;
					case "textarea":
					case "select":
						s += input.value;
						break;
					case "br":
						s += " ";
						break;
					default:
						if (o.hasChildNodes)
							s += o.getTextContent(bIncludeGraphical);
						break;
				}
			}
		return s;
	},


	// Restituisce i nodi di testo contenuti, con filtro opzionale.
	// Complementare a getDescendants().
	//
	getTextNodes: function(reMatch /*= null*/) {
		var arrTextNodes = [];
		for (var i = 0, o = this.firstChild; o; o = o.nextSibling)
			if (o.nodeType == document.TEXT_NODE) {
				if (!reMatch || reMatch.test(o.nodeValue))
					arrTextNodes[i++] = o;
			} else if (o.hasChildNodes()) {
				arrTextNodes = arrTextNodes.concat($(o).getTextNodes(reMatch));
				i = arrTextNodes.length;
			}
		return arrTextNodes;
	},


	// Restituisce true se this è un nodo discendente di o.
	//
	isAncestor: function(o) {
		for (var oChild = this; oChild; oChild = oChild.parentNode)
			if (oChild.parentNode == o)
				return true;
		return false;
	},


	/* Returns true if this.class includes the specified identifier.
	 *
	 * var v
	 *   String or RegExp containing the CSS class to be checked for. Capturing
	 *   subpatterns may be specified.
	 * var return
	 *   If the specified CSS class is not applied to the element, the return
	 *   value is false; otherwise, if v was a RegExp the match array will be
	 *   returned, or true if v was a string.
	 */
	isCssClass: function(v) {
		if (!this.hasAttribute("class"))
			return false;
		if (v instanceof RegExp) {
			v = new RegExp(" " + v.source + " ", "i");
			return (" " + this.getAttribute("class") + " ").match(v) || false;
		} else
			return (" " + this.getAttribute("class") + " ").indexOf(" " + v + " ") != -1;
	},


	// Rimuove una classe CSS da this.
	//
	removeCssClass: function(s) {
		if (!this.hasAttribute("class"))
			return "";
		var sClass = (" " + this.getAttribute("class") + " ").replace(new RegExp(" " + s + " ", "g"), " ").trim();
		this.setAttribute("class", sClass);
		return sClass;
	},


	// Rimuove lo stile CSS specificato. Normalmente è solo una scorciatoia, ma
	// diventa indispensabile nella versione per IE.
	//
	removeStyle: function(sName) {
		return this.style.removeProperty(sName);
	},


	// Assegna lo stile CSS specificato. Normalmente è solo una scorciatoia, ma
	// diventa indispensabile nella versione per IE.
	//
	setStyle: function(sName, sValue, sPriority /*= ""*/) {
		this.style.setProperty(sName, sValue, sPriority || "");
	}
};


// Inizializzatori differenziati in base al tipo di elemento.
//
$._aaElemInits = {
};


// Metodi differenziati in base al tipo di elemento.
//
$._aaElemMethods = {

	"button": {

		// Disabilita, anche visivamente, il controllo.
		//
		disable: function(bDisable) {
			this.removeCssClass(bDisable ? "enabled" : "disabled");
			this.addCssClass(bDisable ? "disabled" : "enabled");
			this.disabled = (bDisable || false);
		}
	},


	"input checkbox": {

		// Disabilita, anche visivamente, il controllo.
		//
		disable: function(bDisable) {
			this.removeCssClass(bDisable ? "chk" : "disabled_chk");
			this.addCssClass(bDisable ? "disabled_chk" : "chk");
			this.disabled = (bDisable || false);
		},


		// Rende il tag "autoapplicante", cioè che a ogni modifica invia
		// immediatamente il nuovo valore al server con una richiesta
		// asincrona.
		// Se bInstall è true, la logica di funzionamento è installata
		// definitivamente; altrimenti lo è solo per una volta.
		//
		enableAutoSubmit: function(bInstall, sUrl, aaParams, onComplete /*= Function.Empty*/, aaOptions /*= {}*/) {
			var onChange = function(e) {
				this.removeEventListener("click", onChange, false);
				aaParams["newvalue"] = (this.checked ? 1 : 0);
				this.disable(true);
				PaLeg.asyncExec(
					sUrl,
					aaParams,
					(function(bSuccess, oResult) {
						this.disable(false);
						if (bSuccess || oResult["error"])
							this.checked = (oResult["newvalue"] != 0);
						else if (!bSuccess)
							this.checked = this.previousChecked;
						if (onComplete)
							onComplete(bSuccess, this.checked);
						if (bInstall)
							this.addEventListener("click", onChange, false);
						this.previousChecked = this.checked;
					}).bind(this),
					aaOptions
				);
			};


			if (bInstall)
				this.addEventListener("click", onChange, false);
			else
				onChange.call(this, null);
		}
	},


	"input file": {

		// disable
	},


	"input password": {

		// disable
	},


	"input radio": {

		// disable
	},


	"input text": {

		// disable


		// enableAutoSubmit
	},


	"select": {

		// disable


		// Rende il tag "autoapplicante", cioè che a ogni modifica invia
		// immediatamente il nuovo valore al server con una richiesta
		// asincrona.
		// Se bInstall è true, la logica di funzionamento è installata
		// definitivamente; altrimenti lo è solo per una volta.
		//
		enableAutoSubmit: function(bInstall, sUrl, aaParams, onComplete /*= Function.Empty*/, aaOptions /*= {}*/) {
			var onChange = function(e) {
				this.removeEventListener("change", onChange, false);
				aaParams["newvalue"] = this.value;
				this.disable(true);
				PaLeg.asyncExec(
					sUrl,
					aaParams,
					(function(bSuccess, oResult) {
						this.disable(false);
						if (bSuccess || oResult["error"])
							this.value = oResult["newvalue"];
						else if (!bSuccess)
							this.selectedIndex = this.previousSelectedIndex;
						if (onComplete)
							onComplete(bSuccess, this.value);
						if (bInstall)
							this.addEventListener("change", onChange, false);
						this.previousSelectedIndex = this.selectedIndex;
					}).bind(this),
					aaOptions
				);
			};


			if (bInstall)
				this.addEventListener("change", onChange, false);
			else
				onChange.call(this, null);
		},


		/* Returns the value, as displayed to the user.
		 *
		 * String return
		 *   Contents of the selected <option> element.
		 */
		getValueLabel: function() {
			return this.options[this.selectedIndex].firstChild.nodeValue;
		}
	},


	// Reimplementare questi metodi serve:
	//   FX BUG: sbaglia a contare le righe (ma non nelle sezioni).
	//   OP BUG: crea i tag col nome in maiuscole.
	//   IE BUG: idem (vabbe', fa così con tutti gli elementi).
	//
	"table": {

		// Crea o restituisce il <caption> della tabella. DOM1.
		//
		createCaption: function() {
			if (!this.caption)
				this.insertBefore(document.createElement("caption"), this.firstChild);
			return this.caption;
		},


		// Crea o restituisce il <tfoot> della tabella. DOM1.
		//
		createTFoot: function() {
			if (!this.tFoot)
				this.insertBefore(document.createElement("tfoot"), !this.tBodies.length ? !this.rows.length ? this.firstChild : this.rows[0] : this.tBodies[0]);
			return this.tFoot;
		},


		// Crea o restituisce il <thead> della tabella. DOM1.
		//
		createTHead: function() {
			if (!this.tHead)
				this.insertBefore(document.createElement("thead"), !this.tFoot ? !this.tBodies.length ? !this.rows.length ? this.firstChild : this.rows[0] : this.tBodies[0] : this.tFoot);
			return this.tHead;
		},


		// Elimina il <caption>, se presente. DOM1.
		//
		deleteCaption: function() {
			if (this.caption)
				this.removeChild(this.caption);
		},


		// Rimuove una riga dalla tabella. DOM2.
		//
		deleteRow: function(i) {
			this.removeChild(this.rows[i != -1 ? i : this.rows.length - 1]);
		},


		// Elimina un <tbody> dalla tabella.
		//
		deleteTBody: function(i) {
			this.removeChild(this.tBodies[i != -1 ? i : this.tBodies.length - 1]);
		},


		// Elimina il <tfoot>, se presente. DOM1.
		//
		deleteTFoot: function() {
			if (this.tFoot)
				this.removeChild(this.tFoot);
		},


		// Elimina il <thead>, se presente. DOM1.
		//
		deleteTHead: function() {
			if (this.tHead)
				this.removeChild(this.tHead);
		},


		// Inserisce una riga all'indice specificato, nella sezione
		// corrispondente. Se la tabella non ha sezioni, inserisce una riga
		// qualunque, ma se non ci sono neanche righe, crea un <tbody> per la
		// nuova riga. DOM2.
		//
		insertRow: function(i) {
			// IE BUG + FX BUG: non semrpre generano l'array rows. L'impatto di
			// questa soluzione non è terribile, perché se la tabella è davvero
			// vuota l'esecuzione dei getChildren() non è troppo costosa, e poi
			// va fatto solo al primo inserimento.
			var arrRows;
			if (this.rows.length)
				arrRows = this.rows;
			else {
				arrRows = this.getChildren("tr");
				if (this.tHead)
					arrRows = arrRows.concat(this.tHead.getChildren("tr"));
				if (this.tFoot)
					arrRows = arrRows.concat(this.tFoot.getChildren("tr"));
				for (var iTB = 0; iTB < this.tBodies.length; ++iTB)
					arrRows = arrRows.concat(this.tBodies[iTB].getChildren("tr"));
			}

			if (i < -1 || i > arrRows.length)
				throw new /*Range*/Error("Row index out of bounds");
			var trPrev, oParent;
			if ((i != -1 && i < arrRows.length) || arrRows.length) {
				trPrev = arrRows[arrRows.length - 1];
				oParent = trPrev.parentNode;
			} else {
				trPrev = null;
				oParent = (this.tBodies.length ? this.tBodies[this.tBodies.length - 1] : this.appendChild(document.createElement("tbody")));
			}
			return oParent.insertBefore(document.createElement("tr"), trPrev);
		},


		// Inserisce un <tbody> all'indice specificato. Se la tabella aveva
		// righe senza avere <tbody>, sposta tutte le righe nel nuovo elemento.
		//
		insertTBody: function(i) {
			var tbody = document.createElement("tbody");
			if (this.rows.length && !this.tBodies.length) {
				var o;
				while (o = this.rows[0])
					tbody.appendChild(o);
			}
			return this.insertBefore(tbody, i == -1 ? null : this.tBodies[i]);
		}
	},


	"tbody": {

		// Rimuove la riga alla posizione specificata. DOM2.
		//
		deleteRow: function(i) {
			this.removeChild(this.rows[i != -1 ? i : this.rows.length - 1]);
		},


		// Inserisce una riga nella posizione specificata. DOM2.
		//
		insertRow: function(i) {
			if (i < -1 || i > this.rows.length)
				throw new /*Range*/Error("Row index out of bounds");
			return this.insertBefore(document.createElement("tr"), i == -1 || i == this.rows.length ? null : this.rows[i]);
		}
	},


	"textarea": {

		// Disabilita, anche visivamente, il controllo.
		//
		disable: function(bDisable) {
			if (bDisable)
				this.addCssClass("disabled");
			else
				this.removeCssClass("disabled");
			this.disabled = (bDisable || false);
		},


		// Rende il tag "autoapplicante", cioè che al termine della modifica
		// invia immediatamente il nuovo valore al server con una richiesta
		// asincrona.
		// Se bInstall è true, la logica di funzionamento è installata
		// definitivamente; altrimenti lo è solo per una volta.
		//
		enableAutoSubmit: function(bInstall, sUrl, aaParams, onComplete /*= Function.Empty*/, aaOptions /*= {}*/) {
			var onBlur = function(e) {
				this.removeEventListener("focus",   onFocus,   false);
				this.removeEventListener("blur",    onBlur,    false);
				this.removeEventListener("keydown", onKeyDown, false);
				if (this.value != this.previousValue) {
					aaParams["newvalue"] = this.value;
					this.disable(true);
					PaLeg.asyncExec(
						sUrl,
						aaParams,
						(function(bSuccess, oResult) {
							this.disable(false);
							if (bSuccess) {
								this.value = oResult["newvalue"];
								if (this.elemName != "textarea")
									this.setAttribute("title", this.value);
							}
							if (onComplete)
								onComplete(bSuccess, oResult["newvalue"]);
							if (!bSuccess) {
								this.focus();
								this.addEventListener("focus",   onFocus,   false);
								this.addEventListener("blur",    onBlur,    false);
								this.addEventListener("keydown", onKeyDown, false);
							} else if (bInstall)
								this.addEventListener("focus", onFocus, false);
						}).bind(this),
						aaOptions
					);
				} else
					if (onComplete)
						onComplete(null, this.value);
			};


			var onKeyDown = function(e) {
				switch (e.keyCode) {
					case 0x0A: // LF
					case 0x0D: // CR
						if (this.elemName == "textarea" && !e.ctrlKey)
							break;
						e.preventDefault();
						this.blur();
						break;

					case 0x1B: // Esc
						e.preventDefault();
						this.value = this.previousValue;
						this.blur();
						break;
				}
			};


			var onFocus = function(e) {
				this.previousValue = this.value;
				this.addEventListener("blur",    onBlur,    false);
				this.addEventListener("keydown", onKeyDown, false);
			};


			if (bInstall)
				this.addEventListener("focus", onFocus, false);
			else
				onFocus.call(this, null);
		}
	},


	"tfoot": {

		// deleteRow


		// insertRow
	},


	"thead": {

		// deleteRow


		// insertRow
	},


	"tr": {

		// Rimuove la cella della colonna specificata.
		//
		deleteCell: function(i) {
			this.removeChild(this.cells[i != -1 ? i : this.cells.length - 1]);
		},


		// Inserisce una riga nella colonna specificata.
		//
		insertCell: function(i) {
			if (i < -1 || i > this.cells.length)
				throw new /*Range*/Error("Cell index out of bounds");
			return this.insertBefore(document.createElement("td"), i == -1 || i == this.cells.length ? null : this.cells[i]);
		}
	}
};
$._aaElemMethods["input file"].disable = $._aaElemMethods["input password"].disable = $._aaElemMethods["input radio"].disable = $._aaElemMethods["input text"].disable = $._aaElemMethods["select"].disable = $._aaElemMethods["textarea"].disable;
$._aaElemMethods["input text"].enableAutoSubmit = $._aaElemMethods["textarea"].enableAutoSubmit;
$._aaElemMethods["tfoot"].deleteRow = $._aaElemMethods["thead"].deleteRow = $._aaElemMethods["tbody"].deleteRow;
$._aaElemMethods["tfoot"].insertRow = $._aaElemMethods["thead"].insertRow = $._aaElemMethods["tbody"].insertRow;



/*****************************************************************************
 * FUNCTIONS
 */


// Crea una proprietà elemName col nome del tag SEMPRE in minuscolo. Serve solo
// per colpa di IE.
//
var $eN = function(o, bReverse /*= false*/) {
	if (!o.elemName)
		o.elemName = o.nodeName;
	return o;
};

// Una ridefinizione di $eN fa sparire questa proprietà, che quindi risulterà
// non-true.
$eN.noop = true;

