/* vim:set et sts=4: */

/*
 * KMFL Input Method for IBUS (The Input Bus)
 *
 * Copyright (C) 2009 SIL International
 *
 * This library 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 of the License, or (at your option) any later version.
 *
 * This library 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 *
 */

#include <ibus.h>
#include <string.h>
#include <stdio.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>

#include "kmflutil.h"
#include "engine.h"

typedef struct _IBusKMFLEngine IBusKMFLEngine;
typedef struct _IBusKMFLEngineClass IBusKMFLEngineClass;

struct _IBusKMFLEngine {
	IBusEngine parent;

    /* members */
    KInputContext   *context;
    IBusLookupTable *table;
    IBusProperty    *status_prop;
    IBusPropList    *prop_list;
    Display         *display;

};

struct _IBusKMFLEngineClass {
	IBusEngineClass parent;
};

/* functions prototype */
static void	ibus_kmfl_engine_class_init	    (IBusKMFLEngineClass    *klass);
static void	ibus_kmfl_engine_init		    (IBusKMFLEngine		    *kmfl);
static GObject*
            ibus_kmfl_engine_constructor    (GType                   type,
                                             guint                   n_construct_params,
                                             GObjectConstructParam  *construct_params);
static void	ibus_kmfl_engine_destroy		(IBusKMFLEngine		    *kmfl);
static gboolean
			ibus_kmfl_engine_process_key_event
                                            (IBusEngine             *engine,
                                             guint               	 keyval,
                                             guint               	 keycode,
                                             guint               	 state);
static void ibus_kmfl_engine_focus_in       (IBusEngine             *engine);
static void ibus_kmfl_engine_focus_out      (IBusEngine             *engine);
static void ibus_kmfl_engine_reset          (IBusEngine             *engine);
static void ibus_kmfl_engine_enable         (IBusEngine             *engine);
static void ibus_kmfl_engine_disable        (IBusEngine             *engine);
static void ibus_engine_set_cursor_location (IBusEngine             *engine,
                                             gint                    x,
                                             gint                    y,
                                             gint                    w,
                                             gint                    h);
static void ibus_kmfl_engine_set_capabilities
                                            (IBusEngine             *engine,
                                             guint                   caps);
static void ibus_kmfl_engine_page_up        (IBusEngine             *engine);
static void ibus_kmfl_engine_page_down      (IBusEngine             *engine);
static void ibus_kmfl_engine_cursor_up      (IBusEngine             *engine);
static void ibus_kmfl_engine_cursor_down    (IBusEngine             *engine);
static void ibus_kmfl_engine_property_activate
                                            (IBusEngine             *engine,
                                             const gchar            *prop_name,
                                             guint                   prop_state);
static void ibus_kmfl_engine_property_show
											(IBusEngine             *engine,
                                             const gchar            *prop_name);
static void ibus_kmfl_engine_property_hide
											(IBusEngine             *engine,
                                             const gchar            *prop_name);

static void ibus_kmfl_engine_commit_string
                                            (IBusKMFLEngine         *kmfl,
                                             const gchar            *string);

static IBusEngineClass *parent_class = NULL;
static GHashTable      *im_table = NULL;


GType
ibus_kmfl_engine_get_type (void)
{
	static GType type = 0;

	static const GTypeInfo type_info = {
		sizeof (IBusKMFLEngineClass),
		(GBaseInitFunc)		NULL,
		(GBaseFinalizeFunc) NULL,
		(GClassInitFunc)	ibus_kmfl_engine_class_init,
		NULL,
		NULL,
		sizeof (IBusKMFLEngine),
		0,
		(GInstanceInitFunc)	ibus_kmfl_engine_init,
	};

	if (type == 0) {
		type = g_type_register_static (IBUS_TYPE_ENGINE,
									   "IBusKMFLEngine",
									   &type_info,
									   (GTypeFlags) 0);
	}

	return type;
}

static void
ibus_kmfl_engine_class_init (IBusKMFLEngineClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
	IBusObjectClass *ibus_object_class = IBUS_OBJECT_CLASS (klass);
	IBusEngineClass *engine_class = IBUS_ENGINE_CLASS (klass);

	parent_class = (IBusEngineClass *) g_type_class_peek_parent (klass);

    object_class->constructor = ibus_kmfl_engine_constructor;
	ibus_object_class->destroy = (IBusObjectDestroyFunc) ibus_kmfl_engine_destroy;

    engine_class->process_key_event = ibus_kmfl_engine_process_key_event;

    engine_class->reset = ibus_kmfl_engine_reset;
    engine_class->enable = ibus_kmfl_engine_enable;
    engine_class->disable = ibus_kmfl_engine_disable;

    engine_class->focus_in = ibus_kmfl_engine_focus_in;
    engine_class->focus_out = ibus_kmfl_engine_focus_out;

    engine_class->page_up = ibus_kmfl_engine_page_up;
    engine_class->page_down = ibus_kmfl_engine_page_down;

    engine_class->cursor_up = ibus_kmfl_engine_cursor_up;
    engine_class->cursor_down = ibus_kmfl_engine_cursor_down;

    engine_class->property_activate = ibus_kmfl_engine_property_activate;
}

static void
ibus_kmfl_engine_init (IBusKMFLEngine *kmfl)
{
    kmfl->status_prop = ibus_property_new ("status",
                                           PROP_TYPE_NORMAL,
                                           NULL,
                                           NULL,
                                           NULL,
                                           TRUE,
                                           FALSE,
                                           0,
                                           NULL);
    g_object_ref_sink(kmfl->status_prop);
    kmfl->prop_list = ibus_prop_list_new ();
    g_object_ref_sink(kmfl->prop_list);
    ibus_prop_list_append (kmfl->prop_list,  kmfl->status_prop);

    kmfl->table = ibus_lookup_table_new (9, 0, TRUE, TRUE);
    g_object_ref_sink(kmfl->table);
    kmfl->context = NULL;
}

static GObject*
ibus_kmfl_engine_constructor (GType                   type,
                              guint                   n_construct_params,
                              GObjectConstructParam  *construct_params)
{
    IBusKMFLEngine *kmfl;
    KInputMethod *im;
    const gchar *engine_name;
    
    g_debug("DAR: ibus_kmfl_engine_constructor");
    
    kmfl = (IBusKMFLEngine *) G_OBJECT_CLASS (parent_class)->constructor (type,
                                                       n_construct_params,
                                                       construct_params);

    engine_name = ibus_engine_get_name ((IBusEngine *) kmfl);
    g_assert (engine_name);
    g_message("DAR: ibus_kmfl_engine_constructor %s", engine_name);

    if (im_table == NULL) {
        im_table = g_hash_table_new_full (g_str_hash,
                                          g_str_equal,
                                          g_free,
                                          (GDestroyNotify) kinput_close_im);
    }

    im = (KInputMethod *) g_hash_table_lookup (im_table, engine_name);

    if (im == NULL) {
        im = kinput_open_im(engine_name);
        
        if (im != NULL)
            g_hash_table_insert (im_table, g_strdup (engine_name), im);

    }

    if (im == NULL) {
        g_warning ("Can not find kmfl keymap %s", engine_name);
        g_object_unref (kmfl);
        return NULL;
    }

    kmfl->context = kmfl_create_ic (im);
    kmfl->display  = XOpenDisplay(NULL);

    return (GObject *) kmfl;
}


static void
ibus_kmfl_engine_destroy (IBusKMFLEngine *kmfl)
{
    const gchar *engine_name;
    
    g_debug("DAR: ibus_kmfl_engine_destroy");
    engine_name = ibus_engine_get_name ((IBusEngine *) kmfl);
    g_assert (engine_name);
    g_message("DAR: ibus_kmfl_engine_destroy %s", engine_name);

    if (kmfl->prop_list) {
        g_debug("DAR: unref kmfl->prop_list");
        g_object_unref (kmfl->prop_list);
        kmfl->prop_list = NULL;
    }

    if (kmfl->status_prop) {
        g_debug("DAR: unref kmfl->status_prop");
        g_object_unref (kmfl->status_prop);
        kmfl->status_prop = NULL;
    }

    if (kmfl->table) {
        g_debug("DAR: unref kmfl->table");
        g_object_unref (kmfl->table);
        kmfl->table = NULL;
    }

    if (kmfl->context) {
        kmfl_destroy_ic (kmfl->context);
        kmfl->context = NULL;
    }
    
    if (kmfl->display) {
        XCloseDisplay(kmfl->display);
        kmfl->display = NULL;
    }
    
    g_hash_table_remove(im_table, engine_name);
     
    IBUS_OBJECT_CLASS (parent_class)->destroy ((IBusObject *)kmfl);
}

static void
ibus_kmfl_engine_commit_string (IBusKMFLEngine *kmfl,
                                const gchar    *string)
{
    IBusText *text;
    g_debug("DAR: ibus_kmfl_engine_commit_string - %s", string);
    text = ibus_text_new_from_static_string (string);
    g_object_ref_sink(text);
    ibus_engine_commit_text ((IBusEngine *)kmfl, text);
    g_object_unref (text);
}

int is_key_pressed(Display * display, char *key_vec, KeySym keysym)
{
    unsigned char keycode;
    keycode = XKeysymToKeycode(display, keysym);
    return key_vec[keycode >> 3] & (1 << (keycode & 7));
}

static void forward_key(IBusKMFLEngine *engine, unsigned int keysym, unsigned int state)
{
    unsigned char keycode;
    g_debug("DAR: forward_key");
    keycode = XKeysymToKeycode(engine->display, keysym);
    ibus_engine_forward_key_event((IBusEngine *)engine, keysym, keycode-8, state);
 }

static gboolean
ibus_kmfl_engine_process_key_event (IBusEngine     *engine,
                                    guint           keyval,
                                    guint           keycode,
                                    guint           state)
{
    IBusKMFLEngine *kmfl = (IBusKMFLEngine *) engine;
    
    if (state & IBUS_RELEASE_MASK)
        return FALSE;

    g_debug("DAR: ibus_kmfl_engine_process_key_event - keyval=%x, keycode=%x, state=%x", keyval, keycode, state);
    
    // If a modifier key is pressed, check to see if it is a right modifier key
    // This is rather expensive so only do it if a shift state is active
    int right_modifier_state = 0;    
    if (state & (IBUS_SHIFT_MASK | IBUS_CONTROL_MASK | IBUS_MOD1_MASK)) {
        Display * m_display  = XOpenDisplay(NULL);;
        char key_vec[32];
        XQueryKeymap(m_display, key_vec);

        if ((state & IBUS_MOD1_MASK) && is_key_pressed(m_display, key_vec, IBUS_Alt_R)) {
            right_modifier_state |= (IBUS_MOD1_MASK << 8);
        }

        if ((state & IBUS_CONTROL_MASK) && is_key_pressed(m_display, key_vec, IBUS_Control_R)) {
            right_modifier_state |= (IBUS_CONTROL_MASK << 8);
        }

        if ((state & IBUS_SHIFT_MASK) && is_key_pressed(m_display, key_vec, IBUS_Shift_R)) {
            right_modifier_state |= (IBUS_SHIFT_MASK << 8);
        }
        XCloseDisplay(m_display);
    }
    g_debug("DAR: ibus_kmfl_engine_process_key_event - right_modifier_state=%x", right_modifier_state);
    // Let the application handle user generated backspaces after resetting the kmfl history
    if (keyval == IBUS_BackSpace) {
        clear_history(kmfl->context->p_kmsi);
    } else if (kmfl_interpret(kmfl->context->p_kmsi, keyval, state | right_modifier_state) == 1) {
        GList * p;
        for (p=kmfl->context->cmds; p != NULL; p = p->next) {
            Cmd * cmd = (Cmd *) p->data;
            if (cmd) {
                switch (cmd->opcode) {
                    case OUTPUT_STRING:
                        ibus_kmfl_engine_commit_string(kmfl, cmd->cmdarg);
                        break;
                    case ERASE_CHAR:
		        g_debug("DAR: ibus_kmfl_engine_process_key_event - client_capabilities=%x, %x, %x", engine->client_capabilities,  IBUS_CAP_SURROUNDING_TEXT, engine->client_capabilities & IBUS_CAP_SURROUNDING_TEXT);

                        if ((engine->client_capabilities & IBUS_CAP_SURROUNDING_TEXT) != 0) {
                            ibus_engine_delete_surrounding_text(engine, -1, 1);
                            if ((engine->client_capabilities & IBUS_CAP_SURROUNDING_TEXT) == 0) {
                                forward_key(kmfl, IBUS_BackSpace, 0);
                            }
                        } else 
                            forward_key(kmfl, IBUS_BackSpace, 0);
                        break;
                    case FORWARD_KEYEVENT:
                        break;
                    case OUTPUT_BEEP:
                        break;
                }
                if (cmd->cmdarg)
                    g_free(cmd->cmdarg);
                g_free(cmd);
            }
        }
        g_list_free(kmfl->context->cmds);
        kmfl->context->cmds=NULL;
        return TRUE;
    }
    
    return FALSE;
 }

static void
ibus_kmfl_engine_focus_in (IBusEngine *engine)
{
    IBusKMFLEngine *kmfl = (IBusKMFLEngine *) engine;

    ibus_engine_register_properties (engine, kmfl->prop_list);

    parent_class->focus_in (engine);
}

static void
ibus_kmfl_engine_focus_out (IBusEngine *engine)
{
    IBusKMFLEngine *kmfl = (IBusKMFLEngine *) engine;

    parent_class->focus_out (engine);
}

static void
ibus_kmfl_engine_reset (IBusEngine *engine)
{
    IBusKMFLEngine *kmfl = (IBusKMFLEngine *) engine;

    parent_class->reset (engine);
    ibus_kmfl_engine_focus_in (engine);
}

static void
ibus_kmfl_engine_enable (IBusEngine *engine)
{
    const gchar *engine_name;
    IBusKMFLEngine *kmfl = (IBusKMFLEngine *) engine;
    KInputMethod *im;

    engine_name = ibus_engine_get_name (engine);
    g_assert (engine_name);
    g_message("WDG: ibus_kmfl_engine_enable %s", engine_name);
    parent_class->enable (engine);
}

static void
ibus_kmfl_engine_disable (IBusEngine *engine)
{
    const gchar *engine_name;
    IBusKMFLEngine *kmfl = (IBusKMFLEngine *) engine;

    engine_name = ibus_engine_get_name (engine);
    g_assert (engine_name);
    g_message("WDG: ibus_kmfl_engine_disable %s", engine_name);
    ibus_kmfl_engine_focus_out (engine);

    parent_class->disable (engine);
}

static void
ibus_kmfl_engine_page_up (IBusEngine *engine)
{
    IBusKMFLEngine *kmfl = (IBusKMFLEngine *) engine;

    parent_class->page_up (engine);
}

static void
ibus_kmfl_engine_page_down (IBusEngine *engine)
{

    IBusKMFLEngine *kmfl = (IBusKMFLEngine *) engine;

    parent_class->page_down (engine);
}

static void
ibus_kmfl_engine_cursor_up (IBusEngine *engine)
{

    IBusKMFLEngine *kmfl = (IBusKMFLEngine *) engine;

    parent_class->cursor_up (engine);
}

static void
ibus_kmfl_engine_cursor_down (IBusEngine *engine)
{

    IBusKMFLEngine *kmfl = (IBusKMFLEngine *) engine;

    parent_class->cursor_down (engine);
}

static void
ibus_kmfl_engine_property_activate (IBusEngine  *engine,
                                    const gchar *prop_name,
                                    guint        prop_state)
{
    parent_class->property_activate (engine, prop_name, prop_state);
}

