/* ====================================================================
 * FTD4Linux Software License, Version 1.0
 *
 * Copyright (c) 2000-2004 The FTD4Linux Development Team.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        FTD4Linux Development Team (http://www.ftd4linux.2y.net/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "FTD" and "FTD4Linux" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact alguno@ftd4linux.2y.net.
 *
 * 5. Products derived from this software may not be called "FTD4Linux",
 *    nor may "FTD4Linux" appear in their name, without prior written
 *    permission of the FTD4Linux Development Team.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE FTD4LINUX DEVELOPMENT TEAM OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <glib-object.h>
#include <sqlite3.h>
#include "ftd4linux.h"
#include "ftd_sqlite.h"

typedef struct FTDSQLiteClass FTDSQLiteClass;

FTDSQLite *ftd_sqlite_get_object(void);
GType ftd_sqlite_get_type (void);

struct FTDSQLite
{
    GObject parent;

    sqlite3         *db;
    gboolean         transaction_active;
    GThread         *calling_thread;
};

struct FTDSQLiteClass
{
    GObjectClass parent;
};

enum
{
  BRING_TO_FRONT_SIGNAL,
  OPEN_POSTING_SIGNAL,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE(FTDSQLite, ftd_sqlite, G_TYPE_OBJECT)

static void
ftd_sqlite_init (FTDSQLite *obj)
{
    int retval;
    char *filename;

    filename = ftd_file_parse_cache_filename(EVENTS_FILE_SQLITE, FALSE);

    obj->calling_thread = g_thread_self();
    obj->transaction_active = FALSE;

    retval = sqlite3_open(filename, &obj->db);
    if (retval > 0) {
        g_error("ftd_sqlite_init(): Kan database '%s' niet openen: %s\n", filename, sqlite3_errmsg(obj->db));

        g_free(filename);
        sqlite3_close(obj->db);
    }

    g_free(filename);

    // Maak de FTDPosts tabel aan indien deze nog niet bestond (negeer fouten)
    ftd_sqlite_execute_query(obj,   "CREATE TABLE FTDPosts (ID integer primary key, Status varchar(5),"
                                    "FName varchar(64), Title varchar(128), Poster varchar(64), Link varchar(128),"
                                    "Stamp integer, Groups varchar(64), Cat smallint, SCat smallint,"
                                    "Updated integer, Views integer, Tags varchar(96), Tag varchar(16))", NULL, NULL, NULL);

    ftd_sqlite_execute_query(obj,   "CREATE INDEX statusindex ON FTDPosts (Status)", NULL, NULL, NULL);
    ftd_sqlite_execute_query(obj,   "CREATE INDEX tagindex ON FTDPosts (Tags)", NULL, NULL, NULL);
    ftd_sqlite_execute_query(obj,   "CREATE INDEX updatedindex ON FTDPosts (Updated)", NULL, NULL, NULL);

    // Stel sqlite zo in dat er geen synchronisatie wordt toegepast.
    // De mogelijke kans op data-verlies in geval van crashes nemen
    // we maar voor lief aangezien deze makkelijk weer hersteld kan
    // worden voor het geval het ooit mocht optreden.
    ftd_sqlite_execute_query(obj,   "PRAGMA synchronous=off", NULL, NULL, NULL);
}

static void
sqlite_finalize (GObject *obj)
{
    FTDSQLite *sqlite = FTD_SQLITE(obj);

    g_return_if_fail(sqlite->calling_thread == g_thread_self());

    sqlite3_close(sqlite->db);
}

static void
ftd_sqlite_class_init (FTDSQLiteClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

    gobject_class->finalize = sqlite_finalize;
}

FTDSQLite *
ftd_sqlite_new(void)
{
    return g_object_new(FTD_SQLITE_TYPE_OBJECT, NULL);
}

void
ftd_sqlite_destroy(FTDSQLite *obj)
{
    g_object_unref(obj);
}

gboolean
ftd_sqlite_execute_query(FTDSQLite *obj, const char *query, char **errmsg, gint64 *insert_id, int *rows_affected)
{
    FTDSQLite *sqlite = FTD_SQLITE(obj);

    g_return_if_fail(sqlite->calling_thread == g_thread_self());

    if (sqlite3_exec(sqlite->db, query, NULL, NULL, NULL) != SQLITE_OK) {
        if (errmsg) {
            *errmsg = g_strdup_printf("sqlite3_exec() failed with error code %i : %s\nquery = %s", sqlite3_errcode(sqlite->db), sqlite3_errmsg(sqlite->db), query);
        }

        if (insert_id) {
            *insert_id = -1;
        }

        if (rows_affected) {
            *rows_affected = -1;
        }

        return FALSE;
    }

    if (insert_id) {
        *insert_id = sqlite3_last_insert_rowid(sqlite->db);
    }

    if (rows_affected) {
        *rows_affected = sqlite3_changes(sqlite->db);
    }

    return TRUE;
}

gboolean
ftd_sqlite_execute_single_int_value_query(FTDSQLite *obj, const char *query, char **errmsg, int *ret_data)
{
    FTDSQLite *sqlite = FTD_SQLITE(obj);
    gboolean retval;
    char *ret_data_tmp;

    g_return_if_fail(sqlite->calling_thread == g_thread_self());
    g_return_if_fail(ret_data != NULL);

    retval = ftd_sqlite_execute_single_string_value_query(sqlite, query, errmsg, &ret_data_tmp);

    if (retval) {
        *ret_data = atoi(ret_data_tmp);
        g_free(ret_data_tmp);
        return TRUE;
    } else {
        *ret_data = -1;
        return FALSE;
    }
}

gboolean
ftd_sqlite_execute_single_string_value_query(FTDSQLite *obj, const char *query, char **errmsg, char **ret_data)
{
    FTDSQLite *sqlite = FTD_SQLITE(obj);
    sqlite3_stmt *stmt;
    int retval;

    g_return_if_fail(sqlite->calling_thread == g_thread_self());
    g_return_if_fail(ret_data != NULL);

    *ret_data = NULL;

    sqlite3_prepare(sqlite->db, query, -1, &stmt, NULL);
    if (sqlite3_errcode(sqlite->db) != SQLITE_OK) {
        if (errmsg) {
            *errmsg = g_strdup_printf("sqlite3_prepare() failed: %s\nquery = %s", sqlite3_errmsg(sqlite->db), query);
        }

        return FALSE;
    }

    retval = sqlite3_step(stmt);
    if (retval != SQLITE_DONE && retval != SQLITE_ROW) {
        if (errmsg) {
            *errmsg = g_strdup_printf("sqlite3_step() failed: %s\nquery = %s", sqlite3_errmsg(sqlite->db), query);
        }

        sqlite3_finalize(stmt);

        return FALSE;
    }

    if (sqlite3_data_count(stmt) == 0) {
        sqlite3_finalize(stmt);

        return FALSE;
    }

    *ret_data = g_strdup((const gchar *) sqlite3_column_text(stmt, 0));
    sqlite3_finalize(stmt);

    return TRUE;
}

gboolean
ftd_sqlite_execute_multi_value_query(FTDSQLite *obj, const char *query, char **errmsg, database_callback callback, gpointer data)
{
    FTDSQLite *sqlite = FTD_SQLITE(obj);
    sqlite3_stmt *stmt;
    int retval;

    g_return_if_fail(sqlite->calling_thread == g_thread_self());

    sqlite3_prepare(sqlite->db, query, -1, &stmt, NULL);
    if (sqlite3_errcode(sqlite->db) != SQLITE_OK) {
        if (errmsg) {
            *errmsg = g_strdup_printf("sqlite3_prepare() failed: %s\nquery = %s", sqlite3_errmsg(sqlite->db), query);
        }

        return FALSE;
    }

    retval = sqlite3_step(stmt);
    while (retval == SQLITE_ROW) {
        int i;
        char **fields_data;
        int column_count = sqlite3_column_count(stmt);

        if (column_count == 0) {
            /* Hier mogen we eigenlijk nooit komen */
            g_warning("ftd_sqlite_execute_multi_value_query(): column_count == 0\n");
            retval = sqlite3_step(stmt);
            continue;
        }

        // Bewaar alle data uit het huidige record in een char **
        fields_data = g_new(gchar *, column_count + 1);

        for (i = 0; i < column_count; i++) {
            const char *field_data = (const char *) sqlite3_column_text(stmt, i);
            fields_data[i] = g_new(gchar, strlen(field_data) + 1);
            strncpy(fields_data[i], field_data, strlen(field_data) + 1);
        }

        fields_data[column_count] = NULL;

        callback(column_count, (const char **) fields_data, data);

        g_strfreev(fields_data);

        retval = sqlite3_step(stmt);
    }

    if (retval != SQLITE_DONE) {
        if (errmsg) {
            *errmsg = g_strdup_printf("sqlite3_step() failed: %s\nquery = %s", sqlite3_errmsg(sqlite->db), query);
        }

        sqlite3_finalize(stmt);

        return FALSE;
    }

    sqlite3_finalize(stmt);

    return TRUE;
}

gboolean
ftd_sqlite_begin_transaction(FTDSQLite *obj, char **errmsg)
{
    FTDSQLite *sqlite = FTD_SQLITE(obj);

    g_return_if_fail(sqlite->calling_thread == g_thread_self());
    g_return_if_fail(sqlite->transaction_active == FALSE);

    if (!ftd_sqlite_execute_query(sqlite, "BEGIN", errmsg, NULL, NULL)) {
        return FALSE;
    }

    sqlite->transaction_active = TRUE;

    return TRUE;
}

gboolean
ftd_sqlite_finish_transaction(FTDSQLite *obj, char **errmsg)
{
    FTDSQLite *sqlite = FTD_SQLITE(obj);

    g_return_val_if_fail(sqlite->calling_thread == g_thread_self(), FALSE);
    g_return_val_if_fail(sqlite->transaction_active == TRUE, FALSE);

    if (!ftd_sqlite_execute_query(sqlite, "END", errmsg, NULL, NULL)) {
        sqlite->transaction_active = FALSE;
        return FALSE;
    }

    sqlite->transaction_active = FALSE;

    return TRUE;
}
