/*
@name: /htdocs/js/lib/oggetti/datasetholder.js
@desc: Componente javascript datasetholder
@authors: Marco Biondi
@lastauthor: Marco Biondi
@require: (Mini) Prototype 1.4
*/
var DataSetHolder = Class.create();
DataSetHolder.prototype = {
	initialize: function(options) {
		this._lastURL = null;
		this._lastParams = null;
		this._lastMethod = null;
		this.options = {
				setName: 'recordset',
				rowName: 'record',
				statusAttr: 'status',
				okValue:	'ok',
				errorAttr: 'message',
				randomizeURL: false,
				importAttrs: false,
				metaHeader: null,
				metaExclude: {},
				orderBy: '__def__',
				flattenTags: {},
				autoUpdate: false,
				autoTime: 10*60,	//10 Minuti
				autoDecay: 2,
				forceSyncData: false	//Se true obbliga i dati a rispecchiare lo stato delle richieste Ajax (dataset vuoto durante la richiesta, meta recuperati anche in caso di errore)
			};

		Object.extend(this.options, options || {});

		//TODO: definire le opzioni che determinano il tipo di dataset
		//Es. aggiornamento periodico, paging o altro
		this._AjaxR = null;
		this._dset = null;
		this._curView = null;
		this._curMeta = null;
		this._timer = null;
		this._curDecay = 1;
		this._lastDoc = null;
		this._isRunning = false;
		this._imKilled = false;

		EventBroadcaster.initialize(this);

		Event.observe(window, 'unload', this.destroy.bindAsEventListener(this), false);
	},

	updateOptions: function(options) {
		Object.extend(this.options, options || {});

		if(!this.options.autoUpdate)
		{
			clearTimeout(this._timer);
			this._timer = null;
		}
	},

	setSchema: function(mixed) {
		//TODO: imposta lo schema per il dataset
		//mixed puņ essere un oggetto ? (come derivare la prototype chain? )
		//un xml
		//come generare lo schema base...
	},

	destroy: function() {
		if (this._AjaxR)
			delete(this._AjaxR);
		if (this._timer)
			clearTimeout(this._timer);
		this._imKilled = true;
	},

	forceRefresh: function() {
		if(this._imKilled)
			return;
		this.bindTo(this._lastURL,	this._lastParams, this._lastMethod);
	},

	_startTimer: function() {
		if(this._imKilled)
			return;
		if(this.options.autoUpdate)
		{
			this._timer = setTimeout(this._onTimerEvent.bind(this),	this._curDecay * this.options.autoTime * 1000);
		}
	},

	_onTimerEvent: function() {
		if(this._imKilled)
			return;
		if(this._isRunning)
		{
			this.dispatchEvent({type: "onDataError", error: this._makeError("Dataset Timer overrun!")});
			this._startTimer();
			return;
		}

		this.bindTo(this._lastURL,	this._lastParams, this._lastMethod);
	},

	_onReqFailure: function(req, json) {
		if(this._imKilled)
			return;
		this._dumpError(req);
		this.dispatchEvent({type: "onDataError", error: this._makeError("HTTP Error ["+req.status+"]: "+req.statusText)});
		this._isRunning = false;
		this._startTimer();
	},

	_onReqException: function(aReq, errObj) {
		if(this._imKilled)
			return;
		this._dumpError(errObj);
		this.dispatchEvent({type: "onDataError", error: this._rethrowError("JS Error: ", errObj)});
		this._isRunning = false;
		this._startTimer();
	},

	_onReqComplete: function(req) {
		if(this._imKilled)
			return;
		var foo, bar, t, xmlD;
		var haserr = false;
		try
		{
			if(req.status > 0 && req.status != 200)
			{
				//Evita eventi doppi di errori se _onReqFailure e' passata (e ha spento isRunning)
				if(this._isRunning)
				{
					throw this._makeError("HTTP Error ["+req.status+"]: "+req.statusText);
				}
				else
				{
					haserr = true;
				}
			}
			else
			{
				var tempMeta = null; //Per i vari casi dei metadati

				if(this.options.autoUpdate && this.options.autoDecay)
				{
					this._curDecay = (req.responseText == this._lastDoc ? this._curDecay * this.options.autoDecay : 1);
					this._lastDoc = req.responseText;
				}

				xmlD = req.responseXML;
				if(xmlD)
				{
					this.dispatchEvent({'type': "onRequestComplete", 'req': req, 'doc': xmlD});
				}
				else
				{
					throw this._makeError("Invalid XML");
				}

				//Parsing dei metadati (in tempMeta)
				if(this.options.metaHeader)
				{
					bar = this.options.metaHeader.split(":");
					if(bar.length == 2)
					{
						t = this.options.metaHeader;
						foo = Try.these(
							function() {return xmlD.getElementsByTagNameNS("*",bar[1]); },
							function() {return xmlD.getElementsByTagName(t); }
						);
						foo = foo[0];
					}
					else
					{
						foo = xmlD.getElementsByTagName(this.options.metaHeader)[0];
					}
	
					if(foo)
					{
						tempMeta = new Object();
						bar = foo.firstChild;
						while(bar)
						{
							if(bar.nodeType == 1 && typeof(this.options.metaExclude[bar.nodeName]) == 'undefined')
							{
								tempMeta[bar.nodeName] = this._importChilds(bar, false);
							}
							bar = bar.nextSibling;
						}
					}
				}

				if(this.options.forceSyncData)
				{
					this._curMeta = tempMeta;
				}

				//Parsing del dataset
				bar = this.options.setName.split(":");
				if(bar.length == 2)
				{
					t = this.options.setName;
					foo = Try.these(
						function() {return xmlD.getElementsByTagNameNS("*",bar[1]); },
						function() {return xmlD.getElementsByTagName(t); }
					);
				}
				else
				{
					foo = xmlD.getElementsByTagName(this.options.setName);
				}

				if(foo.length != 1)
					throw this._makeError("Dataset malformed: ["+foo.length+"] elements "+this.options.setName+" found.");

				foo = foo[0];

				if(!this.options.statusAttr || foo.getAttribute(this.options.statusAttr) == this.options.okValue)
				{
					this._dset = $A();
					bar = foo.firstChild;
					while(bar)
					{
						if(bar.nodeType == 1 && bar.nodeName == this.options.rowName)
						{
							t = new Object();
							foo = bar.firstChild;
							while(foo)
							{
								if(foo.nodeType == 1)
								{
									t[foo.nodeName] = this._importChilds(foo, !this.options.importAttrs);
								}
								foo = foo.nextSibling;
							}
							this._dset.push(t);
						}
						bar = bar.nextSibling;
					}

					this.sortOn(this.options.orderBy, false); //genera _curView

					this._curMeta = tempMeta; //Inietta i metadati

					this.dispatchEvent({type: "onDataChange", dEvType: "updateAll"});
				}
				else
				{
					throw this._makeError((foo.getAttribute(this.options.errorAttr) ? foo.getAttribute(this.options.errorAttr) : 'No data available'));
				}
			}
		}
		catch(err)
		{
			this._dumpError(err);
			this.dispatchEvent({type: "onDataError", error: this._rethrowError("JS Error: ",err)});
			haserr = true;
		}
		this._isRunning = false;
		this.dispatchEvent({'type': "onDataComplete", errstatus: haserr});
		this._startTimer();
	},

	_rethrowError: function(str, err)
	{
		if(err.name == "RecordsetError")
			return(err);
		else
			return this._makeError(str+err.message);
	},

	_makeError: function(str)
	{
		var er = new Error(str);
		try
		{
			//Setto il tipo di errore per riconoscerlo
			er.name = "RecordsetError";
		}
		catch(dummy) {};
		return (er);
	},

	_dumpError: function(err)
	{
		if(_JSDebug || _ISStaging)
		{
			if(_isIE)
			{
				console.debug("Dataset throws error on:\n\turi: %s [%s] (%s)\n\toptions: %s", this._lastURL, this._lastParams, this._lastMethod, this.options);
				console.debug("Dataset throws error: %s", err.toString());
			}
			else
			{
				console.debug("Dataset throws error on:\n\turi: %s [%s] (%s)\n\toptions: %o", this._lastURL, this._lastParams, this._lastMethod, this.options);
				console.debug("Dataset throws error: %1.o", err);
			}
		}
	},

	_flattenChilds: function(node)
	{
		var res = null;
		if(node.firstChild)
		{
			res = '';
			var foo = node.firstChild;
			while(foo)
			{
				switch(foo.nodeType)
				{
					case 1:
						var attrs = '';
						if(foo.attributes.length > 0)
						{
								for(var i=0; i < foo.attributes.length; i++)
								{
									attrs += " "+foo.attributes[i].name+"='"+foo.attributes[i].value+"'";
								}
						}

						var bar = this._flattenChilds(foo);
						if(bar !== null)
							res += "<"+foo.nodeName+attrs+">"+bar+"</"+foo.nodeName+">";
						else
							res += "<"+foo.nodeName+attrs+"/>";
						break;
					case 3:
					case 4:
						res += foo.nodeValue;
						break;
				}
				foo = foo.nextSibling;
			}
		}

		return(res);
	},

	_importChilds: function(node, textOnly)
	{
		var realName = node.nodeName.split(":")[1];
		if(!textOnly && (
				(typeof(this.options.flattenTags[node.nodeName]) != 'undefined') || 
				(typeof(this.options.flattenTags[realName]) != 'undefined') ) )
		{
			return this._flattenChilds(node);
		}

		var res = null;
		if(node.firstChild)
		{
			var foo = node.firstChild;
			var foundNode = false;

			if(textOnly)
				res = '';
			else
			{
				res = { '_CDATA': '' };

				if(node.attributes.length > 0)
				{
					res['_ATTR'] = {};
					for(var i=0; i < node.attributes.length; i++)
					{
						res['_ATTR'][node.attributes[i].name] = node.attributes[i].value;
					}
					foundNode = true;
				}
			}

			while(foo)
			{
				switch(foo.nodeType)
				{
					case 1:
						if(textOnly)
							res += this._importChilds(foo, true);
						else
						{
							res[foo.nodeName] = this._importChilds(foo, false);
							foundNode = true;
						}
						break;
					case 3:
					case 4:
						if(textOnly)
							res += foo.nodeValue;
						else
							res['_CDATA'] += foo.nodeValue;
						break;
				}
				foo = foo.nextSibling;
			}

			if(!foundNode && !textOnly)
				res = res['_CDATA'];
		} 
		else if(!textOnly && (node.attributes.length > 0))
		{
				res = { '_ATTR' : {} };
				for(var i=0; i < node.attributes.length; i++)
				{
					res['_ATTR'][node.attributes[i].name] = node.attributes[i].value;
				}
		}

		return(res);
	},

	isPending: function()
	{
		return(this._isRunning);
	},

	bindTo: function(urlBind, params, metodo, delay)
	{
		if(this._imKilled)
		{
			throw this._makeError("Dataset was deleted!");
			return(true);
		}

		if(this._isRunning)
		{
			throw this._makeError("Dataset overrun!");
			return(true);
		}
		
		if (this._timer)
		{
			 clearTimeout(this._timer); 
			 this._timer = null;	
		}

		if(this._AjaxR)
		{
			delete(this._AjaxR);
		}

		if(metodo == "__local")
		{
			this._bindToLocal(params);
			this._startTimer();
			return;
		}

		var opts = {
				onComplete:		this._onReqComplete.bind(this),
				onFailure:		this._onReqFailure.bind(this),
				onException:	this._onReqException.bind(this)
			};

		var tmpPar;
		if(params)
		{
			if(typeof params == 'string')
			{
				tmpPar = params.toQueryParams();
			}
			else
			{
				tmpPar = Object.clone(params);
			}
		}
		else
		{
			tmpPar = {};
		}

		if(metodo)
			opts['method'] = String(metodo).toLowerCase();

		this._lastURL = urlBind;
		this._lastParams = tmpPar;
		this._lastMethod = metodo;

		if(this.options.randomizeURL)
		{
			tmpPar['__d'] = Date.parse(new Date());
			tmpPar['__r'] = Math.random();
		}

		if(Prototype.Version < "1.5.1")
		{
			opts['parameters'] = $H(tmpPar).map(
				function(pair) {
						var key = encodeURIComponent(pair.key);
						var value = (pair.value != undefined) ? encodeURIComponent(String(pair.value)) : '';
      			return key+'='+value;
    			}
    		).join('&');
		}
		else
		{
			opts['parameters'] = tmpPar;
		}

		if(delay)
			this._startTimer();
		else
		{
			this._isRunning = true;
			if(this.options.forceSyncData)
			{
				this._dset = null;
				this._curView = null;
				this._curMeta = null;
			}
			this.dispatchEvent({type: "onDataRequest"});
			this._AjaxR = new Ajax.Request( urlBind, opts );
		}
	},

	getLength: function() {
		if(this._curView)
			return(this._curView.length);
		else
			return(0);
	},

	getItemAt: function(id) {
		if(this._curView)
		{
			if(this._curView[id])
				return(this._curView[id]);
		}
		return(null);
	},

	getMetaData: function() {
		if(this._curMeta)
		{
			return(this._curMeta);
		}
		return(null);
	},

	sortOn: function(campo) {
		/*
		Funzione sperimentale!!!
		metacampi:
		__def__ non fa nulla
		__rnd__ random
		*/
		switch(campo)
		{
			case '__def__':
				this._curView = $A(this._dset);
				break;
			case '__rnd__':
				var a = $A(this._dset);
				this._curView = new Array();
				var i;
				while(a.length)
				{
					i = Math.floor(Math.random()*a.length);
					this._curView.push(a[i]);
					a.splice(i,1);
				}
				break;
			default:
				//TODO: Implementare l'ordinamento!!!
				this._curView = $A(this._dset);
				break;
		}

		if(arguments[1] !== false)
			this.dispatchEvent({type: "onDataChange", dEvType: "updateAll"});
	},

	filter: function(rexpr, colarr, limit) {
		var res = new Array();
		var a = $A(this._dset);
		
		for(var i=0; i<a.length && res.length<limit; i++)
		{
			for(var j=0; j<colarr.length; j++)
			{
				if(rexpr.test(a[i][colarr[j]]))
				{
					res.push(a[i]);
					break;
				}
			}
		}

		return res;
	},

	isBinded: function() {
		return(this._dset != null);
	},

	_bindToLocal: function(lData)
	{
		this._lastURL = "";
		this._lastParams = lData;
		this._lastMethod = "__local";

		try
		{
			if(lData['meta'])
			{
				this._curMeta = new Object();
				for(var i in lData['meta'])
				{
					if(typeof(lData['meta'][i]) != 'function')
						this._curMeta[i] = lData['meta'][i];
				}
			}
			else
			{
				this._curMeta = null;
			}

			this._dset = $A();

			if(lData['item'])
			{
				var d,i;
				d = $A(lData['item']);
				for(i=0; i<d.length; i++)
					this._dset.push(d[i]);
			}

			this.sortOn(this.options.orderBy); //Generate view and dispatch updateAll
		}
		catch(err)
		{
			this._dumpError(err);
			this.dispatchEvent({type: "onDataError", error: this._rethrowError("JS Error: ", err)});
		}
	},

	getLastBindParams: function()
	{
		return(Object.clone(this._lastParams));
	}
}
