/*
 * $Id$
 *
 * SQlite module core functions
 *
 * Copyright (C) 2010 Timo Teräs
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Kamailio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * Kamailio 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "../../core/mem/mem.h"
#include "../../core/dprint.h"
#include "../../lib/srdb1/db_pool.h"
#include "../../lib/srdb1/db_ut.h"
#include "../../lib/srdb1/db_res.h"
#include "../../lib/srdb1/db_query.h"
#include "dbase.h"
#include "db_sqlite.h"

static time_t sqlite_to_timet(double rT)
{
	return 86400.0 * (rT - 2440587.5) + 0.5;
}

static double timet_to_sqlite(time_t t)
{
	return ((((double)t) - 0.5) / 86400.0) + 2440587.5;
}

/*
 * Initialize database module
 * No function should be called before this
 */

static struct sqlite_connection *db_sqlite_new_connection(
		const struct db_id *id)
{
	struct sqlite_connection *con;
	int rc;
	int flags = 0;

	con = pkg_malloc(sizeof(*con));
	if(!con) {
		LM_ERR("failed to allocate driver connection\n");
		return NULL;
	}

	memset(con, 0, sizeof(*con));
	con->hdr.ref = 1;
	con->hdr.id = (struct db_id *)id; /* set here - freed on error */

	str db_name = str_init((char *)id->database);
	db_param_list_t *db_param = db_param_list_search(db_name);
	if(db_param && db_param->readonly) {
		/* The database is opened in read-only mode. If the database does not
		 * already exist, an error is returned. */
		flags |= SQLITE_OPEN_READONLY;
		LM_DBG("[%s] opened with [SQLITE_OPEN_READONLY]\n", id->database);
	} else {
		flags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
		LM_DBG("[%s] opened with [SQLITE_OPEN_READWRITE | "
			   "SQLITE_OPEN_CREATE]\n",
				id->database);
	}
	rc = sqlite3_open_v2(id->database, &con->conn, flags, NULL);
	if(rc != SQLITE_OK) {
		pkg_free(con);
		LM_ERR("failed to open sqlite database '%s'\n", id->database);
		return NULL;
	}

	if(db_param && db_param->journal_mode.s) {
		sqlite3_stmt *stmt;
		char query[32];
		snprintf(query, 32, "PRAGMA journal_mode=%s", db_param->journal_mode.s);
		int rc = sqlite3_prepare_v2(
				con->conn, query, strlen(query), &stmt, NULL);
		if(rc != SQLITE_OK) {
			LM_ERR("error prepare query [%s]\n", sqlite3_errmsg(con->conn));
		}
		while(1) {
			rc = sqlite3_step(stmt);
			if(rc == SQLITE_DONE) {
				break;
			} else if(rc != SQLITE_ROW) {
				LM_ERR("sqlite3_step[%s]\n", sqlite3_errmsg(con->conn));
				pkg_free(con);
				return NULL;
			} else {
				rc = sqlite3_column_count(stmt);
				LM_DBG("columns in result: %d\n", rc);
			}
		}
		if(stmt) {
			if(sqlite3_finalize(stmt) != SQLITE_OK) {
				LM_ERR("sqlite3_finalize[%s]\n", sqlite3_errmsg(con->conn));
			}
		}
		LM_DBG("[%s]\n", query);
	}
	return con;
}

db1_con_t *db_sqlite_init(const str *_url)
{
	return db_do_init(_url, (void *)db_sqlite_new_connection);
}


/*
 * Shut down database module
 * No function should be called after this
 */

static void db_sqlite_free_connection(struct sqlite_connection *con)
{
	if(!con)
		return;

	sqlite3_close(con->conn);
	free_db_id(con->hdr.id);
	pkg_free(con);
}

void db_sqlite_close(db1_con_t *_h)
{
	db_do_close(_h, db_sqlite_free_connection);
}

/*
 * Release a result set from memory
 */
int db_sqlite_free_result(db1_con_t *_h, db1_res_t *_r)
{
	if(!_h || !_r) {
		LM_ERR("invalid parameter value\n");
		return -1;
	}

	if(db_free_result(_r) < 0) {
		LM_ERR("failed to free result structure\n");
		return -1;
	}
	return 0;
}

/*
 * Store name of table that will be used by
 * subsequent database functions
 */
int db_sqlite_use_table(db1_con_t *_h, const str *_t)
{
	return db_use_table(_h, _t);
}

/*
 * Reset query context
 */
static void db_sqlite_cleanup_query(const db1_con_t *_c)
{
	struct sqlite_connection *conn = CON_SQLITE(_c);
	int rc;

	if(conn->stmt != NULL) {
		rc = sqlite3_finalize(conn->stmt);
		if(rc != SQLITE_OK)
			LM_ERR("finalize failed: %s\n", sqlite3_errmsg(conn->conn));
	}

	conn->stmt = NULL;
	conn->bindpos = 0;
}

/*
 * Convert value to sql-string as db bind index
 */
static int db_sqlite_val2str(
		const db1_con_t *_c, const db_val_t *_v, char *_s, int *_len)
{
	struct sqlite_connection *conn;
	int ret;

	if(!_c || !_v || !_s || !_len || *_len <= 0) {
		LM_ERR("invalid parameter value\n");
		return -1;
	}

	conn = CON_SQLITE(_c);
	if(conn->bindpos >= DB_SQLITE_MAX_BINDS) {
		LM_ERR("too many bindings, recompile with larger "
			   "DB_SQLITE_MAX_BINDS\n");
		return -2;
	}

	conn->bindarg[conn->bindpos] = _v;
	ret = snprintf(_s, *_len, "?%u", ++conn->bindpos);
	if((unsigned)ret >= (unsigned)*_len)
		return -11;

	*_len = ret;
	return 0;
}

/*
 * Send an SQL query to the server
 */
static int db_sqlite_submit_query(const db1_con_t *_h, const str *_s)
{
	struct sqlite_connection *conn = CON_SQLITE(_h);
	sqlite3_stmt *stmt;
	const db_val_t *val;
	int rc, i;

	LM_DBG("submit_query: %.*s\n", _s->len, _s->s);

	rc = sqlite3_prepare_v2(conn->conn, _s->s, _s->len, &stmt, NULL);
	if(rc != SQLITE_OK) {
		LM_ERR("failed to prepare statement: %s\n", sqlite3_errmsg(conn->conn));
		return -1;
	}
	conn->stmt = stmt;

	for(i = 1; i <= conn->bindpos; i++) {
		val = conn->bindarg[i - 1];
		if(VAL_NULL(val)) {
			rc = sqlite3_bind_null(stmt, i);
		} else
			switch(VAL_TYPE(val)) {
				case DB1_INT:
					rc = sqlite3_bind_int(stmt, i, VAL_INT(val));
					break;
				case DB1_BIGINT:
					rc = sqlite3_bind_int64(stmt, i, VAL_BIGINT(val));
					break;
				case DB1_DOUBLE:
					rc = sqlite3_bind_double(stmt, i, VAL_DOUBLE(val));
					break;
				case DB1_STRING:
					rc = sqlite3_bind_text(stmt, i, VAL_STRING(val), -1, NULL);
					break;
				case DB1_STR:
					rc = sqlite3_bind_text(
							stmt, i, VAL_STR(val).s, VAL_STR(val).len, NULL);
					break;
				case DB1_DATETIME:
					rc = sqlite3_bind_double(
							stmt, i, timet_to_sqlite(VAL_TIME(val)));
					break;
				case DB1_BLOB:
					rc = sqlite3_bind_blob(
							stmt, i, VAL_BLOB(val).s, VAL_BLOB(val).len, NULL);
					break;
				case DB1_BITMAP:
					rc = sqlite3_bind_int(stmt, i, VAL_BITMAP(val));
					break;
				default:
					LM_ERR("unknown bind value type %d\n", VAL_TYPE(val));
					return -1;
			}
		if(rc != SQLITE_OK) {
			LM_ERR("Parameter bind failed: %s\n", sqlite3_errmsg(conn->conn));
			return -1;
		}
	}

	return 0;
}

#define H3(a, b, c) ((a << 16) + (b << 8) + c)
#define H4(a, b, c, d) ((a << 24) + (b << 16) + (c << 8) + d)

static int decltype_to_dbtype(const char *decltype)
{
	/* SQlite3 has dynamic typing. It does not store the actual
	 * exact type, instead it uses 'affinity' depending on the
	 * value. We have to go through the declaration type to see
	 * what to return.
	 * The loose string matching (4 letter substring match) is what
	 * SQlite does internally, but our return values differ as we want
	 * the more exact srdb type instead of the affinity. */

	uint32_t h = 0;

	for(; *decltype; decltype ++) {
		h <<= 8;
		h += toupper(*decltype);

		switch(h & 0x00ffffff) {
			case H3('I', 'N', 'T'):
				return DB1_INT;
		}

		switch(h) {
			case H4('S', 'E', 'R', 'I'): /* SERIAL */
				return DB1_INT;
			case H4('B', 'I', 'G', 'I'): /* BIGINT */
				return DB1_BIGINT;
			case H4('C', 'H', 'A', 'R'):
			case H4('C', 'L', 'O', 'B'):
				return DB1_STRING;
			case H4('T', 'E', 'X', 'T'):
				return DB1_STR;
			case H4('R', 'E', 'A', 'L'):
			case H4('F', 'L', 'O', 'A'): /* FLOAT */
			case H4('D', 'O', 'U', 'B'): /* DOUBLE */
				return DB1_DOUBLE;
			case H4('B', 'L', 'O', 'B'):
				return DB1_BLOB;
			case H4('T', 'I', 'M', 'E'):
			case H4('D', 'A', 'T', 'E'):
				return DB1_DATETIME;
		}
	}

	LM_ERR("sqlite decltype '%s' not recognized, defaulting to int", decltype);
	return DB1_INT;
}

static int type_to_dbtype(int type)
{
	switch(type) {
		case SQLITE_INTEGER:
			return DB1_INT;
		case SQLITE_FLOAT:
			return DB1_DOUBLE;
		case SQLITE_TEXT:
			return DB1_STR;
		case SQLITE_BLOB:
			return DB1_BLOB;
		default:
			/* Unknown, or NULL column value. Assume this is a
		 * string. */
			return DB1_STR;
	}
}

static str *str_dup(const char *_s)
{
	str *s;
	int len = strlen(_s);

	s = (str *)pkg_malloc(sizeof(str) + len + 1);
	if(!s)
		return NULL;

	s->len = len;
	s->s = ((char *)s) + sizeof(str);
	memcpy(s->s, _s, len);
	s->s[len] = '\0';

	return s;
}

static void str_assign(str *s, const char *_s, int len)
{
	s->s = (char *)pkg_malloc(len + 1);
	if(s->s) {
		s->len = len;
		memcpy(s->s, _s, len);
		s->s[len] = 0;
	}
}

/*
 * Read database answer and fill the structure
 */
int db_sqlite_store_result(const db1_con_t *_h, db1_res_t **_r)
{
	struct sqlite_connection *conn = CON_SQLITE(_h);
	db1_res_t *res;
	int i, rc, num_rows = 0, num_alloc = 0;
	db_row_t *rows = NULL, *row;
	db_val_t *val;

	res = db_new_result();
	if(res == NULL)
		goto no_mem;

	while(1) {
		rc = sqlite3_step(conn->stmt);
		if(rc == SQLITE_DONE) {
			*_r = res;
			return 0;
		}
		if(rc != SQLITE_ROW) {
			LM_INFO("sqlite3_step failed: %s\n", sqlite3_errmsg(conn->conn));
			goto err;
		}
		if(num_rows == 0) {
			/* get column types */
			rc = sqlite3_column_count(conn->stmt);
			if(db_allocate_columns(res, rc) != 0)
				goto err;
			RES_COL_N(res) = rc;

			for(i = 0; i < RES_COL_N(res); i++) {
				const char *decltype;
				int dbtype;

				RES_NAMES(res)[i] = str_dup(sqlite3_column_name(conn->stmt, i));
				if(RES_NAMES(res)[i] == NULL)
					goto no_mem;
				decltype = sqlite3_column_decltype(conn->stmt, i);
				if(decltype != NULL)
					dbtype = decltype_to_dbtype(decltype);
				else
					dbtype = type_to_dbtype(sqlite3_column_type(conn->stmt, i));
				RES_TYPES(res)[i] = dbtype;
			}
		}
		if(num_rows >= num_alloc) {
			if(num_alloc)
				num_alloc *= 2;
			else
				num_alloc = 8;
			rows = pkg_reallocxf(rows, sizeof(db_row_t) * num_alloc);
			if(rows == NULL)
				goto no_mem;
			RES_ROWS(res) = rows;
		}

		row = &RES_ROWS(res)[num_rows];
		num_rows++;
		RES_ROW_N(res) = num_rows;	  /* rows in this result set */
		RES_NUM_ROWS(res) = num_rows; /* rows in total */

		if(db_allocate_row(res, row) != 0)
			goto no_mem;

		for(i = 0, val = ROW_VALUES(row); i < RES_COL_N(res); i++, val++) {
			VAL_TYPE(val) = RES_TYPES(res)[i];
			VAL_NULL(val) = 0;
			VAL_FREE(val) = 0;
			if(sqlite3_column_type(conn->stmt, i) == SQLITE_NULL) {
				VAL_NULL(val) = 1;
			} else
				switch(VAL_TYPE(val)) {
					case DB1_INT:
						VAL_INT(val) = sqlite3_column_int(conn->stmt, i);
						break;
					case DB1_BIGINT:
						VAL_BIGINT(val) = sqlite3_column_int64(conn->stmt, i);
						break;
					case DB1_STRING:
						/* first field of struct str* is the char* so we can just
				 * do whatever DB1_STR case does */
					case DB1_STR:
						str_assign(&VAL_STR(val),
								(const char *)sqlite3_column_text(
										conn->stmt, i),
								sqlite3_column_bytes(conn->stmt, i));
						if(!VAL_STR(val).s)
							goto no_mem;
						VAL_FREE(val) = 1;
						break;
					case DB1_DOUBLE:
						VAL_DOUBLE(val) = sqlite3_column_double(conn->stmt, i);
						break;
					case DB1_DATETIME:
						VAL_TIME(val) = sqlite_to_timet(
								sqlite3_column_double(conn->stmt, i));
						break;
					case DB1_BLOB:
						str_assign(&VAL_BLOB(val),
								(const char *)sqlite3_column_blob(
										conn->stmt, i),
								sqlite3_column_bytes(conn->stmt, i));
						if(!VAL_STR(val).s)
							goto no_mem;
						VAL_FREE(val) = 1;
						break;
					default:
						LM_ERR("unhandled db-type\n");
						goto err;
				}
		}
	}

no_mem:
	LM_ERR("no private memory left\n");
err:
	if(res)
		db_free_result(res);
	return -1;
}

/*
 * Query table for specified rows
 * _h: structure representing database connection
 * _k: key names
 * _op: operators
 * _v: values of the keys that must match
 * _c: column names to return
 * _n: number of key=values pairs to compare
 * _nc: number of columns to return
 * _o: order by the specified column
 */
int db_sqlite_query(const db1_con_t *_h, const db_key_t *_k, const db_op_t *_op,
		const db_val_t *_v, const db_key_t *_c, int _n, int _nc,
		const db_key_t _o, db1_res_t **_r)
{
	int rc;

	rc = db_do_query(_h, _k, _op, _v, _c, _n, _nc, _o, _r, db_sqlite_val2str,
			db_sqlite_submit_query, db_sqlite_store_result);
	db_sqlite_cleanup_query(_h);

	return rc;
}

static int db_sqlite_commit(const db1_con_t *_h)
{
	struct sqlite_connection *conn = CON_SQLITE(_h);
	int rc;

	rc = sqlite3_step(conn->stmt);
	if(rc != SQLITE_DONE && rc != SQLITE_OK) {
		LM_ERR("sqlite commit failed: %s\n", sqlite3_errmsg(conn->conn));
		return -1;
	}

	return 0;
}

/*
 * Insert a row into specified table
 * _h: structure representing database connection
 * _k: key names
 * _v: values of the keys
 * _n: number of key=value pairs
 */
int db_sqlite_insert(
		const db1_con_t *_h, const db_key_t *_k, const db_val_t *_v, int _n)
{
	int rc = -1;

	rc = db_do_insert(
			_h, _k, _v, _n, db_sqlite_val2str, db_sqlite_submit_query);
	if(rc == 0)
		rc = db_sqlite_commit(_h);
	db_sqlite_cleanup_query(_h);

	return rc;
}


/*
 * Delete a row from the specified table
 * _h: structure representing database connection
 * _k: key names
 * _o: operators
 * _v: values of the keys that must match
 * _n: number of key=value pairs
 */
int db_sqlite_delete(const db1_con_t *_h, const db_key_t *_k, const db_op_t *_o,
		const db_val_t *_v, int _n)
{
	int rc;

	rc = db_do_delete(
			_h, _k, _o, _v, _n, db_sqlite_val2str, db_sqlite_submit_query);
	if(rc == 0)
		rc = db_sqlite_commit(_h);
	db_sqlite_cleanup_query(_h);

	return rc;
}


/*
 * Update some rows in the specified table
 * _h: structure representing database connection
 * _k: key names
 * _o: operators
 * _v: values of the keys that must match
 * _uk: updated columns
 * _uv: updated values of the columns
 * _n: number of key=value pairs
 * _un: number of columns to update
 */
int db_sqlite_update(const db1_con_t *_h, const db_key_t *_k, const db_op_t *_o,
		const db_val_t *_v, const db_key_t *_uk, const db_val_t *_uv, int _n,
		int _un)
{
	int rc;

	rc = db_do_update(_h, _k, _o, _v, _uk, _uv, _n, _un, db_sqlite_val2str,
			db_sqlite_submit_query);
	if(rc == 0)
		rc = db_sqlite_commit(_h);
	db_sqlite_cleanup_query(_h);

	return rc;
}

int db_sqlite_raw_query(const db1_con_t *_h, const str *_s, db1_res_t **_r)
{
	int rc;

	rc = db_do_raw_query(
			_h, _s, _r, db_sqlite_submit_query, db_sqlite_store_result);
	db_sqlite_cleanup_query(_h);

	return rc;
}
