/*
 * Copyright (C) 2006, 2007 Apple Inc.
 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS 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 APPLE COMPUTER, INC. OR
 * 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 <gtk/gtk.h>
#include <webkit/webkit.h>
#include <gdom/GdomDOMWindow.h>
#include <gdom/GdomDocument.h>
#include <gdom/GdomNode.h>
#include <gdom/GdomNodeList.h>
#include <gdom/GdomText.h>
#include <gdom/GdomElement.h>
#include <gdom/GdomHTMLCollection.h>
#include <gdom/GdomEvent.h>
#include <gdom/GdomHTMLAnchorElement.h>
#include <gdom/GdomCSSStyleDeclaration.h>
#include <gdom/GdomNamedNodeMap.h>
#include <gdom/GdomAttr.h>

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

static gulong get_absolute_left(GdomNode *elem);
static gulong get_absolute_top(GdomNode *elem);
static GdomDocument* get_dom_document(void);
static void print_href(GdomNode *node);
static void set_css_property(GdomNode *node, const char *prop, const char *val);

static GtkWidget* main_window;
static GtkWidget* uri_entry;
static GtkStatusbar* main_statusbar;
static WebKitWebView* web_view;
static gchar* main_title;
static gint load_progress;
static guint status_context_id;

typedef struct _node_list {
    GdomNode *node; /* the node being highlighted */
    GdomNode *div; /* for storing the highlighter */
    struct _node_list *next;
    bool mark;
} node_list;

node_list *links = NULL;

static node_list * add_link(node_list **list, GdomNode *node, GdomNode *div)
{
    node_list *item = malloc(sizeof(node_list));
    item->node = node;
    item->div = div;
    item->next = *list;
    item->mark = false;
    *list = item;
    return item;
}

static void free_link(node_list *item)
{
    g_object_unref(item->node);
    if (item->div != NULL)
    {
        GdomNode *parent;
        g_object_get(item->node, "parent", &parent, NULL);
        if (parent)
        {
            gdom_node_remove_child(parent, item->div);
            g_object_unref(parent);
        }
        g_object_unref(item->div);
    }
    free(item);
}

static void remove_link(node_list **list, node_list *item)
{
    node_list *ptr = *list;
    if (item == (*list))
    {
        *list = item->next;
        free_link(item);
        return;
    }
    while (ptr != NULL)
    {
        if (ptr->next == item)
        {
            ptr->next = item->next;
            free_link(item);
            return;
        }
        ptr = ptr->next;
    }
}

static void mark_list(node_list *ptr)
{
    while (ptr != NULL)
    {
        ptr->mark = false;
        ptr = ptr->next;
    }
}

static void free_unmarked_list(node_list **list)
{
    node_list *ptr = *list;
    while (ptr != NULL)
    {
        if (ptr->mark == false)
        {
            remove_link(list, ptr);
        }
        ptr = ptr->next;
    }
}

static void free_list(node_list **list)
{
    while (*list != NULL)
    {
        remove_link(list, *list);
    }
}

static bool is_in_list(node_list *ptr, node_list *item)
{
    while (ptr != NULL)
    {
        if (ptr == item)
        {
            return true;
        }
        ptr = ptr->next;
    }
    return false;
}

static node_list *find_item_by_node(node_list *ptr, GdomNode *node)
{
    while (ptr != NULL)
    {
        if (ptr->node == node)
        {
            return ptr;
        }
        ptr = ptr->next;
    }
    return NULL;
}

static void add_or_mark(node_list **list, GdomNode *node)
{
    GdomNode *div;
    gulong x = get_absolute_left(node);
    gulong y = get_absolute_top(node);
    char left[20];
    char top[20];
    node_list *ptr = find_item_by_node(*list, node);

    if (!ptr)
    {
        GdomDocument *doc = get_dom_document();
        GdomNode *body;

        g_object_get(doc, "body", &body, NULL);

        div = gdom_document_create_element(doc, "div");
        gdom_node_append_child(body, div);

        ptr = add_link(list, node, div);
        print_href(node);

        g_object_unref(body);
        g_object_unref(doc);
    }


    div = ptr->div;

    sprintf(left, "%ldpx", x);
    sprintf(top, "%ldpx", y);
    set_css_property(div, "width", "50px");
    set_css_property(div, "height", "3px");
    set_css_property(div, "left", left);
    set_css_property(div, "top", top);
    set_css_property(div, "position", "absolute");
    set_css_property(div, "border", "1px solid #802000");
    set_css_property(div, "z-index", "100000000");
    ptr->mark = true;
}

static void
make_transparent(WebKitWebView* l_web_view)
{
    GValue val = {0,};
    g_value_init (&val, G_TYPE_BOOLEAN);
    g_value_set_boolean (&val, 1);
    g_object_set_property (G_OBJECT (l_web_view), "transparent", &val);
}

static void
make_transparent_cb (GtkWidget* widget, gpointer data)
{
    make_transparent (web_view);
}

static void
set_colormap(GtkWidget* window)
{
    GdkScreen* screen = gtk_widget_get_screen (window);
    GdkColormap* colormap = gdk_screen_get_rgba_colormap (screen);
    if (!colormap) {
        g_warning ("No ARGB colormap available! Using RGB colormap!\n");
        colormap = gdk_screen_get_rgb_colormap (screen);
    } else {
        g_print ("Using ARGB colormap now! :-)\n");
    }
    gtk_widget_set_colormap (window, colormap);
}

static void
activate_uri_entry_cb (GtkWidget* entry, gpointer data)
{
    const gchar* uri = gtk_entry_get_text (GTK_ENTRY (entry));
    g_assert (uri);
    webkit_web_view_open (web_view, uri);
}

static void
update_title (GtkWindow* window)
{
    GString* string = g_string_new (main_title);
    g_string_append (string, " - WebKit Launcher");
    if (load_progress < 100)
        g_string_append_printf (string, " (%d%%)", load_progress);
    gchar* title = g_string_free (string, FALSE);
    gtk_window_set_title (window, title);
    g_free (title);
}

static void
link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data)
{
    /* underflow is allowed */
    gtk_statusbar_pop (main_statusbar, status_context_id);
    if (link)
        gtk_statusbar_push (main_statusbar, status_context_id, link);
}

static void
title_change_cb (WebKitWebView* web_view, WebKitWebFrame* web_frame, const gchar* title, gpointer data)
{
    if (main_title)
        g_free (main_title);
    main_title = g_strdup (title);
    update_title (GTK_WINDOW (main_window));
}

static void
progress_change_cb (WebKitWebView* page, gint progress, gpointer data)
{
    load_progress = progress;
    update_title (GTK_WINDOW (main_window));
}

static void
load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data)
{
    const gchar* uri = webkit_web_frame_get_uri(frame);
    if (uri)
        gtk_entry_set_text (GTK_ENTRY (uri_entry), uri);
}

static GdomDOMWindow*
get_dom_window(void)
{
    WebKitWebFrame* frame = webkit_web_view_get_main_frame(web_view);
    return webkit_web_frame_get_dom_window(frame);
}

static GdomDocument*
get_dom_document(void)
{
    WebKitWebFrame* frame = webkit_web_view_get_main_frame(web_view);
    return webkit_web_frame_get_dom_document(frame);
}

static void set_css_property(GdomNode *node, const char *prop, const char *val)
{
    GdomCSSStyleDeclaration *sty;
    g_object_get(node, "style", &sty, NULL);
    gdom_css_style_declaration_set_css_property(sty, prop, val, "");
    g_object_unref(sty);
}

static void set_example_css_property(GdomNode *node)
{
    set_css_property(node, "border", "1px solid #802000");
}

bool isVisible(GdomNode *elem)
{
    GdomCSSStyleDeclaration *visibility;
    GdomCSSStyleDeclaration *display;
    gchar *visible_prop;
    gchar *display_prop;
    bool res;

    GdomDOMWindow *wnd = get_dom_window();

    visibility = gdom_dom_window_get_computed_style(wnd, elem, "visibility");
    display = gdom_dom_window_get_computed_style(wnd, elem, "display");
    visible_prop = gdom_css_style_declaration_get_property_value(visibility,
                                                        "visibility");
    display_prop = gdom_css_style_declaration_get_property_value(display,
    "visibility");

    res = (strcmp((char*)visible_prop, "hidden") != 0) &&
          (strcmp((char*)display, "hidden") != 0);

    g_free(visible_prop);
    g_free(display_prop);
    g_object_unref(visibility);
    g_object_unref(display);

    return res;
}

static void print_outer_html(GdomNode *node)
{
    gchar *html;

    g_object_get(node, "outer_html", &html, NULL);

    if (html)
    {
        printf("%s\n", html);
        g_free(html);
    }
}

static void print_src(GdomNode *node)
{
    gchar *href;
    g_object_get(node, "src", &href, NULL);
    if (href)
    {
        printf("embed src %s\n", href);
        g_free(href);
    }
}

static void print_href(GdomNode *node)
{
    gchar *href;
    g_object_get(node, "href", &href, NULL);
    if (href)
    {
        printf("type %s\n", href);
        g_free(href);
    }
}

static void walk_embed_node(GdomNode *node)
{
    GdomNode *child;
    g_object_get(node, "first_child", &child, NULL);
    print_src(node);
    while (child)
    {
        GdomNode *next_sibling;
        gchar *name;
        g_object_get(child, "node_name", &name, NULL);
        printf("embed child node %s\n", name);
        if (strcmp(name, "A") == 0 || strcmp(name, "a") == 0)
        {
            set_example_css_property(child);
            print_href(child);
        }
        /*else
        {
            GdomNode *next_child;
            g_object_get(child, "first_child", &next_child, NULL);
            if (next_child)
            {
                walk_embed_node(next_child);
                g_object_unref(next_child);
            }
        }*/
        g_free(name);
        g_object_get(child, "next_sibling", &next_sibling, NULL);
        g_object_unref(child);
        child = next_sibling;
    }
}

static void print_attribute(GdomNode *node, const gchar *attr_name)
{
    GdomNamedNodeMap *attrs;
    GdomAttr *attr;
    gchar *value;
    g_object_get(node, "attributes", &attrs, NULL);
    if (!attrs)
        return;
    attr = gdom_named_node_map_get_named_item(attrs, attr_name);
    g_object_unref(attrs);
    if (!attr)
    {
        return;
    }
    g_object_get(attr, "value", &value, NULL);
    g_object_unref(attr);
    if (!value)
    {
        return;
    }
    printf("value %s: %s\n", attr_name, value);
    g_free(value);
}

static void walk_object_tag(GdomNode *node, const gchar *tagname)
{
    GdomNode *child;
    /*set_example_css_property(node);*/
    g_object_get(node, "first_child", &child, NULL);
    while (child)
    {
        GdomNode *next_sibling;
        gchar *name;
        g_object_get(child, "node_name", &name, NULL);
        printf("object child node %s\n", name);
        if (strcmp(name, tagname) == 0)
        {
            printf("found %s\n", tagname);
            print_outer_html(child);
        }
        g_object_get(child, "next_sibling", &next_sibling, NULL);
        g_object_unref(child);
        child = next_sibling;
        g_free(name);
    }
}

static void walk_object_node(GdomNode *node)
{
    GdomNode *child;
    set_example_css_property(node);
    g_object_get(node, "first_child", &child, NULL);
    while (child)
    {
        GdomNode *next_sibling;
        gchar *name;
        g_object_get(child, "node_name", &name, NULL);
        printf("object child node %s\n", name);
        if (strcmp(name, "FORM") == 0)
        {
            printf("form\n");
            print_outer_html(child);
        }
        if (strcmp(name, "EMBED") == 0)
        {
            print_outer_html(child);
            print_attribute(child, "href");
        }
        g_object_get(child, "next_sibling", &next_sibling, NULL);
        g_object_unref(child);
        child = next_sibling;
        g_free(name);
    }
}

static gulong get_long_attribute( GdomNode *node, const gchar *prop)
{
    glong ul;
    g_object_get(node, prop, &ul, NULL);
    printf("prop: %s val: %ld\n", prop, ul);
    return ul;
}

static gulong get_body_scroll_top(void)
{
    GdomDocument *doc = get_dom_document();
    GdomNode *body;
    gulong res;
    g_object_get(doc, "body", &body, NULL);
    res = get_long_attribute(body, "scroll_top");
    g_object_unref(body);
    return res;
}

static gulong get_absolute_left(GdomNode *elem)
{
    glong left = 0;
    GdomNode *el = elem;
    while (elem)
    {
        GdomNode *parent;
        gchar *tag_name;

        left += get_long_attribute(elem, "offset_left") - 
               get_long_attribute(elem, "scroll_left");

        g_object_get(elem, "offset_parent", &parent, NULL);
        if (parent)
        {
            g_object_get(parent, "tag_name", &tag_name, NULL);
            if (strcmp(tag_name, "BODY") == 0)
            {
                /*if parent.props.tag_name == 'BODY' and \
                hasattr(elem, 'style') and \
                getStyleAttribute(elem, 'position') == 'absolute':*/
                g_free(tag_name);
                g_object_unref(parent);
                break;
            }
            g_free(tag_name);
        }
        if (elem != el)
            g_object_unref(elem);
        elem = parent;
    }
    return left; /*+ get_body_scroll_top();*/
}

static gulong get_absolute_top(GdomNode *elem)
{
    glong top = 0;
    GdomNode *el = elem;
    while (elem)
    {
        GdomNode *parent;
        gchar *tag_name;

        top += get_long_attribute(elem, "offset_top") - 
               get_long_attribute(elem, "scroll_top");

        g_object_get(elem, "offset_parent", &parent, NULL);
        if (parent)
        {
            g_object_get(parent, "tag_name", &tag_name, NULL);
            if (strcmp(tag_name, "BODY") == 0)
            {
                /*if parent.props.tag_name == 'BODY' and \
                hasattr(elem, 'style') and \
                getStyleAttribute(elem, 'position') == 'absolute':*/
                g_free(tag_name);
                g_object_unref(parent);
                break;
            }
            g_free(tag_name);
        }
        if (elem != el)
            g_object_unref(elem);
        elem = parent;
    }
    return top; /*+ get_body_scroll_top();*/
}

static void scroll_into_view(GdomNode *node)
{
    GdomDOMWindow *wnd = get_dom_window();
    gulong top = get_absolute_top(node);
    printf("top: %ld\n", top);
    gdom_dom_window_scroll_to(wnd, 0, top);
}

void print_a_hrefs (void)
{
    GdomDocument *doc = get_dom_document();
    GdomNodeList *els;
    gulong tags_length;
    gulong i;

    els = gdom_document_get_elements_by_tag_name(doc, "a");

    g_object_get(els, "length", &tags_length, NULL);

    printf("href count: %ld\n", tags_length);

    mark_list(&links);

    for (i = 0; i < tags_length; i++)
    {
        GdomNode *node = gdom_node_list_item(els, i);
        if (i == tags_length/2)
        {
            scroll_into_view(node);
        }

        if (isVisible(node))
        { 
            add_or_mark(&links, node);
        }
    }
    free_unmarked_list(&links);
}

void print_object_hrefs (void)
{
    GdomDocument *doc = get_dom_document();
    GdomNodeList *els;
    gulong tags_length;
    gulong i;

    els = gdom_document_get_elements_by_tag_name(doc, "object");

    g_object_get(els, "length", &tags_length, NULL);

    printf("href count: %ld\n", tags_length);

    for (i = 0; i < tags_length; i++)
    {
        GdomNode *node = gdom_node_list_item(els, i);
        if (i == 0)
        {
            scroll_into_view(node);
        }

        walk_object_node(node);
    }
}

void print_object_tag (const gchar *tagname)
{
    GdomDocument *doc = get_dom_document();
    gulong tags_length;
    gulong i;
    GdomHTMLElement *body;
    GdomHTMLCollection *children;

    printf("print object tags %s\n", tagname);

    g_object_get(doc, "body", &body, NULL);
    g_object_get(body, "children", &children, NULL);

    g_object_get(children, "length", &tags_length, NULL);

    printf("tag count: %ld\n", tags_length);

    for (i = 0; i < tags_length; i++)
    {
        GdomNode *node = gdom_html_collection_item(children, i);
        walk_object_tag(node, tagname);
    }
    g_object_unref(children);
    g_object_unref(body);
}

static gboolean
dispatch_event_cb (GdomEventTargetNode* et, GdomEvent *event, gchar *event_name, gboolean useCapture)
{
    gchar *event_type;

    g_object_get(event, "type", &event_type, NULL);
    printf("event:%s\n", event_type);

    print_a_hrefs ();
    /*print_object_tag ("form");*/
        
    return 0;
}

static void
load_finished_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data)
{
    GdomDocument *doc = webkit_web_frame_get_dom_document(frame);
    GdomNodeList *els = gdom_document_get_elements_by_tag_name(doc, (gchar*)"body");
    GdomNode *body = gdom_node_list_item(els, 0);
    GdomElement *div = gdom_document_create_element(doc, (gchar*)"div");
    GdomText *txt = gdom_document_create_text_node(doc, (gchar*)"click me to see hrefs");

    gdom_node_append_child(GDOM_NODE(div), GDOM_NODE(txt));
    gdom_node_append_child(GDOM_NODE(body), GDOM_NODE(div));

    gdom_element_set_attribute(GDOM_ELEMENT(div), (gchar*)"id", (gchar*)"helloworld");

    g_signal_connect (G_OBJECT (div), "browser-event", G_CALLBACK (dispatch_event_cb), div);
    add_event_listener(div, "click", true);

    webkit_web_view_set_transparent(page, true);
    /*make_transparent (page);*/

    print_object_tag ("A");
}

static void
destroy_cb (GtkWidget* widget, gpointer data)
{
    gtk_main_quit ();
}

static void
go_back_cb (GtkWidget* widget, gpointer data)
{
    webkit_web_view_go_back (web_view);
}

static void
go_forward_cb (GtkWidget* widget, gpointer data)
{
    webkit_web_view_go_forward (web_view);
}

static GtkWidget*
create_browser ()
{
    GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

    web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
    gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (web_view));

    g_signal_connect (G_OBJECT (web_view), "load-finished", G_CALLBACK (load_finished_cb), web_view);
    g_signal_connect (G_OBJECT (web_view), "title-changed", G_CALLBACK (title_change_cb), web_view);
    g_signal_connect (G_OBJECT (web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), web_view);
    g_signal_connect (G_OBJECT (web_view), "load-committed", G_CALLBACK (load_commit_cb), web_view);
    g_signal_connect (G_OBJECT (web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), web_view);

    return scrolled_window;
}

static GtkWidget*
create_statusbar ()
{
    main_statusbar = GTK_STATUSBAR (gtk_statusbar_new ());
    status_context_id = gtk_statusbar_get_context_id (main_statusbar, "Link Hover");

    return (GtkWidget*)main_statusbar;
}

static GtkWidget*
create_toolbar ()
{
    GtkWidget* toolbar = gtk_toolbar_new ();

    gtk_toolbar_set_orientation (GTK_TOOLBAR (toolbar), GTK_ORIENTATION_HORIZONTAL);
    gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_BOTH_HORIZ);

    GtkToolItem* item;

    /* the back button */
    item = gtk_tool_button_new_from_stock (GTK_STOCK_GO_BACK);
    g_signal_connect (G_OBJECT (item), "clicked", G_CALLBACK (go_back_cb), NULL);
    gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);

    /* The forward button */
    item = gtk_tool_button_new_from_stock (GTK_STOCK_GO_FORWARD);
    g_signal_connect (G_OBJECT (item), "clicked", G_CALLBACK (go_forward_cb), NULL);
    gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);

    /* The URL entry */
    item = gtk_tool_item_new ();
    gtk_tool_item_set_expand (item, TRUE);
    uri_entry = gtk_entry_new ();
    gtk_container_add (GTK_CONTAINER (item), uri_entry);
    g_signal_connect (G_OBJECT (uri_entry), "activate", G_CALLBACK (activate_uri_entry_cb), NULL);
    gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);

    /* The go button */
    item = gtk_tool_button_new_from_stock (GTK_STOCK_OK);
    g_signal_connect_swapped (G_OBJECT (item), "clicked", G_CALLBACK (activate_uri_entry_cb), (gpointer)uri_entry);
    gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);

    return toolbar;
}

static GtkWidget*
create_window ()
{
    GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
    gtk_widget_set_name (window, "GtkLauncher");
    g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);

    return window;
}

int
main (int argc, char* argv[])
{
    gtk_init (&argc, &argv);

    GtkWidget* vbox = gtk_vbox_new (FALSE, 0);
    gtk_box_pack_start (GTK_BOX (vbox), create_toolbar (), FALSE, FALSE, 0);
    gtk_box_pack_start (GTK_BOX (vbox), create_browser (), TRUE, TRUE, 0);
    gtk_box_pack_start (GTK_BOX (vbox), create_statusbar (), FALSE, FALSE, 0);

    main_window = create_window ();
    set_colormap(main_window);
    gtk_container_add (GTK_CONTAINER (main_window), vbox);

    gchar* uri = (gchar*) (argc > 1 ? argv[1] : "http://www.google.com/");
    webkit_web_view_open (web_view, uri);

    gtk_widget_grab_focus (GTK_WIDGET (web_view));
    gtk_widget_show_all (main_window);
    gtk_main ();

    return 0;
}
