/*
	Copyright (C) 2012 - Juan Ferrer Toribio

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 2.1 of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this program; if not, write to the Free
	Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
	02111-1307 USA.
*/

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Global

var n;

function include (file)
{
/*	var script;
	var head = document.getElementsByTagName ('head')[0];
	
	script = document.createElement ('script');
	script.type = 'text/javascript';
	script.src = file;
	head.appendChild (script);
*/
	document.write('<script type="text/javascript" src="' + file + '"></script>'); 
}

Date.prototype.clone = function ()
{
	return new Date (this.getTime ());
}

function roundTo (number, places)
{
	var num = 10 * places;
	return Math.round (number * num) / num;
}

/**
 * Clase base para todos los objetos de la libreria. Gestiona todo el sistema
 * de señales.
 *
 * @prop signal Matriz con todas las señales del objeto instanciado.
 **/
var SqlObject = new Class
({
	signal: new Array ()

	,initialize: function () {}

	/**
	 * Conecta una señal con una método o función.
	 *
	 * @param id Identificador único de la señal.
	 * @param func Función o método del objeto obj al que llamar.
	 * @param obj Objeto que tiene el método a llamar.
	 * @param args Parámetros adicionales que se pasarán al llamar al método.
	 **/
	,addSignal: function (id, func, obj)
	{
		var n;
		var arg;
		var cb;
		var sig = this.signal;

		cb = new Object ();
		cb.blocked = false;
		cb.func = func;
		cb.obj = obj;
		
		if (arguments.length > 3)
		{
			arg = new Array ();
		
			for (n = 3; n < arguments.length; n++)
				arg.push (arguments[n]);
		}
		else
			arg = null;
		
		cb.arg = arg;
		
		for (n = 0; n < sig.length && sig[n].id != id; n++);
		
		if (n == sig.length)
		{
			var signal = new Object ();
			signal.id = id;	
			signal.callback = new Array ();
			sig.push (signal);
		}
			
		sig[n].callback.push (cb);
	}

	/**
	 * Bloquea/desbloquea una conexión con una señal.
	 *
	 * @param id Identificador de la señal.
	 * @param obj Callback a bloquear/desbloquear.
	 * @block true para bloquear, false para desbloquear.
	 **/
	,blockSignal: function (id, obj, block)
	{
		var cb;
		var sig = this.signal;
	
		for (var n = 0; n < sig.length; n++)
		{
			if (sig[n].id == id)
			{
				cb = sig[n].callback;

				for (n = 0; n < cb.length; n++)
				{
					if (cb[n].obj == obj)
					{
						cb[n].blocked = block;
						break;
					}
				}

				break;
			}
		}				
	}
	
	/**
	 * Emite una señal del objeto actual.
	 *
	 * @param id Identificador de la señal a emitir.
	 **/
	,signalEmit: function (id)
	{
		var cb;
		var arg = new Array ();
		var sig = this.signal;

		arg.push (this);
		for (n = 1; n < arguments.length; n++)
			arg.push (arguments[n]);

		for (var n = 0; n < sig.length; n++)
		{
			if (sig[n].id == id)
			{
				cb = sig[n].callback;
			
				for (n = 0; n < cb.length; n++)
					if (!cb[n].blocked)
						cb[n].func.apply (cb[n].obj, arg.concat (cb[n].arg));
			
				break;
			}
		}
	}
});

//+++++++++++++++++++++++++++++++++++++++++++++++++++ SqlParam

n = -1;
var SQL_PARAM_NONE		= ++n;
var SQL_PARAM_MASTER	= ++n;
var SQL_PARAM_SLAVE		= ++n;

var SqlParam = new Class
({
	state: SQL_PARAM_NONE

	,setValue: function (value)
	{
		if (typeof value === 'object' && value != null && value != undefined)
			this.realValue = value.clone ();
		else
			this.realValue = value;

		this.setRealValue (this.realValue);
	}

	,getValue: function ()
	{
		return this.realValue;
	}

	,bindParam: function (param)
	{
		if (param.state == SQL_PARAM_NONE)
		{
			switch (this.state)
			{
				case SQL_PARAM_NONE:
					this.state = SQL_PARAM_MASTER;
				case SQL_PARAM_MASTER:
					param.state = SQL_PARAM_SLAVE;
					param.addSignal ('changed', this.paramChanged, this);
					this.addSignal ('changed', this.paramChanged, param);
					param.paramChanged (this);
					return;
			}
		}
		
		alert (TEXT_SlaveParamBind);
	}

	,paramChanged: function (param)
	{
		this.blockSignal ('changed', param, true);
		this.setValue (param.getValue ());
		this.blockSignal ('changed', param, false);
	}
});

//+++++++++++++++++++++++++++++++++++++++++++++++++++ SqlExpr

var SqlExpr = new Class
({
	Extends: SqlObject
});

var SqlField = new Class
({
	Extends: SqlExpr
	
	,target: null

	,initialize: function (fname, target)
	{
		this.fname = fname;
		this.target = target;
	}

	,render: function ()
	{
		var sql = (this.target) ? '`' + this.target + '`.' : '';	
		return sql + '`' + this.fname + '`';
	}
});

var SqlFunction = new Class
({
	Extends: SqlExpr
	
	,fName: null
	,param: new Array ()
	
	,initialize: function (fname)
	{
		this.fname = fname;
	}

	,render: function ()
	{
		return this.fname + '()';
	}
});

var SqlValue = new Class
({
	Extends: SqlExpr
	,Implements: SqlParam

	,initialize: function ()
	{
		this.parent ();
	}

	,setRealValue: function (value)
	{
		this.signalEmit ('changed');
	}

	,render: function ()
	{
		var sql;
		var val = this.realValue;
	
		switch (typeof val)
		{
			case 'number':
				sql = val;
				break;
			case 'boolean':
				sql = (val) ? 'TRUE' : 'FALSE';
				break;
			case 'string':
				val = val.replace (new RegExp ('[\\\\]', 'g'), "\\\\");
				sql = "'" + val.replace (new RegExp ("[']", 'g'), "\\'") + "'";
				break;
			case 'object':
				if (val instanceof Date) {
					sql = "'" + val.getFullYear () + '-' + (val.getMonth () + 1) + '-' + val.getDate () + "'";
					break;
				}
			default:
				sql = 'NULL';
		}
		
		return sql;
	}
});

n = -1;
var SQL_OPERATION_EQUAL	= ++n;
var SQL_OPERATION_AND	= ++n;
var SQL_OPERATION_OR	= ++n;

n = -1;
var SQL_OPERATION_MODE_NORMAL	= ++n;
var SQL_OPERATION_MODE_GROUP	= ++n;

var SqlOperation = new Class
({
	Extends: SqlExpr

	,expr: new Array ()
	,mode: SQL_OPERATION_MODE_NORMAL

	,initialize: function (type)
	{
		this.type = type;
	}

	,addExpr: function (expr)
	{
		this.expr.push (expr);
		expr.addSignal ('changed', this.exprChanged, this);
	}

	,exprChanged: function (expr)
	{
		this.signalEmit ('changed');
	}

	,addParam: function (param)
	{
		var value = new SqlValue ();
		param.bindParam (value);
		this.addExpr (value);
	}

	,isReady: function ()
	{
		var n;
		var e = this.expr;

		if (this.mode != SQL_OPERATION_MODE_GROUP)
		{ 
			for (n = 0; n < e.length; n++)
				if (e[n] instanceof SqlValue
				&& e[n].getValue () == null)
					return false;
					
			return true;
		}
		else
		{
			for (n = 0; n < e.length; n++)
				if ((e[n] instanceof SqlOperation
				|| e[n] instanceof SqlString)
				&& e[n].isReady ())
					return true;

			return false;
		}
	}
	
	,setMode: function (mode)
	{
		this.mode = mode;
	}

	,render: function ()
	{
		var n;
		var sql = '(';
		var sqlop;
		var e = this.expr;
		
		switch (this.type)
		{
			case SQL_OPERATION_EQUAL:
				sqlop = '=';
				break;
			case SQL_OPERATION_AND:
				sqlop = 'AND';
				break;
			case SQL_OPERATION_OR:
				sqlop = 'OR';
				break;
			default:
				sqlop = ',';
		}
		
		sqlop = ' ' + sqlop + ' ';

		if (this.mode != SQL_OPERATION_MODE_GROUP)
		{
			for (n = 0; n < e.length; n++)
			{
				if (n > 0)
					sql += sqlop;

				sql += e[n].render ();
			}
		}
		else
		{
			var ok = false;

			for (n = 0; n < e.length; n++)
			{
				if ((e[n] instanceof SqlOperation
				|| e[n] instanceof SqlString)
				&& e[n].isReady ())
				{
					if (ok)
						sql += sqlop;

					sql += e[n].render ();
					ok = true;
				}
			}
			
			if (!ok)
				sql += 'TRUE';
		}
		
		sql += ')';
		
		return sql;
	}
});

var SqlGroup = new Class
({
	Extends: SqlOperation
	
	,initialize: function (type)
	{
		this.parent (type);
		this.setMode (SQL_OPERATION_MODE_GROUP);
	}
	
	,addEqual: function (param, field, target)
	{
		var op = new SqlOperation (SQL_OPERATION_EQUAL);
		op.addExpr (new SqlField (field, target));
		op.addParam (param);
		this.addExpr (op);
	}
	
	,addLike: function (param, field, target)
	{
		var op = new SqlString ("%s LIKE CONCAT('%', %s, '%')");
		op.addExpr (new SqlField (field, target));
		op.addParam (param);
		this.addExpr (op);
	}
})

//+++++++++++++++++++++++++++++++++++++++++++++++++++ SqlTarget

var SqlTarget = new Class
({
	Extends: SqlObject
});

var SqlTable = new Class
({
	Extends: SqlTarget

	,initialize: function (tname, schema)
	{
		this.schema = schema;
		this.tname = tname;
	}

	,render: function ()
	{
		var sql;
		sql = (this.schema) ? '`' + this.schema + '`.' : '';
		sql = sql + '`' +  this.tname + '`';
		return sql;
	}
});

//+++++++++++++++++++++++++++++++++++++++++++++++++++ SqlStmt

var SqlStmt = new Class
({
	Extends: SqlObject
	
	,where: false
	,target: new Array ()

	,initialize: function ()
	{
		this.parent ();
	}

	,addTarget: function (target)
	{
		this.target.push (target);
	}

	,renderTarget: function ()
	{
		var sql;
		var len = this.target.length;
		
		if (len > 0)
		{
			sql = ' ';
		
			for (var n = 0; n < len; n++)
			{
				if (n > 0) sql += ', ';
				sql += this.target[n].render ();
			}
		}
		else
			sql += 'DUAL';
		
		return sql;
	}		
});

var SqlSelect = new Class
({
	Extends: SqlStmt

	,expr: new Array (),

	initialize: function ()
	{
		this.parent ();
	}

	,addField: function (fname)
	{
		this.expr.push (new SqlField (fname, null));
	}

	,render: function ()
	{
		var sql = 'SELECT '
		
		for (var n = 0; n < this.expr.length; n++)
		{
			if (n > 0)
				sql += ', ';
			sql += this.expr[n].render();
		}
		
		sql += ' FROM ' + this.renderTarget ();
		
		if (this.where)
			sql += ' WHERE ' + this.where.render ();
		
		return sql;
	}
});

var SqlDml = new Class
({
	Extends: SqlStmt
	
	,field: new Array ()
	,expr: new Array ()

	,initialize: function ()
	{
		this.parent ();
	}

	,addSet: function (fname, fvalue)
	{
		var expr = new SqlValue ();
		expr.setValue (fvalue);
		this.expr.push (expr);
	
		this.field.push (new SqlField (fname, null));
	}

	,delSet: function ()
	{
		this.field.splice (0, this.field.length);
		this.expr.splice (0, this.expr.length);
	}
});

var SqlInsert = new Class
({
	Extends: SqlDml

	,initialize: function ()
	{
		this.parent ();
	},

	render: function ()
	{
		var sql;
		var n;
		
		sql = 'INSERT INTO ' + this.renderTarget () + ' (';
		
		for (n = 0; n < this.field.length; n++)
		{
			if (n > 0)
				sql += ', ';
			sql += this.field[n].render ();
		}
		
		sql += ') VALUES (';

		for (n = 0; n < this.field.length; n++)
		{
			if (n > 0)
				sql += ', ';
			sql += this.expr[n].render();
		}
		
		sql += ')';
			
		return sql;
	}
})

var SqlUpdate = new Class
({
	Extends: SqlDml

	,initialize: function ()
	{
		this.parent ();
	}

	,render: function ()
	{
		var sql;
		var n;
		
		sql = 'UPDATE ' + this.renderTarget () + ' SET ';
		
		for (n = 0; n < this.field.length; n++)
		{
			if (n > 0)
				sql += ', ';
			sql += this.field[n].render () + ' = ' + this.expr[n].render();
		}
		
		if (this.where)
			sql += ' WHERE ' + this.where.render ();
			
		sql += ' LIMIT 1';	// Trash. Only for security.
			
		return sql;
	}
});

var SqlDelete = new Class
({
	Extends: SqlStmt

	,initialize: function ()
	{
		this.parent ();
	}

	,render: function ()
	{
		var sql = 'DELETE FROM ' + this.renderTarget ();
		
		if (this.where)
			sql += ' WHERE ' + this.where.render ();
			
		sql += ' LIMIT 1';	// Trash. Only for security.
			
		return sql;
	}
});

var SqlString = new Class
({
	Extends: SqlStmt,

	initialize: function (sql)
	{
		var next;
		var count = 0;

		this.parent ();
		
		while (true)
		{
			next = sql.indexOf ('%s', next);

			if (next != -1)
			{
				count++;
				next++;
			}
			else
				break;
		}

		this.count = count;
		this.param = new Array ();
		this.sql = sql;
	},

	addValue: function (uvalue)
	{
		var value = new SqlValue ();
		value.setValue (uvalue);
		this.param.push (value);
	},

	addParam: function (param)
	{
		var value = new SqlValue ();
		param.bindParam (value);
		this.addExpr (value);
	},

	addExpr: function (value)
	{
		this.param.push (value);
		value.addSignal ('changed', this.paramChanged, this);
	},

	paramChanged: function (param)
	{
		this.signalEmit ('changed');
	},

	isReady: function ()
	{
		var p;
		var count = this.count;
		var param = this.param;

		if (param.length < count)
			return false;

		for (var n = 0; n < count; n++)
		{
			p = param[n];
		
			if (p instanceof SqlValue)
			{
				if (p.getValue () == null)
					return false;
			}
			else if (p instanceof SqlOperation)
				if (!p.isReady ())
					return false;
		}

		return true;
	}

	,render: function ()
	{
		var sql = this.sql;
	
		for (var n = 0; n < this.param.length; n++)
			sql = sql.replace ('%s', this.param[n].render ());
		
		return sql;
	}
});

//+++++++++++++++++++++++++++++++++++++++++++++++++++ SqlMultiStmt

var SqlMultiStmt = new Class
({
	Extends: SqlObject

	,initialize: function ()
	{
		this.parent ();
		this.stmt = new Array ();
	}

	,addStmt: function (stmt)
	{
		return this.stmt.push (stmt);
	}

	,render: function ()
	{
		var sql = '';

		for (var n = 0; n < this.stmt.length; n++)
		{
			sql += this.stmt[n].render () + '; ';
		}

		return sql;
	}
});



