/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/***************************************************************************
 *            camel-kolab-imapx-metadata-db.c
 *
 *  Mon Oct 11 12:37:05 2010
 *  Copyright  2010  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 main.c; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */

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

/* TODO There is code duplication regarding SQLite in KolabImapxMetadataDb,
 *	KolabMailInfoDb and KolabMailSideCache.
 *
 *	libekolabutil/kolab-util-sqlite.[hc] has the needed functions,
 *	which can be used here instead of the local dupes.
 *
 *	A generalized approach could be to use some sort of persistent
 *	GHashTable, which would be mapped transparently onto a SQLite DB
 *	(or an object oriented DB right away), with some constraints
 *	applying to the hash table keys and values (as with marshalling
 *	of serializable objects). A GPersistentHashTable could be nested,
 *	binary data stored as GByteArray values and basic data types carry
 *	with them some type annotations (maybe through GType)
 */

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

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <glib/gi18n-lib.h>

/* Kolab error reporting */
#include <libekolabutil/kolab-util-error.h>

#include <camel/providers/imapx/camel-imapx-utils.h>
#include <camel/providers/imapx/camel-imapx-metadata.h>

#include "camel-kolab-imapx-folder.h"
#include "camel-kolab-imapx-metadata.h"
#include "camel-kolab-imapx-metadata-db.h"

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

#define CAMEL_KOLAB_IMAPX_METADATA_DB_FILE "folders_metadata.db"
#define CAMEL_KOLAB_IMAPX_SQLITE_DB_MASTER "sqlite_master"
#define CAMEL_KOLAB_IMAPX_SQLITE_DB_TBL_FOLDERS "folders"

#define CAMEL_KOLAB_IMAPX_SQLITE_COL_FOLDERNAME "folder_name"
#define CAMEL_KOLAB_IMAPX_SQLITE_COL_FOLDERTYPE "folder_type"


typedef enum {
	CAMEL_KOLAB_IMAPX_METADATA_TABLE_TYPE_PATHS,
	CAMEL_KOLAB_IMAPX_METADATA_TABLE_TYPE_ENTRIES
} CamelKolabImapxMetadataTableType;

enum {
	CAMEL_KOLAB_IMAPX_METADATA_TABLE_COL_FOLDERNAME = 0,
	CAMEL_KOLAB_IMAPX_METADATA_TABLE_COL_FOLDERTYPE,
	CAMEL_KOLAB_IMAPX_METADATA_TABLE_LAST_COL
};

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

static sqlite3*
camel_kolab_imapx_metadata_db_open_trycreate (const gchar *path)
{
	gint sql3_err = SQLITE_OK;
	sqlite3 *db = NULL;

	g_assert (path != 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_warning ("%s: %s", __func__, sqlite3_errmsg(db));
		(void)sqlite3_close (db);
		return NULL;
	}

	return db;
}

static gint
camel_kolab_imapx_metadata_db_table_exists_cb (void *data,
                                               gint ncols,
                                               gchar **coltext,
                                               gchar **colname)
{
	CamelKolabImapxMetadataDb *mdb = (CamelKolabImapxMetadataDb *)data;

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

	mdb->ctr++;

	return SQLITE_OK;
}

static gboolean
camel_kolab_imapx_metadata_db_table_exists (CamelKolabImapxMetadataDb *mdb,
                                            const gchar *name,
                                            gboolean *exists,
                                            GError **err)
{
	gchar *sql_str = NULL;
	gint sql_errno = SQLITE_OK;

	g_assert (mdb != NULL);
	g_assert (mdb->db != NULL);
	g_assert (name != NULL);
	g_assert (exists != 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;",
	                           CAMEL_KOLAB_IMAPX_SQLITE_DB_MASTER,
	                           name);
	mdb->ctr = 0;
	sql_errno = sqlite3_exec (mdb->db,
	                          sql_str,
	                          camel_kolab_imapx_metadata_db_table_exists_cb,
	                          (void*)mdb,
	                          NULL);
	sqlite3_free (sql_str);

	if (sql_errno != SQLITE_OK) {
		g_set_error (err,
		             KOLAB_CAMEL_KOLAB_ERROR,
		             KOLAB_CAMEL_KOLAB_ERROR_DB,
		             _("SQLite Error: %s"),
		             sqlite3_errmsg (mdb->db));
		return FALSE;
	}

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

	if (mdb->ctr == 0)
		*exists = FALSE;
	else
		*exists = TRUE;

	return TRUE;
}

static gboolean
camel_kolab_imapx_metadata_db_table_create (CamelKolabImapxMetadataDb *mdb,
                                            const gchar *name,
                                            GError **err)
{
	gchar* sql_str = NULL;
	gint sql_errno = SQLITE_OK;

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

	/* primary key (1st column) is folder names,
	 * rest of the row maps to CamelKolabImapxFolderMetadata */
	sql_str = sqlite3_mprintf ("CREATE TABLE IF NOT EXISTS %Q ( %Q TEXT PRIMARY KEY, %Q INTEGER );",
	                           name,
	                           CAMEL_KOLAB_IMAPX_SQLITE_COL_FOLDERNAME,
	                           CAMEL_KOLAB_IMAPX_SQLITE_COL_FOLDERTYPE);

	sql_errno = sqlite3_exec (mdb->db, sql_str, NULL, NULL, NULL);
	sqlite3_free (sql_str);

	if (sql_errno != SQLITE_OK) {
		g_set_error (err,
		             KOLAB_CAMEL_KOLAB_ERROR,
		             KOLAB_CAMEL_KOLAB_ERROR_DB,
		             _("SQLite Error: %s"),
		             sqlite3_errmsg (mdb->db));
		return FALSE;
	}

	return TRUE;
}

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

CamelKolabImapxMetadataDb*
camel_kolab_imapx_metadata_db_new (void)
{
	CamelKolabImapxMetadataDb *mdb = NULL;
	mdb = g_new0 (CamelKolabImapxMetadataDb, 1);
	mdb->db   = NULL;
	mdb->path = NULL;
	mdb->ctr  = 0;
	return mdb;
}

gboolean
camel_kolab_imapx_metadata_db_free (CamelKolabImapxMetadataDb *mdb,
                                    GError **err)
{
	return camel_kolab_imapx_metadata_db_close (mdb, err);
}


gboolean
camel_kolab_imapx_metadata_db_open (CamelKolabImapxMetadataDb *mdb,
                                    const gchar *cachepath,
                                    GError **err)
{
	sqlite3	*tmp_db = NULL;
	gchar *filename = NULL;

	g_assert (mdb != NULL);
	g_assert (cachepath != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

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

	filename = g_build_filename (cachepath,
	                             CAMEL_KOLAB_IMAPX_METADATA_DB_FILE,
	                             NULL);

	tmp_db = camel_kolab_imapx_metadata_db_open_trycreate (filename);

	if (tmp_db == NULL) {
		g_set_error (err,
		             KOLAB_CAMEL_KOLAB_ERROR,
		             KOLAB_CAMEL_KOLAB_ERROR_DB,
		             _("SQLite Error: could not open/create SQLite database '%s'"),
		             filename);
		g_free (filename);
		return FALSE;
	}

	mdb->db = tmp_db;
	mdb->path = filename;
	mdb->ctr = 0;

	return TRUE;
}

gboolean
camel_kolab_imapx_metadata_db_init (CamelKolabImapxMetadataDb *mdb,
                                    GError **err)
{
	GError *tmp_err = NULL;
	gboolean sql_ok = SQLITE_OK;
	gboolean exists = FALSE;

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

	/* make sure paths table is there */
	sql_ok = camel_kolab_imapx_metadata_db_table_exists (mdb,
	                                                     CAMEL_KOLAB_IMAPX_SQLITE_DB_TBL_FOLDERS,
	                                                     &exists,
	                                                     &tmp_err);
	if (!sql_ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	if (!exists)
		sql_ok = camel_kolab_imapx_metadata_db_table_create (mdb,
		                                                     CAMEL_KOLAB_IMAPX_SQLITE_DB_TBL_FOLDERS,
		                                                     &tmp_err);
	if (!sql_ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	return TRUE;
}

gboolean
camel_kolab_imapx_metadata_db_close (CamelKolabImapxMetadataDb *mdb,
                                     GError **err)
{
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	if (mdb == NULL)
		return TRUE;

	if (mdb->db) {
		if (sqlite3_close (mdb->db) != SQLITE_OK) {
			g_set_error (err,
			             KOLAB_CAMEL_KOLAB_ERROR,
			             KOLAB_CAMEL_KOLAB_ERROR_DB,
			             _("SQLite Error: %s"),
			             sqlite3_errmsg (mdb->db));
			return FALSE;
		}
	}

	if (mdb->path)
		g_free (mdb->path);

	g_free (mdb);
	return TRUE;
}

gboolean
camel_kolab_imapx_metadata_db_folder_update (CamelKolabImapxMetadataDb *mdb,
                                             const gchar *foldername,
                                             const CamelKolabImapxFolderMetadata *kfmd,
                                             GError **err)
{
	gchar *sql_str = NULL;
	gint sql_errno = SQLITE_OK;

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

	sql_str = sqlite3_mprintf ("INSERT OR REPLACE INTO %Q VALUES ( %Q, %i );",
	                           CAMEL_KOLAB_IMAPX_SQLITE_DB_TBL_FOLDERS,
	                           foldername,
	                           kfmd->folder_type);

	sql_errno = sqlite3_exec (mdb->db, sql_str, NULL, NULL, NULL);
	sqlite3_free (sql_str);
	if (sql_errno != SQLITE_OK) {
		g_set_error (err,
		             KOLAB_CAMEL_KOLAB_ERROR,
		             KOLAB_CAMEL_KOLAB_ERROR_DB,
		             _("SQLite Error: %s"),
		             sqlite3_errmsg (mdb->db));
		return FALSE;
	}

	return TRUE;
}

gboolean
camel_kolab_imapx_metadata_db_update (CamelKolabImapxMetadataDb *mdb,
                                      GHashTable *kolab_metadata,
                                      GError **err)
{
	/* TODO extend this to true 'update' or
	 *	create a separate db cleansing function
	 */

	GError *tmp_err = NULL;
	gpointer kmd_key = NULL;
	gpointer kmd_value = NULL;
	gchar *folder_name = NULL;
	CamelKolabImapxFolderMetadata *kfmd = NULL;
	gboolean row_ok = FALSE;
	GHashTableIter kmd_iter;

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

	if (kolab_metadata == NULL)
		return TRUE;

	g_hash_table_iter_init (&kmd_iter, kolab_metadata);
	while (g_hash_table_iter_next (&kmd_iter, &kmd_key, &kmd_value)) {
		folder_name = (gchar*) kmd_key;
		kfmd = (CamelKolabImapxFolderMetadata *) kmd_value;
		row_ok = camel_kolab_imapx_metadata_db_folder_update (mdb,
		                                                      folder_name,
		                                                      kfmd,
		                                                      &tmp_err);
		if (!row_ok) {
			g_propagate_error (err, tmp_err);
			return FALSE;
		}
	}

	return TRUE;
}

CamelKolabImapxFolderMetadata*
camel_kolab_imapx_metadata_db_lookup (CamelKolabImapxMetadataDb *mdb,
                                      const gchar *foldername,
                                      GError **err)
{
	CamelKolabImapxFolderMetadata *kfmd = NULL;
	gchar *sql_str = NULL;
	gint sql_errno = SQLITE_OK;
	sqlite3_stmt *sql_stmt = NULL;

	g_assert (mdb != NULL);
	g_assert (mdb->db != NULL);
	g_assert (foldername != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	sql_str = sqlite3_mprintf ("SELECT * FROM %Q WHERE %q=%Q;",
	                           CAMEL_KOLAB_IMAPX_SQLITE_DB_TBL_FOLDERS,
	                           CAMEL_KOLAB_IMAPX_SQLITE_COL_FOLDERNAME,
	                           foldername);

	sql_errno = sqlite3_prepare_v2 (mdb->db, sql_str, -1, &sql_stmt, NULL);
	g_assert ((sql_errno == SQLITE_OK) && (sql_stmt != NULL));

	sql_errno = sqlite3_step (sql_stmt);
	if (sql_errno != SQLITE_ROW) {
		if (sql_errno != SQLITE_DONE) {
			g_set_error (err,
			             KOLAB_CAMEL_KOLAB_ERROR,
			             KOLAB_CAMEL_KOLAB_ERROR_DB,
			             _("SQLite Error: %s"),
			             sqlite3_errmsg (mdb->db));
		}
		sql_errno = sqlite3_finalize (sql_stmt);
		sqlite3_free (sql_str);
		return NULL;
	}

	kfmd = camel_kolab_imapx_folder_metadata_new ();
	kfmd->folder_type = sqlite3_column_int (sql_stmt,
	                                        CAMEL_KOLAB_IMAPX_METADATA_TABLE_COL_FOLDERTYPE);

	sql_errno = sqlite3_finalize (sql_stmt);
	sqlite3_free (sql_str);
	if (sql_errno != SQLITE_OK) {
		g_set_error (err,
		             KOLAB_CAMEL_KOLAB_ERROR,
		             KOLAB_CAMEL_KOLAB_ERROR_DB,
		             _("SQLite Error: %s"),
		             sqlite3_errmsg (mdb->db));
		camel_kolab_imapx_folder_metadata_free (kfmd);
		return NULL;
	}

	return kfmd;
}

gboolean
camel_kolab_imapx_metadata_db_remove_folder (CamelKolabImapxMetadataDb *mdb,
                                             const gchar *foldername,
                                             GError **err)
{
	gchar *sql_str = NULL;
	gint sql_errno = SQLITE_OK;

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

	sql_str = sqlite3_mprintf ("DELETE FROM %Q WHERE %q=%Q;",
	                           CAMEL_KOLAB_IMAPX_SQLITE_DB_TBL_FOLDERS,
	                           CAMEL_KOLAB_IMAPX_SQLITE_COL_FOLDERNAME,
	                           foldername);

	sql_errno = sqlite3_exec (mdb->db, sql_str, NULL, NULL, NULL);
	sqlite3_free (sql_str);

	/* 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 folder_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_CAMEL_KOLAB_ERROR,
		             KOLAB_CAMEL_KOLAB_ERROR_DB,
		             _("SQLite Error: %s"),
		             sqlite3_errmsg (mdb->db));
		return FALSE;
	}

	return TRUE;
}

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