/**
 * Simulates a connection to a database by making asynchronous http requests to 
 * a remote PHP script that returns the results in JSON format.
 * Using this class can perform any operation that can be done with a database,
 * like open/close a connection or selecion/updating queries.
 **/
Db.Conn = new Class ().extend
({
	 Flag:
	{
		 NOT_NULL : 1
		,PRI_KEY  : 2
		,AI       : 512 | 2 | 1
	}
	
	,Type:
	{
		 BOOLEAN   : 1
		,INTEGER   : 2
		,DOUBLE    : 3
		,STRING    : 4
		,DATE      : 8
		,DATE_TIME : 9
	}
});

Db.Conn.implement
({
	Extends: Vn.Object

	,connected: false
	,requestsCount: 0

	/**
	 * Initilizes the connection object.
	 **/
	,initialize: function ()
	{
		this.parent ();
	}

	/**
	 * Opens the connection to the database.
	 *
	 * @param {string} user The user name
	 * @param {String} password The user password
	 * @param {Boolean} remember Specifies if the user should be remembered
	 * @param {Function} openCallback The function to call when operation is done
	 **/
	,open: function (user, pass, remember, openCallback)
	{		
		this.signalEmit ('loading-changed', true);

		var request = new Vn.HttpRequest ();
		request.add ({'action': 'login'});

		if (user != null)
		{
			request.add
			({
				 'user': user
				,'password': pass
				,'remember': remember
			});
		}

		request.send ('rest.php',
			this.opened.bind (this, request, openCallback));
	}

	/*
	 * Called when open operation is done.
	 */
	,opened: function (request, openCallback, success)
	{
		var openSuccess = false;

		if (success)
		try {
			var json = request.getJson ();
			openSuccess = json.data == true;
		}
		catch (e) {}
	
		if (openSuccess)
		{
			this.connected = true;
			this.signalEmit ('openned');
		}

		this.signalEmit ('loading-changed', false);
		
		if (openCallback)
			openCallback (this, openSuccess);
	}

	/**
	 * Closes the connection to the database.
	 *
	 * @param {Function} closeCallback The function to call when operation is done
	 **/
	,close: function (closeCallback)
	{
		this.signalEmit ('loading-changed', true);

		var request = new Vn.HttpRequest ();
		request.add ({'action': 'close'});
		request.send ('rest.php',
			this.closed.bind (this, closeCallback));
	}

	/*
	 * Called when close operation is done.
	 */
	,closed: function (closeCallback)
	{
		this.connected = false;
		this.signalEmit ('closed');
		
		this.signalEmit ('loading-changed', false);

		if (closeCallback)
			closeCallback (this);
	}

	/**
	 * Runs a SQL query on the database.
	 *
	 * @param {String} sql The SQL statement
	 * @param {Function} callback The function to call when operation is done
	 **/
	,execSql: function (sql, callback)
	{
		this.requestsCount++;
		
		if (this.requestsCount == 1)
			this.signalEmit ('loading-changed', true);

		var httpRequest = new Vn.HttpRequest ()
		httpRequest.add
		({
			 'action': 'query'
			,'sql': sql
		});
		httpRequest.send ('rest.php',
			this.execDone.bind (this, httpRequest, callback));
	}
	
	/**
	 * Runs a stmt on the database.
	 *
	 * @param {Sql.Stmt} stmt The statement
	 * @param {Function} callback The function to call when operation is done
	 * @param {Sql.Batch} batch The batch used to set the parameters
	 **/
	,execStmt: function (stmt, callback, batch)
	{
		this.execSql (stmt.render (batch), callback);
	}

	/**
	 * Runs a query on the database.
	 *
	 * @param {String} query The SQL statement
	 * @param {Function} callback The function to call when operation is done
	 * @param {Sql.Batch} batch The batch used to set the parameters
	 **/
	,execQuery: function (query, callback, batch)
	{
		this.execStmt (new Sql.String ({query: query}), callback, batch);
	}

	/*
	 * Parses a value to date.
	 */
	,valueToDate: function (value)
	{
		return new Date (value * 1000);
	}
	
	/*
	 * Called when a query is executed.
	 */
	,execDone: function (httpRequest, callback, success)
	{
		var e;
		var error = null;
		var results = null;

		this.requestsCount--;
		
		if (this.requestsCount == 0)
			this.signalEmit ('loading-changed', false);

		if (!success)
		{
			error = new Vn.Error ('Conn', 'connError', _('ConnError'));
			this.signalEmit ('error', error);
		}
		else
		try {
			var json = httpRequest.getJson ();
			results = json.data;
			
			if (json.error !== null)
				error = new Vn.Error (json.error.domain,
					json.error.code, json.error.message);

			if (results instanceof Array)
			for (var i = 0; i < results.length; i++)
			if (results[i] !== true)
			{
				var data = results[i].data;
				var columns = results[i].columns;

				for (var j = 0; j < columns.length; j++)
				{
					var castFunc = null;
			
					switch (columns[j].type)
					{
						case Db.Conn.Type.DATE:
						case Db.Conn.Type.DATE_TIME:
						case Db.Conn.Type.TIMESTAMP:
							castFunc = this.valueToDate;
							break;
					}
			
					if (castFunc !== null)
					{
						if (columns[j].def != null)
							columns[j].def = castFunc (columns[j].def);

						for (var k = 0; k < data.length; k++)
							if (data[k][j] != null)
								data[k][j] = castFunc (data[k][j]);
					}
				}
			}
		}
		catch (e)
		{
			error = e;
		}
		
		if (error)
			this.signalEmit ('error', error);

		if (callback)
		try {
			callback (new Db.ResultSet (results, error));
		}
		catch (e)
		{
			this.signalEmit ('error', e);
		}
	}
});

