/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/***************************************************************************
 *            kolab-util-sqlite.c
 *
 *  Fri May 20 10:13:25 2011
 *  Copyright  2011  Christian Hilberg
 *  <hilberg@kernelconcepts.de>
 ****************************************************************************/

/*
 * 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., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */

/*----------------------------------------------------------------------------*/

#include <config.h>

#include <glib/gi18n-lib.h>

#include "kolab-util-error.h"
#include "kolab-util-sqlite.h"

/*----------------------------------------------------------------------------*/

#define KOLAB_UTIL_SQLITE_DB_MASTER "sqlite_master"

/*----------------------------------------------------------------------------*/

static gint
kolab_util_sqlite_table_exists_cb (void *data,
                                   gint ncols,
                                   gchar **coltext,
                                   gchar **colname)
{
	KolabUtilSqliteDb *kdb = (KolabUtilSqliteDb *)data;

	(void)ncols;
	(void)coltext;
	(void)colname;

	kdb->ctr++;

	return SQLITE_OK;
}

static sqlite3*
kolab_util_sqlite_open_trycreate (const gchar *path,
                                  GError **err)
{
	gint sql3_err = SQLITE_OK;
	sqlite3 *db = NULL;

	g_assert (path != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	sql3_err = sqlite3_open_v2 (path,
	                            &db,
	                            SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
	                            NULL /* default sqlite3 vfs module */);

	if (sql3_err != SQLITE_OK) {
		g_set_error (err,
		             KOLAB_UTIL_ERROR,
		             KOLAB_UTIL_ERROR_SQLITE_DB,
		             _("SQLite Error: %s"),
		             sqlite3_errmsg (db));
		(void)sqlite3_close (db);
		return NULL;
	}

	return db;
}

static gboolean
kolab_util_sqlite_exec_str_full (KolabUtilSqliteDb *kdb,
                                 const gchar *sql_str,
                                 int (*cb_func)(void*,int,char**,char**),
                                 void *cb_arg,
                                 GError **err)
{
	gint sql_errno = SQLITE_OK;

	g_assert (kdb != NULL);
	g_assert (kdb->db != NULL);
	g_assert (sql_str != NULL);
	/* cb_func may be NULL */
	/* cb_arg may be NULL  */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	sql_errno = sqlite3_exec (kdb->db, sql_str, cb_func, cb_arg, NULL);

	/* the SQLite3-Docs state that SQLITE_NOTFOUND is an unused
	 * error code. Since the WHERE clause should return an empty
	 * match for non-existent rows, DELETE should just take no action
	 * and return successfully, if a name was not found in the
	 * table
	 */
#if 0
	if (sql_errno == SQLITE_NOTFOUND)
		return TRUE;
#endif

	if (sql_errno != SQLITE_OK) {
		g_set_error (err,
		             KOLAB_UTIL_ERROR,
		             KOLAB_UTIL_ERROR_SQLITE_DB,
		             _("SQLite Error: %s"),
		             sqlite3_errmsg (kdb->db));
		return FALSE;
	}
	return TRUE;
}

/*----------------------------------------------------------------------------*/

KolabUtilSqliteDb*
kolab_util_sqlite_db_new (void)
{
	KolabUtilSqliteDb *kdb = NULL;
	kdb = g_new0 (KolabUtilSqliteDb, 1);
	kdb->db   = NULL;
	kdb->path = NULL;
	kdb->ctr  = 0;
	return kdb;
}

gboolean
kolab_util_sqlite_db_free (KolabUtilSqliteDb *kdb,
                           GError **err)
{
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	if (kdb == NULL)
		return TRUE;

	ok = kolab_util_sqlite_db_close (kdb, &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	if (kdb->path != NULL) {
		g_free (kdb->path);
		kdb->path = NULL;
	}
	g_free (kdb);
	return TRUE;
}

gboolean
kolab_util_sqlite_db_open (KolabUtilSqliteDb *kdb,
                           const gchar *dbpath,
                           const gchar *filename,
                           GError **err)
{
	gchar   *store_db_path = NULL;
	sqlite3	*tmp_db = NULL;
	GError *tmp_err = NULL;
	gboolean ok = TRUE;

	g_assert (kdb != NULL);
	g_assert (dbpath != NULL);
	g_assert (filename != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	if (kdb->db != NULL)
		return TRUE;

	/* inspired by construct() from camel-store.c */

	store_db_path = g_build_filename (dbpath, filename, NULL);
	if (store_db_path == NULL) {
		g_set_error (err,
		             KOLAB_UTIL_ERROR,
		             KOLAB_UTIL_ERROR_SQLITE_DB,
		             _("Could not create full DB path for database '%s' filename '%s'"),
		             dbpath, filename);
		return FALSE;
	}

	tmp_db = kolab_util_sqlite_open_trycreate (store_db_path, &tmp_err);
	if (tmp_db == NULL) {
		g_propagate_error (err, tmp_err);
		g_free (store_db_path);
		return FALSE;
	}

	kdb->db = tmp_db;
	kdb->path = store_db_path;
	kdb->ctr = 0;

	/* SQLiteDB speedup (issue warnings if pragmas cannot be set) */
	ok = kolab_util_sqlite_exec_str (kdb,
	                                 "PRAGMA synchronous = OFF",
	                                 &tmp_err);
	if (! ok) {
		g_warning ("%s: %s", __func__, tmp_err->message);
		g_error_free (tmp_err);
		tmp_err = NULL;
	}
	ok = kolab_util_sqlite_exec_str (kdb,
	                                 "PRAGMA journal_mode = MEMORY",
	                                 &tmp_err);
	if (! ok) {
		g_warning ("%s: %s", __func__, tmp_err->message);
		g_error_free (tmp_err);
		tmp_err = NULL;
	}

	return TRUE;
}

gboolean
kolab_util_sqlite_db_close (KolabUtilSqliteDb *kdb,
                            GError **err)
{
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	if (kdb == NULL)
		return TRUE;

	if (kdb->db != NULL) {
		if (sqlite3_close (kdb->db) != SQLITE_OK) {
			g_set_error (err,
			             KOLAB_UTIL_ERROR,
			             KOLAB_UTIL_ERROR_SQLITE_DB,
			             _("SQLite Error: %s"),
			             sqlite3_errmsg (kdb->db));
			return FALSE;
		}
		kdb->db = NULL;
	}

	return TRUE;
}

gboolean
kolab_util_sqlite_table_exists (KolabUtilSqliteDb *kdb,
                                const gchar *name,
                                GError **err)
{
	gchar* sql_str = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (kdb != NULL);
	g_assert (kdb->db != NULL);
	g_assert (name != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	/* SELECT name FROM sqlite_master WHERE type='table' AND name='table_name'; */
	sql_str = sqlite3_mprintf ("SELECT name FROM %Q WHERE type='table' AND name=%Q;",
	                           KOLAB_UTIL_SQLITE_DB_MASTER,
	                           name);
	kdb->ctr = 0;
	ok = kolab_util_sqlite_exec_str_full (kdb,
	                                      sql_str,
	                                      kolab_util_sqlite_table_exists_cb,
	                                      kdb,
	                                      &tmp_err);
	sqlite3_free (sql_str);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	if (kdb->ctr > 1) {
		/* each table must exist only once (or not at all) */
		g_set_error (err,
		             KOLAB_UTIL_ERROR,
		             KOLAB_UTIL_ERROR_SQLITE_DB,
		             _("SQLite Error: Multiple tables named '%s', corrupted database '%s'"),
		             name, kdb->path);
		return FALSE;
	}

	if (kdb->ctr == 0)
		return FALSE;

	return TRUE;
}

gboolean
kolab_util_sqlite_table_drop (KolabUtilSqliteDb *kdb,
                              const gchar *name,
                              GError **err)
{
	gchar* sql_str = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (kdb != NULL);
	g_assert (kdb->db != NULL);
	g_assert (name != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	/* DROP TABLE IF EXISTS */
	sql_str = sqlite3_mprintf ("DROP TABLE IF EXISTS %Q;", name);
	ok = kolab_util_sqlite_exec_str (kdb, sql_str, &tmp_err);
	sqlite3_free (sql_str);

	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	return TRUE;
}

gint
kolab_util_sqlite_table_get_rowcount (KolabUtilSqliteDb *kdb,
                                      const gchar *name,
                                      GError **err)
{
	gint nrows = 0;
	gchar *sql_str = NULL;
	gint sql_errno = SQLITE_OK;
	sqlite3_stmt *sql_stmt = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (kdb != NULL);
	g_assert (kdb->db != NULL);
	g_assert (name != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	/* get the number of rows in table */
	sql_str = sqlite3_mprintf ("SELECT COUNT(*) FROM %Q;", name);
	ok = kolab_util_sqlite_prep_stmt (kdb, &sql_stmt, sql_str, &tmp_err);
	sqlite3_free (sql_str);
	if (! ok) {
		(void)kolab_util_sqlite_fnlz_stmt (kdb, sql_stmt, NULL);
		g_propagate_error (err, tmp_err);
		return -1;
	}

	/* get row count from db */
	sql_errno = sqlite3_step (sql_stmt);
	if (sql_errno != SQLITE_ROW) {
		g_set_error (err,
		             KOLAB_UTIL_ERROR,
		             KOLAB_UTIL_ERROR_SQLITE_DB,
		             _("SQLite Error: %s"),
		             sqlite3_errmsg (kdb->db));
		(void)kolab_util_sqlite_fnlz_stmt (kdb, sql_stmt, NULL);
		return -1;
	}

	nrows = sqlite3_column_int (sql_stmt, 0);
	ok = kolab_util_sqlite_fnlz_stmt (kdb, sql_stmt, &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return -1;
	}

	return nrows;
}

gboolean
kolab_util_sqlite_exec_str (KolabUtilSqliteDb *kdb,
                            const gchar *sql_str,
                            GError **err)
{
	gboolean ok = FALSE;
	ok = kolab_util_sqlite_exec_str_full (kdb, sql_str, NULL, NULL, err);
	return ok;
}

gboolean
kolab_util_sqlite_prep_stmt (KolabUtilSqliteDb *kdb,
                             sqlite3_stmt **sql_stmt,
                             const gchar *sql_str,
                             GError **err)
{
	gint sql_errno = SQLITE_OK;

	g_assert (kdb != NULL);
	g_assert (kdb->db != NULL);
	g_assert (*sql_stmt == NULL);
	g_assert (sql_str != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	sql_errno = sqlite3_prepare_v2 (kdb->db, sql_str, -1, sql_stmt, NULL);

	if (sql_errno != SQLITE_OK) {
		g_set_error (err,
		             KOLAB_UTIL_ERROR,
		             KOLAB_UTIL_ERROR_SQLITE_DB,
		             _("SQLite Error: %s"),
		             sqlite3_errmsg (kdb->db));
		return FALSE;
	}
	return TRUE;
}

gboolean
kolab_util_sqlite_fnlz_stmt (KolabUtilSqliteDb *kdb,
                             sqlite3_stmt *sql_stmt,
                             GError **err)
{
	gint sql_errno = SQLITE_OK;

	g_assert (kdb != NULL);
	g_assert (kdb->db != NULL);
	/* sql_stmt may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	sql_errno = sqlite3_finalize (sql_stmt);

	if (sql_errno != SQLITE_OK) {
		g_set_error (err,
		             KOLAB_UTIL_ERROR,
		             KOLAB_UTIL_ERROR_SQLITE_DB,
		             _("SQLite Error: %s"),
		             sqlite3_errmsg (kdb->db));
		return FALSE;
	}
	return TRUE;
}

gboolean
kolab_util_sqlite_transaction_start (KolabUtilSqliteDb *kdb,
                                     GError **err)
{
	GError *tmp_err = NULL;
	gboolean ok = TRUE;

	g_assert (kdb != NULL);
	g_assert (kdb->db != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	ok = kolab_util_sqlite_exec_str (kdb, "BEGIN TRANSACTION", &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	return TRUE;
}

gboolean
kolab_util_sqlite_transaction_commit (KolabUtilSqliteDb *kdb,
                                      GError **err)
{
	GError *tmp_err = NULL;
	gboolean ok = TRUE;

	g_assert (kdb != NULL);
	g_assert (kdb->db != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	ok = kolab_util_sqlite_exec_str (kdb, "COMMIT TRANSACTION", &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	return TRUE;
}

gboolean
kolab_util_sqlite_transaction_abort (KolabUtilSqliteDb *kdb,
                                     GError **err)
{
	GError *tmp_err = NULL;
	gboolean ok = TRUE;

	g_assert (kdb != NULL);
	g_assert (kdb->db != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	ok = kolab_util_sqlite_exec_str (kdb, "ROLLBACK TRANSACTION", &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	return TRUE;
}

/*----------------------------------------------------------------------------*/
