/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 *  Copyright (C) 2009 Hiroyuki Ikezoe
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-bindings.h>

#include "kz-embed.h"
#include "kz-dbus-embed-agent.h"
#include "kz-dbus-embed-agent-client-bindings.h"
#include "kz-marshalers.h"

#define KZ_TYPE_EMBED_PROCESS		       (kz_embed_process_get_type ())

enum {
    PROP_0,
    PROP_SOCKET_ADDRESS
};

typedef struct _KzEmbedProcess KzEmbedProcess;
typedef struct _KzEmbedProcessClass KzEmbedProcessClass;

struct _KzEmbedProcess
{
    GObject parent;
};

struct _KzEmbedProcessClass
{
    GObjectClass parent;
};

typedef struct _KzEmbedProcessPrivate	KzEmbedProcessPrivate;
struct _KzEmbedProcessPrivate
{
    DBusGProxy *proxy;
    KzEmbed *embed;
    guint32 embed_id;
    gchar *socket_address;
};

#define KZ_EMBED_PROCESS_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), KZ_TYPE_EMBED_PROCESS, KzEmbedProcessPrivate))

GType        kz_embed_process_get_type (void) G_GNUC_CONST;
static GObject *constructor  (GType type,
                              guint n_props,
                              GObjectConstructParam *props);
static void  set_property (GObject      *object,
                           guint         prop_id,
                           const GValue *value,
                           GParamSpec   *pspec);
static void  get_property (GObject      *object,
                           guint         prop_id,
                           GValue       *value,
                           GParamSpec   *pspec);

G_DEFINE_TYPE(KzEmbedProcess, kz_embed_process, G_TYPE_OBJECT);

static void
kz_embed_process_class_init (KzEmbedProcessClass *klass)
{
	GObjectClass *object_class;

	object_class = G_OBJECT_CLASS(klass);
    object_class->constructor = constructor;
    object_class->set_property = set_property;
    object_class->get_property = get_property;

    g_object_class_install_property
        (object_class,
         PROP_SOCKET_ADDRESS,
         g_param_spec_string("socket-address",
             _("Socket Address"),
             _("The address for comunicate KzDBusServer"),
             NULL,
             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

	g_type_class_add_private(object_class, sizeof(KzEmbedProcessPrivate));
}

static void
cb_net_stop (KzEmbed *embed, KzEmbedProcess *process)
{
    GError *error = NULL;
    KzEmbedProcessPrivate *priv;

    priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);

    if (!org_kazehakase_Embed_net_stop(priv->proxy, &error)) {
        g_print("%s\n", error->message);
        g_error_free(error);
    } 
}

static void
cb_net_start (KzEmbed *embed, KzEmbedProcess *process)
{
    GError *error = NULL;
    KzEmbedProcessPrivate *priv;

    priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);

    if (!org_kazehakase_Embed_net_start(priv->proxy, &error)) {
        g_print("%s\n", error->message);
        g_error_free(error);
    } 
}

static void
cb_title (KzEmbed *embed, const gchar *title, KzEmbedProcess *process)
{
    GError *error = NULL;
    KzEmbedProcessPrivate *priv;

    priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);

    if (!org_kazehakase_Embed_title(priv->proxy, title, &error)) {
        g_print("%s\n", error->message);
        g_error_free(error);
    } 
}

static void
cb_location (KzEmbed *embed, const gchar *location, KzEmbedProcess *process)
{
    GError *error = NULL;
    KzEmbedProcessPrivate *priv;

    priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);

    if (!org_kazehakase_Embed_location(priv->proxy, location, &error)) {
        g_print("%s\n", error->message);
        g_error_free(error);
    } 
}

static void
cb_progress (KzEmbed *embed, gdouble progress, KzEmbedProcess *process)
{
    GError *error = NULL;
    KzEmbedProcessPrivate *priv;

    priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);

    if (!org_kazehakase_Embed_progress(priv->proxy, progress, &error)) {
        g_print("%s\n", error->message);
        g_error_free(error);
    } 
}

#define DEFINE_DOM_MOUSE_EVENT(type)                                                        \
static gint                                                                                 \
cb_dom_mouse_ ## type (KzEmbed *embed, KzEmbedEventMouse *event, KzEmbedProcess *process)   \
{                                                                                           \
    GError *error = NULL;                                                                   \
    gint ret = TRUE;                                                                        \
    KzEmbedProcessPrivate *priv;                                                            \
                                                                                            \
    priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);                                           \
                                                                                            \
    if (!org_kazehakase_Embed_dom_mouse_ ## type(priv->proxy,                               \
                                                 event->cinfo.context,                      \
                                                 event->cinfo.link,                         \
                                                 event->cinfo.linktext,                     \
                                                 event->cinfo.img,                          \
                                                 event->cinfo.frame_src,                    \
                                                 event->button,                             \
                                                 event->modifier,                           \
                                                 event->x,                                  \
                                                 event->y,                                  \
                                                 &ret,                                      \
                                                 &error)) {                                 \
        g_print("%s\n", error->message);                                                    \
        g_error_free(error);                                                                \
    }                                                                                       \
    return ret;                                                                             \
}

DEFINE_DOM_MOUSE_EVENT(down)
DEFINE_DOM_MOUSE_EVENT(up)
DEFINE_DOM_MOUSE_EVENT(click)

static void
connect_embed_dom_signals (KzEmbedProcess *process)
{
    KzEmbedProcessPrivate *priv;

    priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);

    g_signal_connect(priv->embed, "kz-dom-mouse-down", G_CALLBACK(cb_dom_mouse_down), process);
    g_signal_connect(priv->embed, "kz-dom-mouse-up", G_CALLBACK(cb_dom_mouse_up), process);
    g_signal_connect(priv->embed, "kz-dom-mouse-click", G_CALLBACK(cb_dom_mouse_click), process);
}

static void
disconnect_embed_dom_signals (KzEmbedProcess *process)
{
    KzEmbedProcessPrivate *priv;

    priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);

    g_signal_handlers_disconnect_by_func(priv->embed, G_CALLBACK(cb_dom_mouse_down), process);
    g_signal_handlers_disconnect_by_func(priv->embed, G_CALLBACK(cb_dom_mouse_up), process);
    g_signal_handlers_disconnect_by_func(priv->embed, G_CALLBACK(cb_dom_mouse_click), process);
}

static void
setup_embed_signals (GObject *process)
{
    KzEmbedProcessPrivate *priv;

    priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);

    g_signal_connect(priv->embed, "kz-title", G_CALLBACK(cb_title), process);
    g_signal_connect(priv->embed, "kz-location", G_CALLBACK(cb_location), process);
    g_signal_connect(priv->embed, "kz-net-start", G_CALLBACK(cb_net_start), process);
    g_signal_connect(priv->embed, "kz-net-stop", G_CALLBACK(cb_net_stop), process);
    g_signal_connect(priv->embed, "kz-progress", G_CALLBACK(cb_progress), process);
}

static gboolean
cb_visibility_notify (GtkWidget *widget, GdkEventVisibility *event, KzEmbedProcess *process)
{
    if (event->state == GDK_VISIBILITY_FULLY_OBSCURED)
        disconnect_embed_dom_signals(process);
    else
        connect_embed_dom_signals(process);
    return FALSE;
}

static void
cb_load_uri (DBusGProxy *proxy,
             const gchar *uri,
             KzEmbedProcess *process)
{
    KzEmbedProcessPrivate *priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);
    kz_embed_load_uri(priv->embed, uri);
}

static void
cb_reload (DBusGProxy *proxy, KzEmbedProcess *process)
{
    KzEmbedProcessPrivate *priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);
    kz_embed_reload(priv->embed, KZ_EMBED_RELOAD_NORMAL);
}

static void
cb_stop_load (DBusGProxy *proxy, KzEmbedProcess *process)
{
    KzEmbedProcessPrivate *priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);
    kz_embed_stop_load(priv->embed);
}

static void
cb_go_back (DBusGProxy *proxy, KzEmbedProcess *process)
{
    KzEmbedProcessPrivate *priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);
    kz_embed_go_back(priv->embed);
}

static void
cb_go_forward (DBusGProxy *proxy, KzEmbedProcess *process)
{
    KzEmbedProcessPrivate *priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);
    kz_embed_go_forward(priv->embed);
}

static void
cb_copy (DBusGProxy *proxy, KzEmbedProcess *process)
{
    KzEmbedProcessPrivate *priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);
    kz_embed_copy_selection(priv->embed);
}

static void
cb_cut (DBusGProxy *proxy, KzEmbedProcess *process)
{
    KzEmbedProcessPrivate *priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);
    kz_embed_cut_selection(priv->embed);
}

static void
cb_paste (DBusGProxy *proxy, KzEmbedProcess *process)
{
    KzEmbedProcessPrivate *priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);
    kz_embed_paste(priv->embed);
}

static void
cb_zoom (DBusGProxy *proxy, gdouble ratio, KzEmbedProcess *process)
{
    KzEmbedProcessPrivate *priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);
    kz_embed_zoom(priv->embed, ratio);
}

static void
cb_find (DBusGProxy *proxy,
         const gchar *keyword,
         gboolean backward,
         KzEmbedProcess *process)
{
    KzEmbedProcessPrivate *priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);
    kz_embed_find(priv->embed, keyword, backward);
}

static void
setup_dbus_signals (GObject *process)
{
    KzEmbedProcessPrivate *priv;

    priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);

    dbus_g_proxy_add_signal(priv->proxy, "LoadUri", G_TYPE_STRING, G_TYPE_INVALID);
    dbus_g_proxy_connect_signal(priv->proxy, "LoadUri", G_CALLBACK(cb_load_uri), process, NULL);
    dbus_g_proxy_add_signal(priv->proxy, "Reload", G_TYPE_INVALID);
    dbus_g_proxy_connect_signal(priv->proxy, "Reload", G_CALLBACK(cb_reload), process, NULL);
    dbus_g_proxy_add_signal(priv->proxy, "StopLoad", G_TYPE_INVALID);
    dbus_g_proxy_connect_signal(priv->proxy, "StopLoad", G_CALLBACK(cb_stop_load), process, NULL);
    dbus_g_proxy_add_signal(priv->proxy, "GoBack", G_TYPE_INVALID);
    dbus_g_proxy_connect_signal(priv->proxy, "GoBack", G_CALLBACK(cb_go_back), process, NULL);
    dbus_g_proxy_add_signal(priv->proxy, "GoForward", G_TYPE_INVALID);
    dbus_g_proxy_connect_signal(priv->proxy, "GoForward", G_CALLBACK(cb_go_forward), process, NULL);
    dbus_g_proxy_add_signal(priv->proxy, "Copy", G_TYPE_INVALID);
    dbus_g_proxy_connect_signal(priv->proxy, "Copy", G_CALLBACK(cb_copy), process, NULL);
    dbus_g_proxy_add_signal(priv->proxy, "Cut",  G_TYPE_INVALID);
    dbus_g_proxy_connect_signal(priv->proxy, "Cut", G_CALLBACK(cb_cut), process, NULL);
    dbus_g_proxy_add_signal(priv->proxy, "Paste", G_TYPE_INVALID);
    dbus_g_proxy_connect_signal(priv->proxy, "Paste", G_CALLBACK(cb_paste), process, NULL);
    dbus_g_proxy_add_signal(priv->proxy, "Zoom", G_TYPE_DOUBLE, G_TYPE_INVALID);
    dbus_g_proxy_connect_signal(priv->proxy, "Zoom", G_CALLBACK(cb_zoom), process, NULL);
    dbus_g_proxy_add_signal(priv->proxy, "Find", G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INVALID);
    dbus_g_proxy_connect_signal(priv->proxy, "Find", G_CALLBACK(cb_find), process, NULL);
}

static void
create_widget (guint32 process_id,
               guint32 embed_id,
               const gchar *engine_name,
               GObject *process)
{
    KzEmbedProcessPrivate *priv;
    GtkWidget *plug;

    if (getpid() != process_id)
        return;

    priv = KZ_EMBED_PROCESS_GET_PRIVATE(process);
    priv->embed_id = embed_id;

    plug = gtk_plug_new(embed_id);

    priv->embed = KZ_EMBED(kz_embed_new(engine_name, engine_name));
    gtk_container_add(GTK_CONTAINER(plug), GTK_WIDGET(priv->embed));
    gtk_widget_show(GTK_WIDGET(priv->embed));
    gtk_widget_show(plug);

    g_signal_connect(plug, "visibility-notify-event", G_CALLBACK(cb_visibility_notify), process);

    setup_embed_signals(process);
}

static void
setup_dbus (GObject *object)
{
    DBusGConnection *connection;
    GError *error = NULL;
    KzEmbedProcessPrivate *priv;
    gchar *address = NULL;
    gchar *engine_name = NULL;
    guint32 embed_id;

    priv = KZ_EMBED_PROCESS_GET_PRIVATE(object);

    connection = dbus_g_connection_open(priv->socket_address, &error);
    if (error) {
        g_print("Error:%s\n", error->message);
        g_error_free(error);
    } 

    priv->proxy = dbus_g_proxy_new_for_peer(connection,
                                            KZ_DBUS_EMBED_AGENT_PATH,
                                            KZ_DBUS_EMBED_AGENT_INTERFACE);

    dbus_g_object_register_marshaller(_kz_marshal_VOID__STRING_BOOLEAN, 
                                      G_TYPE_NONE,
                                      G_TYPE_STRING,
                                      G_TYPE_BOOLEAN,
                                      G_TYPE_INVALID);
    if (!org_kazehakase_Embed_ready(priv->proxy, getpid(), &address, &embed_id, &engine_name, &error)) {
        g_print("%s\n", error->message);
        g_error_free(error);
    } 

    create_widget(getpid(), embed_id, engine_name, object);
    setup_dbus_signals(object);
}

static void
set_property (GObject         *object,
              guint            prop_id,
              const GValue    *value,
              GParamSpec      *pspec)
{
    KzEmbedProcessPrivate *priv = KZ_EMBED_PROCESS_GET_PRIVATE(object);

    switch (prop_id)
    {
    case PROP_SOCKET_ADDRESS:
        priv->socket_address = g_value_dup_string(value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}


static void
get_property (GObject         *object,
              guint            prop_id,
              GValue          *value,
              GParamSpec      *pspec)
{
    KzEmbedProcessPrivate *priv = KZ_EMBED_PROCESS_GET_PRIVATE(object);

    switch (prop_id)
    {
    case PROP_SOCKET_ADDRESS:
        g_value_set_string(value, priv->socket_address);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}

static GObject*
constructor (GType                  type,
             guint                  n_props,
             GObjectConstructParam *props)
{
	GObject *object;
	GObjectClass *klass = G_OBJECT_CLASS(kz_embed_process_parent_class);

	object = klass->constructor(type, n_props, props);

    setup_dbus(object);

	return object;
}

static void
kz_embed_process_init (KzEmbedProcess *object)
{
    KzEmbedProcessPrivate *priv = KZ_EMBED_PROCESS_GET_PRIVATE(object);

    priv->embed_id = 0;
    priv->embed = NULL;
    priv->socket_address = NULL;
}

static KzEmbedProcess *
kz_embed_process_new (const gchar *socket_address)
{
    return g_object_new(KZ_TYPE_EMBED_PROCESS,
                        "socket-address", socket_address,
                        NULL);
}

int
main (int argc, char *argv[])
{
    KzApp *app;
    KzEmbedProcess *process;

    if (argc < 2)
        exit(EXIT_FAILURE);

    if (!g_thread_supported()) g_thread_init(NULL);
    dbus_g_thread_init();

    gtk_init(&argc, &argv);

    /* We need KzApp to obtain the directory in which embed modules is stored */
    app = kz_app_new(argc, argv);
    process = kz_embed_process_new(argv[1]);

    gtk_main();

    g_object_unref(process);
    g_object_unref(app);

    exit(EXIT_SUCCESS);
}

/* 
vi:ts=4:nowrap:ai:expandtab:sw=4
*/
