/**
 * Simulates a connection to a database by making asynchronous requests to a
 * remote REST service 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.
 *
 * Warning! You should set a well defined dababase level privileges to use this
 * class or you could have a serious security hole in you application becasuse
 * the user can send any statement to the server. For example: DROP DATABASE
 */
var Connection = new Class();
module.exports = Connection;

var Flag = {
	 NOT_NULL : 1
	,PRI_KEY  : 2
	,AI       : 512 | 2 | 1
};

var Type = {
	 BOOLEAN   : 1
	,INTEGER   : 3
	,DOUBLE    : 4
	,STRING    : 5
	,DATE      : 8
	,DATE_TIME : 9
};

Connection.extend({
	 Flag: Flag
	,Type: Type
});

Connection.implement({
	Extends: Vn.JsonConnection

	/**
	 * 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.send('core/query', {'sql': sql},
			this._onExec.bind(this, 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 {Object} params The query params
	 */
	,execStmt: function(stmt, callback, params) {
		this.execSql(stmt.render(params), 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 {Object} params The query params
	 */
	,execQuery: function(query, callback, params) {
		this.execStmt(new Sql.String({query: query}), callback, params);
	}

	/*
	 * Parses a value to date.
	 */
	,valueToDate: function(value) {
		return fixTz(new Date(value));
	}

	/*
	 * Called when a query is executed.
	 */
	,_onExec: function(callback, json, error) {
		const results = [];

		if (json)
		try {
			if (json && json instanceof Array)
			for (let i = 0; i < json.length; i++)
			if (json[i] !== true) {
				const rows = json[i].data;
				const columns = json[i].columns;

				const data = new Array(rows.length);
				results.push({
					data,
					columns,
					tables: json[i].tables
				});

				for (let j = 0; j < rows.length; j++) {
					const row = data[j] = {};
					for (let k = 0; k < columns.length; k++)
						row[columns[k].name] = rows[j][k];
				}

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

						for (let k = 0; k < data.length; k++)
							if (data[k][col.name] != null)
								data[k][col.name] = castFunc(data[k][col.name]);
					}
				}
			} else
				results.push(json[i]);
		} catch (e) {
			error = e;
		}

		if (callback)
			callback(new Db.ResultSet(results, error));
	}
});

// TODO: Read time zone from db configuration
var tz = {timeZone: 'Europe/Madrid'};
var isLocal = Intl
	.DateTimeFormat()
	.resolvedOptions()
	.timeZone == tz.timeZone;

function fixTz(date) {
	if (isLocal) return date;

	var localDate = new Date(date.toLocaleString('en-US', tz));
	var hasTime = localDate.getHours()
		|| localDate.getMinutes()
		|| localDate.getSeconds()
		|| localDate.getMilliseconds(); 

	if (!hasTime) {
		date.setHours(date.getHours() + 12);
		date.setHours(0, 0, 0, 0);
	}

	return date;
}