/*
 Copyright (C) 2010 Christian Dywan <christian@twotoasts.de>
 Copyright (C) 2010 Alexander Butenko <a.butenka@gmail.com>

 This library 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.

 See the file COPYING for the full license text.
*/

#include "midori-bookmarks.h"
#include "panels/midori-bookmarks.h"
#include "midori-app.h"
#include "midori-array.h"
#include "sokoke.h"
#include "midori-core.h"

#include <glib/gstdio.h>
#include <glib/gi18n.h>

#include <config.h>
#ifdef HAVE_UNISTD_H
    #include <unistd.h>
#endif

void
midori_bookmarks_dbtracer (void*       dummy,
                           const char* query)
{
    g_printerr ("%s\n", query);
}

void
midori_bookmarks_add_item_cb (KatzeArray* array,
                              KatzeItem*  item,
                              sqlite3*    db)
{
    midori_bookmarks_insert_item_db (db, item,
        katze_item_get_meta_integer (item, "parentid"));
}

void
midori_bookmarks_remove_item_cb (KatzeArray* array,
                                 KatzeItem*  item,
                                 sqlite3*    db)
{
    gchar* sqlcmd;
    char* errmsg = NULL;


    sqlcmd = sqlite3_mprintf (
            "DELETE FROM bookmarks WHERE id = %" G_GINT64_FORMAT ";",
            katze_item_get_meta_integer (item, "id"));

    if (sqlite3_exec (db, sqlcmd, NULL, NULL, &errmsg) != SQLITE_OK)
    {
        g_printerr (_("Failed to remove history item: %s\n"), errmsg);
        sqlite3_free (errmsg);
    }

    sqlite3_free (sqlcmd);
}

#define _APPEND_TO_SQL_ERRORMSG(custom_errmsg) \
    do { \
        if (sql_errmsg) \
        { \
            g_string_append_printf (errmsg_str, "%s : %s\n", custom_errmsg, sql_errmsg); \
            sqlite3_free (sql_errmsg); \
        } \
        else \
            g_string_append (errmsg_str, custom_errmsg); \
    } while (0)

gboolean
midori_bookmarks_import_from_old_db (sqlite3*     db,
                                     const gchar* oldfile,
                                     gchar**      errmsg)
{
    gint sql_errcode;
    gboolean failure = FALSE;
    gchar* sql_errmsg = NULL;
    GString* errmsg_str = g_string_new (NULL);
    gchar* attach_stmt = sqlite3_mprintf ("ATTACH DATABASE %Q AS old_db;", oldfile);
    const gchar* convert_stmts =
        "BEGIN TRANSACTION;"
        "INSERT INTO main.bookmarks (parentid, title, uri, desc, app, toolbar) "
        "SELECT NULL AS parentid, title, uri, desc, app, toolbar "
        "FROM old_db.bookmarks;"
        "UPDATE main.bookmarks SET parentid = ("
        "SELECT id FROM main.bookmarks AS b1 WHERE b1.title = ("
        "SELECT folder FROM old_db.bookmarks WHERE title = main.bookmarks.title));"
        "COMMIT;";
    const gchar* detach_stmt = "DETACH DATABASE old_db;";

    *errmsg = NULL;
    sql_errcode = sqlite3_exec (db, attach_stmt, NULL, NULL, &sql_errmsg);
    sqlite3_free (attach_stmt);

    if (sql_errcode != SQLITE_OK)
    {
        _APPEND_TO_SQL_ERRORMSG (_("failed to ATTACH old db"));
        goto convert_failed;
    }

    if (sqlite3_exec (db, convert_stmts, NULL, NULL, &sql_errmsg) != SQLITE_OK)
    {
        failure = TRUE;
        _APPEND_TO_SQL_ERRORMSG (_("failed to import from old db"));

        /* try to get back to previous state */
        if (sqlite3_exec (db, "ROLLBACK TRANSACTION;", NULL, NULL, &sql_errmsg) != SQLITE_OK)
            _APPEND_TO_SQL_ERRORMSG (_("failed to rollback the transaction"));
    }

    if (sqlite3_exec (db, detach_stmt, NULL, NULL, &sql_errmsg) != SQLITE_OK)
        _APPEND_TO_SQL_ERRORMSG (_("failed to DETACH "));

    if (failure)
    {
    convert_failed:
        *errmsg = g_string_free (errmsg_str, FALSE);
        g_print ("ERRORR: %s\n", errmsg_str->str);
        return FALSE;
    }

    return TRUE;
}
#undef _APPEND_TO_SQL_ERRORMSG

KatzeArray*
midori_bookmarks_new (char** errmsg)
{
    sqlite3* db;
    gchar* oldfile;
    gchar* newfile;
    gboolean newfile_did_exist, oldfile_exists;
    const gchar* create_stmt;
    gchar* sql_errmsg = NULL;
    gchar* import_errmsg = NULL;
    KatzeArray* array;

    g_return_val_if_fail (errmsg != NULL, NULL);

    oldfile = midori_paths_get_config_filename_for_writing ("bookmarks.db");
    oldfile_exists = g_access (oldfile, F_OK) == 0;
    newfile = midori_paths_get_config_filename_for_writing ("bookmarks_v2.db");
    newfile_did_exist = g_access (newfile, F_OK) == 0;

    /* sqlite3_open will create the file if it did not exists already */
    if (sqlite3_open (newfile, &db) != SQLITE_OK)
    {
        *errmsg = g_strdup_printf (_("Failed to open database: %s\n"),
            db ? sqlite3_errmsg (db) : "(db = NULL)");
        goto init_failed;
    }

    if (midori_debug ("bookmarks"))
        sqlite3_trace (db, midori_bookmarks_dbtracer, NULL);

    create_stmt =     /* Table structure */
        "CREATE TABLE IF NOT EXISTS bookmarks "
        "(id INTEGER PRIMARY KEY AUTOINCREMENT, "
        "parentid INTEGER DEFAULT NULL, "
        "title TEXT, uri TEXT, desc TEXT, app INTEGER, toolbar INTEGER, "
        "pos_panel INTEGER, pos_bar INTEGER, "
        "created DATE DEFAULT CURRENT_TIMESTAMP, "
        "last_visit DATE, visit_count INTEGER DEFAULT 0, "
        "nick TEXT, "
        "FOREIGN KEY(parentid) REFERENCES bookmarks(id) "
        "ON DELETE CASCADE); PRAGMA foreign_keys = ON;"

        /* trigger: insert panel position */
        "CREATE TRIGGER IF NOT EXISTS bookmarkInsertPosPanel "
        "AFTER INSERT ON bookmarks FOR EACH ROW "
        "BEGIN UPDATE bookmarks SET pos_panel = ("
        "SELECT ifnull(MAX(pos_panel),0)+1 FROM bookmarks WHERE "
        "(NEW.parentid IS NOT NULL AND parentid = NEW.parentid) "
        "OR (NEW.parentid IS NULL AND parentid IS NULL)) "
        "WHERE id = NEW.id; END;"

        /* trigger: insert Bookmarkbar position */
        "CREATE TRIGGER IF NOT EXISTS bookmarkInsertPosBar "
        "AFTER INSERT ON bookmarks FOR EACH ROW WHEN NEW.toolbar=1 "
        "BEGIN UPDATE bookmarks SET pos_bar = ("
        "SELECT ifnull(MAX(pos_bar),0)+1 FROM bookmarks WHERE "
        "((NEW.parentid IS NOT NULL AND parentid = NEW.parentid) "
        "OR (NEW.parentid IS NULL AND parentid IS NULL)) AND toolbar=1) "
        "WHERE id = NEW.id; END;"

        /* trigger: update panel position */
        "CREATE TRIGGER IF NOT EXISTS bookmarkUpdatePosPanel "
        "BEFORE UPDATE OF parentid ON bookmarks FOR EACH ROW "
        "WHEN ((NEW.parentid IS NULL OR OLD.parentid IS NULL) "
        "AND NEW.parentid IS NOT OLD.parentid) OR "
        "((NEW.parentid IS NOT NULL AND OLD.parentid IS NOT NULL) "
        "AND NEW.parentid!=OLD.parentid) "
        "BEGIN UPDATE bookmarks SET pos_panel = pos_panel-1 "
        "WHERE ((OLD.parentid IS NOT NULL AND parentid = OLD.parentid) "
        "OR (OLD.parentid IS NULL AND parentid IS NULL)) AND pos_panel > OLD.pos_panel; "
        "UPDATE bookmarks SET pos_panel = ("
        "SELECT ifnull(MAX(pos_panel),0)+1 FROM bookmarks "
        "WHERE (NEW.parentid IS NOT NULL AND parentid = NEW.parentid) "
        "OR (NEW.parentid IS NULL AND parentid IS NULL)) "
        "WHERE id = OLD.id; END;"

        /* trigger: update Bookmarkbar position */
        "CREATE TRIGGER IF NOT EXISTS bookmarkUpdatePosBar0 "
        "AFTER UPDATE OF parentid, toolbar ON bookmarks FOR EACH ROW "
        "WHEN ((NEW.parentid IS NULL OR OLD.parentid IS NULL) "
        "AND NEW.parentid IS NOT OLD.parentid) "
        "OR ((NEW.parentid IS NOT NULL AND OLD.parentid IS NOT NULL) "
        "AND NEW.parentid!=OLD.parentid) OR (OLD.toolbar=1 AND NEW.toolbar=0) "
        "BEGIN UPDATE bookmarks SET pos_bar = NULL WHERE id = NEW.id; "
        "UPDATE bookmarks SET pos_bar = pos_bar-1 "
        "WHERE ((OLD.parentid IS NOT NULL AND parentid = OLD.parentid) "
        "OR (OLD.parentid IS NULL AND parentid IS NULL)) AND pos_bar > OLD.pos_bar; END;"

        /* trigger: update Bookmarkbar position */
        "CREATE TRIGGER IF NOT EXISTS bookmarkUpdatePosBar1 "
        "BEFORE UPDATE OF parentid, toolbar ON bookmarks FOR EACH ROW "
        "WHEN ((NEW.parentid IS NULL OR OLD.parentid IS NULL) "
        "AND NEW.parentid IS NOT OLD.parentid) OR "
        "((NEW.parentid IS NOT NULL AND OLD.parentid IS NOT NULL) "
        "AND NEW.parentid!=OLD.parentid) OR (OLD.toolbar=0 AND NEW.toolbar=1) "
        "BEGIN UPDATE bookmarks SET pos_bar = ("
        "SELECT ifnull(MAX(pos_bar),0)+1 FROM bookmarks WHERE "
        "(NEW.parentid IS NOT NULL AND parentid = NEW.parentid) "
        "OR (NEW.parentid IS NULL AND parentid IS NULL)) "
        "WHERE id = OLD.id; END;"

        /* trigger: delete panel position */
        "CREATE TRIGGER IF NOT EXISTS bookmarkDeletePosPanel "
        "AFTER DELETE ON bookmarks FOR EACH ROW "
        "BEGIN UPDATE bookmarks SET pos_panel = pos_panel-1 "
        "WHERE ((OLD.parentid IS NOT NULL AND parentid = OLD.parentid) "
        "OR (OLD.parentid IS NULL AND parentid IS NULL)) AND pos_panel > OLD.pos_panel; END;"

        /* trigger: delete Bookmarkbar position */
        "CREATE TRIGGER IF NOT EXISTS bookmarkDeletePosBar "
        "AFTER DELETE ON bookmarks FOR EACH ROW WHEN OLD.toolbar=1 "
        "BEGIN UPDATE bookmarks SET pos_bar = pos_bar-1 "
        "WHERE ((OLD.parentid IS NOT NULL AND parentid = OLD.parentid) "
        "OR (OLD.parentid IS NULL AND parentid IS NULL)) AND pos_bar > OLD.pos_bar; END;";


    if (newfile_did_exist)
    {
        /* we are done */
        goto init_success;
    }
    else
    {
        /* initial creation */
        if (sqlite3_exec (db, create_stmt, NULL, NULL, &sql_errmsg) != SQLITE_OK)
        {
            *errmsg = g_strdup_printf (_("Couldn't create bookmarks table: %s\n"),
                sql_errmsg ? sql_errmsg : "(err = NULL)");
            sqlite3_free (sql_errmsg);

            /* we can as well remove the new file */
            g_unlink (newfile);
            goto init_failed;
        }

    }

    if (oldfile_exists)
        /* import from old db */
        if (!midori_bookmarks_import_from_old_db (db, oldfile, &import_errmsg))
        {
            *errmsg = g_strdup_printf (_("Couldn't import from old database: %s\n"),
                import_errmsg ? import_errmsg : "(err = NULL)");
            g_free (import_errmsg);
        }

    init_success:
        g_free (newfile);
        g_free (oldfile);
        array = katze_array_new (KATZE_TYPE_ARRAY);
        g_signal_connect (array, "add-item",
                          G_CALLBACK (midori_bookmarks_add_item_cb), db);
        g_signal_connect (array, "remove-item",
                          G_CALLBACK (midori_bookmarks_remove_item_cb), db);
        g_object_set_data (G_OBJECT (array), "db", db);
        return array;

    init_failed:
        g_free (newfile);
        g_free (oldfile);

        if (db)
            sqlite3_close (db);

        return NULL;
}

void
midori_bookmarks_import (const gchar* filename,
                         sqlite3*     db)
{
    KatzeArray* bookmarks;
    GError* error = NULL;

    bookmarks = katze_array_new (KATZE_TYPE_ARRAY);

    if (!midori_array_from_file (bookmarks, filename, "xbel", &error))
    {
        g_warning (_("The bookmarks couldn't be saved. %s"), error->message);
        g_error_free (error);
        return;
    }
    midori_bookmarks_import_array_db (db, bookmarks, 0);
}

void
midori_bookmarks_on_quit (KatzeArray* array)
{
    g_return_if_fail (KATZE_IS_ARRAY (array));

    sqlite3* db = g_object_get_data (G_OBJECT (array), "db");
    g_return_if_fail (db != NULL);
    sqlite3_close (db);
}

