#include "kernel/debug.h"
#include "kernel/openbox.h"
#include "kernel/dispatch.h"
#include "kernel/frame.h"
#include "kernel/client.h"
#include "kernel/screen.h"
#include "parser/parse.h"
#include "history.h"
#include <glib.h>
#include <string.h>
#ifdef HAVE_STDLIB_H
#  include <stdlib.h>
#endif

#define PLACED        (1 << 0)

#define HAVE_POSITION (1 << 1)
#define HAVE_SIZE     (1 << 2)
#define HAVE_DESKTOP  (1 << 3)

struct HistoryItem {
    char *name;
    char *class;
    char *role;

    int flags;

    int x, y;
    int w, h;
    guint desk;
};

static GSList *history_list = NULL;
static char *history_path = NULL;

static struct HistoryItem *history_find(const char *name, const char *class,
                                 const char *role)
{
    GSList *it;
    struct HistoryItem *hi = NULL;

    /* find the client */
    for (it = history_list; it != NULL; it = it->next) {
        hi = it->data;
        if (!strcmp(hi->name, name) &&
            !strcmp(hi->class, class) &&
            !strcmp(hi->role, role))
            return hi;
    }
    return NULL;
}

gboolean place_history(ObClient *c)
{
    struct HistoryItem *hi;
    int x, y, w, h;

    hi = history_find(c->name, c->class, c->role);

    if (hi && !(hi->flags & PLACED)) {
        hi->flags |= PLACED;
        if (ob_state() != OB_STATE_STARTING) {
            if (hi->flags & HAVE_POSITION ||
                hi->flags & HAVE_SIZE) {
                if (hi->flags & HAVE_POSITION) {
                    x = hi->x;
                    y = hi->y;
                    /* get where the client should be */
                    frame_frame_gravity(c->frame, &x, &y);
                } else {
                    x = c->area.x;
                    y = c->area.y;
                }
                if (hi->flags & HAVE_SIZE) {
                    w = hi->w * c->size_inc.width;
                    h = hi->h * c->size_inc.height;
                } else {
                    w = c->area.width;
                    h = c->area.height;
                }
                client_configure(c, OB_CORNER_TOPLEFT, x, y, w, h,
                                 TRUE, TRUE);
            }
            if (hi->flags & HAVE_DESKTOP) {
                client_set_desktop(c, hi->desk, FALSE);
            }
        }
        return hi->flags & HAVE_POSITION;
    }

    return FALSE;
}

static void set_history(ObClient *c)
{
    struct HistoryItem *hi;

    hi = history_find(c->name, c->class, c->role);

    if (hi == NULL) {
        hi = g_new(struct HistoryItem, 1);
        history_list = g_slist_append(history_list, hi);
        hi->name = g_strdup(c->name);
        hi->class = g_strdup(c->class);
        hi->role = g_strdup(c->role);
        hi->flags = HAVE_POSITION;
    }

    if (hi->flags & HAVE_POSITION) {
        hi->x = c->frame->area.x;
        hi->y = c->frame->area.y;
    }

    hi->flags &= ~PLACED;
}

static void event(ObEvent *e, void *foo)
{
    g_assert(e->type == Event_Client_Destroy);

    set_history(e->data.c.client);
}

/*

<entry name="name" class="class" role="role">
  <x>0</x>
  <y>0</y>
  <width>300</width>
  <height>200</height>
  <desktop>1</desktop>
</entry>

*/

static void save_history()
{
    xmlDocPtr doc;
    xmlNodePtr root, node;
    char *s;
    GSList *it;

    doc = xmlNewDoc(NULL);
    root = xmlNewNode(NULL, (const xmlChar*) "openbox_history");
    xmlDocSetRootElement(doc, root);

    for (it = history_list; it; it = g_slist_next(it)) {
        struct HistoryItem *hi = it->data;
        ob_debug("adding %s\n", hi->name);
        node = xmlNewChild(root, NULL, (const xmlChar*) "entry", NULL);
        xmlNewProp(node, (const xmlChar*) "name", (const xmlChar*) hi->name);
        xmlNewProp(node, (const xmlChar*) "class", (const xmlChar*) hi->class);
        xmlNewProp(node, (const xmlChar*) "role", (const xmlChar*) hi->role);
        if (hi->flags & HAVE_POSITION) {
            s = g_strdup_printf("%d", hi->x);
            xmlNewTextChild(node, NULL,
                            (const xmlChar*) "x", (const xmlChar*) s);
            g_free(s);
            s = g_strdup_printf("%d", hi->y);
            xmlNewTextChild(node, NULL,
                            (const xmlChar*) "y", (const xmlChar*) s);
            g_free(s);
        }
        if (hi->flags & HAVE_SIZE) {
            s = g_strdup_printf("%d", hi->w);
            xmlNewTextChild(node, NULL,
                            (const xmlChar*) "width", (const xmlChar*) s);
            g_free(s);
            s = g_strdup_printf("%d", hi->h);
            xmlNewTextChild(node, NULL,
                            (const xmlChar*) "height", (const xmlChar*) s);
            g_free(s);
        }
        if (hi->flags & HAVE_DESKTOP) {
            s = g_strdup_printf("%d", hi->desk < 0 ? hi->desk : hi->desk + 1);
            xmlNewTextChild(node, NULL,
                            (const xmlChar*) "desktop", (const xmlChar*) s);
            g_free(s);
        }
    }

    xmlIndentTreeOutput = 1;
    xmlSaveFormatFile(history_path, doc, 1);

    xmlFreeDoc(doc);
}

static void load_history()
{
    xmlDocPtr doc;
    xmlNodePtr node, n;
    char *name;
    char *class;
    char *role;
    struct HistoryItem *hi;

    if (!parse_load(history_path, "openbox_history", &doc, &node))
        return;

    node = parse_find_node("entry", node->xmlChildrenNode);
    while (node) {
        name = class = role = NULL;
        if (parse_attr_string("name", node, &name) &&
            parse_attr_string("class", node, &class) &&
            parse_attr_string("role", node, &role)) {

            hi = history_find(name, class, role);
            if (!hi) {
                hi = g_new(struct HistoryItem, 1);
                hi->name = g_strdup(name);
                hi->class = g_strdup(class);
                hi->role = g_strdup(role);
                hi->flags = 0;
            }
            if ((n = parse_find_node("x", node->xmlChildrenNode))) {
                hi->x = parse_int(doc, n);
                if ((n = parse_find_node("y", node->xmlChildrenNode))) {
                    hi->y = parse_int(doc, n);
                    hi->flags |= HAVE_POSITION;
                }
            }
            if ((n = parse_find_node("width", node->xmlChildrenNode))) {
                hi->w = parse_int(doc, n);
                if ((n = parse_find_node("height", node->xmlChildrenNode))) {
                    hi->h = parse_int(doc, n);
                    hi->flags |= HAVE_SIZE;
                }
            }
            if ((n = parse_find_node("desktop", node->xmlChildrenNode))) {
                hi->desk = parse_int(doc, n);
                if (hi->desk > 0) --hi->desk;
                hi->flags |= HAVE_DESKTOP;
            }

            history_list = g_slist_append(history_list, hi);
        }
        g_free(name); g_free(class); g_free(role);
        node = parse_find_node("entry", node->next);
    }
    xmlFreeDoc(doc);
}

void history_startup()
{
    char *path;

    history_list = NULL;

    path = g_build_filename(g_get_home_dir(), ".openbox", "history", NULL);
    history_path = g_strdup_printf("%s.%d", path, ob_screen);
    g_free(path);

    /*load_history(); /\* load from the historydb file */

    dispatch_register(Event_Client_Destroy, (EventHandler)event, NULL);
}

void history_shutdown()
{
    GSList *it;

    /*save_history(); /\* save to the historydb file */
    for (it = history_list; it != NULL; it = it->next) {
        struct HistoryItem *hi = it->data;
        g_free(hi->name);
        g_free(hi->class);
        g_free(hi->role);
        g_free(hi);
    }
    g_slist_free(history_list);

    dispatch_register(0, (EventHandler)event, NULL);

    g_free(history_path);
}