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

/*
 * Keyman Input Method for IBUS (The Input Bus)
 *
 * Copyright (C) 2009-2018 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 <gdk/gdk.h>
#include <keyman/keyboardprocessor.h>

#include "keymanutil.h"
#include "keyman-service.h"
#include "engine.h"

#define MAXCONTEXT_ITEMS 128
#define KEYMAN_BACKSPACE 14
#define KEYMAN_BACKSPACE_KEYSYM 0xff08
#define KEYMAN_LCTRL 29
#define KEYMAN_LALT 56
#define KEYMAN_RCTRL 97
#define KEYMAN_RALT 100

typedef struct _IBusKeymanEngine IBusKeymanEngine;
typedef struct _IBusKeymanEngineClass IBusKeymanEngineClass;

struct _IBusKeymanEngine {
	IBusEngine parent;

    /* members */
    km_kbp_keyboard *keyboard;
    km_kbp_state    *state;
    gchar           *ldmlfile;
    gchar           *kb_name;
    gchar           *char_buffer;
    gunichar         firstsurrogate;
    gboolean         lctrl_pressed;
    gboolean         rctrl_pressed;
    gboolean         lalt_pressed;
    gboolean         ralt_pressed;
    gboolean         emitting_keystroke;
    IBusLookupTable *table;
    IBusProperty    *status_prop;
    IBusPropList    *prop_list;
};

struct _IBusKeymanEngineClass {
	IBusEngineClass parent;
};

/* functions prototype */
static void	ibus_keyman_engine_class_init	    (IBusKeymanEngineClass    *klass);
static void	ibus_keyman_engine_init		    (IBusKeymanEngine		    *kmfl);
static GObject*
            ibus_keyman_engine_constructor    (GType                   type,
                                             guint                   n_construct_params,
                                             GObjectConstructParam  *construct_params);
static void	ibus_keyman_engine_destroy		(IBusKeymanEngine		    *kmfl);
static gboolean
			ibus_keyman_engine_process_key_event
                                            (IBusEngine             *engine,
                                             guint               	 keyval,
                                             guint               	 keycode,
                                             guint               	 state);
static void ibus_keyman_engine_focus_in       (IBusEngine             *engine);
static void ibus_keyman_engine_focus_out      (IBusEngine             *engine);
static void ibus_keyman_engine_reset          (IBusEngine             *engine);
static void ibus_keyman_engine_enable         (IBusEngine             *engine);
static void ibus_keyman_engine_disable        (IBusEngine             *engine);
static void ibus_keyman_engine_set_surrounding_text(
                                             IBusEngine               *engine,
                                             IBusText                  *text,
                                             guint                     cursor_pos,
                                             guint                     anchor_pos);
// static void ibus_keyman_engine_set_cursor_location (IBusEngine             *engine,
//                                              gint                    x,
//                                              gint                    y,
//                                              gint                    w,
//                                              gint                    h);
static void ibus_keyman_engine_set_capabilities(
                                            IBusEngine             *engine,
                                             guint                   caps);
// static void ibus_keyman_engine_page_up        (IBusEngine             *engine);
// static void ibus_keyman_engine_page_down      (IBusEngine             *engine);
// static void ibus_keyman_engine_cursor_up      (IBusEngine             *engine);
// static void ibus_keyman_engine_cursor_down    (IBusEngine             *engine);
static void ibus_keyman_engine_property_activate
                                            (IBusEngine             *engine,
                                             const gchar            *prop_name,
                                             guint                   prop_state);
static void ibus_keyman_engine_property_show
											(IBusEngine             *engine,
                                             const gchar            *prop_name);
static void ibus_keyman_engine_property_hide
											(IBusEngine             *engine,
                                             const gchar            *prop_name);

static void ibus_keyman_engine_commit_string
                                            (IBusKeymanEngine         *kmfl,
                                             const gchar            *string);

static IBusEngineClass *parent_class = NULL;

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

	static const GTypeInfo type_info = {
		sizeof (IBusKeymanEngineClass),
		(GBaseInitFunc)		NULL,
		(GBaseFinalizeFunc) NULL,
		(GClassInitFunc)	ibus_keyman_engine_class_init,
		NULL,
		NULL,
		sizeof (IBusKeymanEngine),
		0,
		(GInstanceInitFunc)	ibus_keyman_engine_init,
	};

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

	return type;
}

static void
ibus_keyman_engine_class_init (IBusKeymanEngineClass *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_keyman_engine_constructor;
	ibus_object_class->destroy = (IBusObjectDestroyFunc) ibus_keyman_engine_destroy;

    engine_class->process_key_event = ibus_keyman_engine_process_key_event;

    engine_class->reset = ibus_keyman_engine_reset;
    engine_class->enable = ibus_keyman_engine_enable;
    engine_class->disable = ibus_keyman_engine_disable;

    engine_class->set_surrounding_text = ibus_keyman_engine_set_surrounding_text;
    // engine_class->set_cursor_location = ibus_keyman_engine_set_cursor_location;


    engine_class->focus_in = ibus_keyman_engine_focus_in;
    engine_class->focus_out = ibus_keyman_engine_focus_out;

    // engine_class->page_up = ibus_keyman_engine_page_up;
    // engine_class->page_down = ibus_keyman_engine_page_down;

    // engine_class->cursor_up = ibus_keyman_engine_cursor_up;
    // engine_class->cursor_down = ibus_keyman_engine_cursor_down;

    engine_class->property_activate = ibus_keyman_engine_property_activate;
}

static gchar *get_current_context_text(km_kbp_context *context)
{
    size_t buf_size = 512;
    km_kbp_context_item *context_items;
    gchar *current_context_utf8 = g_new0(gchar, buf_size);
    if (km_kbp_context_get(context, &context_items) == KM_KBP_STATUS_OK) {
        km_kbp_context_items_to_utf8(context_items,
                            current_context_utf8,
                            &buf_size);
    }
    km_kbp_context_items_dispose(context_items);
    g_message("current context is:%lu:%lu:%s:", km_kbp_context_length(context), buf_size, current_context_utf8);
    return current_context_utf8;
}

static void reset_context(IBusEngine *engine)
{
    IBusKeymanEngine *keyman = (IBusKeymanEngine *) engine;
    IBusText *text;
    gchar *surrounding_text, *current_context_utf8;
    guint cursor_pos, anchor_pos, context_start, context_pos;
    km_kbp_context_item *context_items;
    km_kbp_context *context;

    g_message("reset_context");
    keyman->firstsurrogate = 0;
    context = km_kbp_state_context(keyman->state);
    if ((engine->client_capabilities & IBUS_CAP_SURROUNDING_TEXT) != 0)
    {
        current_context_utf8 = get_current_context_text(context);

        ibus_engine_get_surrounding_text(engine, &text, &cursor_pos, &anchor_pos);
        context_pos = anchor_pos < cursor_pos ? anchor_pos : cursor_pos;
        context_start = context_pos > MAXCONTEXT_ITEMS ? context_pos - MAXCONTEXT_ITEMS : 0;
        surrounding_text = g_utf8_substring(ibus_text_get_text(text), context_start, context_pos);
        g_message("new context is:%u:%s: cursor:%d anchor:%d", context_pos - context_start, surrounding_text, cursor_pos, anchor_pos);

        g_message(":%s:%s:", surrounding_text, current_context_utf8);
        if (g_strcmp0(surrounding_text, current_context_utf8) != 0)
        {
            g_message("setting context because it has changed from expected");
            if (km_kbp_context_items_from_utf8(surrounding_text, &context_items) == KM_KBP_STATUS_OK) {
                km_kbp_context_set(context, context_items);
            }
            km_kbp_context_items_dispose(context_items);
        }
        g_free(surrounding_text);
        g_free(current_context_utf8);
    }
    else {
        km_kbp_context_clear(context);
    }
}

static void
ibus_keyman_engine_init (IBusKeymanEngine *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->state = NULL;
}

static GObject*
ibus_keyman_engine_constructor (GType                   type,
                              guint                   n_construct_params,
                              GObjectConstructParam  *construct_params)
{
    IBusKeymanEngine *keyman;
    IBusEngine *engine;
    IBusText *text;
    const gchar *engine_name;
    gchar *surrounding_text, *p, *abs_kmx_path;
    guint cursor_pos, anchor_pos;
    km_kbp_context_item *context_items;
    
    g_debug("DAR: ibus_keyman_engine_constructor");
    
    keyman = (IBusKeymanEngine *) G_OBJECT_CLASS (parent_class)->constructor (type,
                                                       n_construct_params,
                                                       construct_params);

    engine = (IBusEngine *) keyman;
    engine_name = ibus_engine_get_name (engine);
    g_assert (engine_name);
    g_message("DAR: ibus_keyman_engine_constructor %s", engine_name);

    keyman->kb_name = NULL;
    keyman->ldmlfile = NULL;
    keyman->firstsurrogate = 0;
    keyman->lalt_pressed = FALSE;
    keyman->lctrl_pressed = FALSE;
    keyman->ralt_pressed = FALSE;
    keyman->rctrl_pressed = FALSE;
    keyman->emitting_keystroke = FALSE;
    gchar **split_name = g_strsplit(engine_name, ":", 2);
    if (split_name[0] == NULL)
    {
        IBUS_OBJECT_CLASS (parent_class)->destroy ((IBusObject *)keyman);
        return NULL;
    }
    else if (split_name[1] == NULL)
    {
        abs_kmx_path = g_strdup(split_name[0]);
    }
    else
    {
        abs_kmx_path = g_strdup(split_name[1]);
    }

    g_strfreev(split_name);

    gchar *kmx_file = g_path_get_basename(abs_kmx_path);
    p = rindex(kmx_file, '.'); // get id to use as dbus service name
    if (p) {
        keyman->kb_name = g_strndup(kmx_file, p-kmx_file);
        p = rindex(abs_kmx_path, '.');
        if (p)
        {
            gchar *dir = g_path_get_dirname(abs_kmx_path);
            gchar *ldmlfile = g_strdup_printf("%s/%s.ldml", dir, keyman->kb_name);
            if (g_file_test(ldmlfile, G_FILE_TEST_EXISTS))
            {
                keyman->ldmlfile = g_strdup(ldmlfile);
            }
            g_free(dir);
            g_free(ldmlfile);
        }
    }
    g_free(kmx_file);

    km_kbp_option_item *keyboard_opts = g_new0(km_kbp_option_item, 4);

    keyboard_opts[0].scope = KM_KBP_OPT_ENVIRONMENT;
    km_kbp_cp *cp = g_utf8_to_utf16 ("platform", -1, NULL, NULL, NULL);
    keyboard_opts[0].key = cp;
    cp = g_utf8_to_utf16 ("linux desktop hardware native", -1, NULL, NULL, NULL);
    keyboard_opts[0].value = cp;

    keyboard_opts[1].scope = KM_KBP_OPT_ENVIRONMENT;
    cp = g_utf8_to_utf16 ("baseLayout", -1, NULL, NULL, NULL);
    keyboard_opts[1].key = cp;
    cp = g_utf8_to_utf16 ("kbdus.dll", -1, NULL, NULL, NULL);
    keyboard_opts[1].value = cp;

    keyboard_opts[2].scope = KM_KBP_OPT_ENVIRONMENT;
    cp = g_utf8_to_utf16 ("baseLayoutAlt", -1, NULL, NULL, NULL);
    keyboard_opts[2].key = cp;
    #if 0 // in the future when mnemonic layouts are to be supported
    const gchar *lang_env = g_getenv("LANG");
    gchar *lang;
    if (lang_env != NULL) {
        g_message("LANG=%s", lang_env);
        gchar **splitlang = g_strsplit(lang_env, ".", 2);
        g_message("before . is %s", splitlang[0]);
        if (g_strrstr(splitlang[0], "_")) {
            g_message("splitting %s", splitlang[0]);
            gchar **taglang = g_strsplit(splitlang[0], "_", 2);
            g_message("lang of tag is %s", taglang[0]);
            g_message("country of tag is %s", taglang[1]);
            lang = g_strjoin("-", taglang[0], taglang[1], NULL);
            g_strfreev(taglang);
        }
        else {
            lang = g_strdup(splitlang[0]);
        }
        g_strfreev(splitlang);
    }
    else {
        lang = strdup("en-US");
    }
    g_message("lang is %s", lang);
    #endif
    cp = g_utf8_to_utf16 ("en-US", -1, NULL, NULL, NULL);
    // g_free(lang);
    keyboard_opts[2].value = cp;

    // keyboard_opts[3] already initialised to {0, 0, 0}

    km_kbp_status status_keyboard = km_kbp_keyboard_load(abs_kmx_path, &(keyman->keyboard));
    g_free(abs_kmx_path);

    if (status_keyboard != KM_KBP_STATUS_OK)
    {
        g_warning("problem creating km_kbp_keyboard");
    }

    km_kbp_status status_state = km_kbp_state_create(keyman->keyboard,
                                  keyboard_opts,
                                  &(keyman->state));
    if (status_state != KM_KBP_STATUS_OK)
    {
        g_warning("problem creating km_kbp_state");
    }
    for (int i =0; i < 3; i++) {
        g_free((km_kbp_cp *)keyboard_opts[i].key);
        g_free((km_kbp_cp *)keyboard_opts[i].value);
    }
    g_free(keyboard_opts);

    reset_context(engine);

    return (GObject *) keyman;
}


static void
ibus_keyman_engine_destroy (IBusKeymanEngine *keyman)
{
    const gchar *engine_name;
    
    g_debug("DAR: ibus_keyman_engine_destroy");
    engine_name = ibus_engine_get_name ((IBusEngine *) keyman);
    g_assert (engine_name);
    g_message("DAR: ibus_keyman_engine_destroy %s", engine_name);

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

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

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

    if (keyman->keyboard) {
        km_kbp_keyboard_dispose(keyman->keyboard);
        keyman->keyboard = NULL;
    }

    g_free(keyman->kb_name);
    g_free(keyman->ldmlfile);
     
    IBUS_OBJECT_CLASS (parent_class)->destroy ((IBusObject *)keyman);
}

static void
ibus_keyman_engine_commit_string (IBusKeymanEngine *kmfl,
                                const gchar    *string)
{
    IBusText *text;
    g_message("DAR: ibus_keyman_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);
}

static void forward_backspace(IBusKeymanEngine *engine, unsigned int state)
{
    g_message("DAR: forward_backspace %d no keysym state %d", KEYMAN_BACKSPACE, state);
    ibus_engine_forward_key_event((IBusEngine *)engine, KEYMAN_BACKSPACE_KEYSYM, KEYMAN_BACKSPACE, state);
}

// from android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMHardwareKeyboardInterpreter.java
// uses kernel keycodes which are X11 keycode - 8
//private static final
//  int scanCodeMap[] = {
static km_kbp_virtual_key const keycode_to_vk[256] = {
    0,      //        padding = 0x00;
    KM_KBP_VKEY_ESC,      //        public static final int KEY_ESC = 0x01;
    KM_KBP_VKEY_1,    //        public static final int KEY_1 = 0x02;
    KM_KBP_VKEY_2,    //        public static final int KEY_2 = 0x03;
    KM_KBP_VKEY_3,    //        public static final int KEY_3 = 0x04;
    KM_KBP_VKEY_4,    //        public static final int KEY_4 = 0x05;
    KM_KBP_VKEY_5,    //        public static final int KEY_5 = 0x06;
    KM_KBP_VKEY_6,    //        public static final int KEY_6 = 0x07;
    KM_KBP_VKEY_7,    //        public static final int KEY_7 = 0x08;
    KM_KBP_VKEY_8,    //        public static final int KEY_8 = 0x09;
    KM_KBP_VKEY_9,    //        public static final int KEY_9 = 0x0A;
    KM_KBP_VKEY_0,    //        public static final int KEY_0 = 0x0B;
    KM_KBP_VKEY_HYPHEN,    //        public static final int KEY_MINUS = 0x0C;
    KM_KBP_VKEY_EQUAL,    //        public static final int KEY_EQUALS = 0x0D;
    KM_KBP_VKEY_BKSP,      //        public static final int KEY_BACKSPACE = 0x0E;
    KM_KBP_VKEY_TAB,      //        public static final int KEY_TAB = 0x0F;
    KM_KBP_VKEY_Q,    //        public static final int KEY_Q = 0x10;
    KM_KBP_VKEY_W,    //        public static final int KEY_W = 0x11;
    KM_KBP_VKEY_E,    //        public static final int KEY_E = 0x12;
    KM_KBP_VKEY_R,    //        public static final int KEY_R = 0x13;
    KM_KBP_VKEY_T,    //        public static final int KEY_T = 0x14;
    KM_KBP_VKEY_Y,    //        public static final int KEY_Y = 0x15;
    KM_KBP_VKEY_U,    //        public static final int KEY_U = 0x16;
    KM_KBP_VKEY_I,    //        public static final int KEY_I = 0x17;
    KM_KBP_VKEY_O,    //        public static final int KEY_O = 0x18;
    KM_KBP_VKEY_P,    //        public static final int KEY_P = 0x19;
    KM_KBP_VKEY_LBRKT,    //        public static final int KEY_LEFTBRACE = 0x1A;
    KM_KBP_VKEY_RBRKT,    //        public static final int KEY_RIGHTBRACE = 0x1B;
    KM_KBP_VKEY_ENTER,     //        public static final int KEY_ENTER = 0x1C;
    0,      //        public static final int KEY_LEFTCTRL = 0x1D;
    KM_KBP_VKEY_A,    //        public static final int KEY_A = 0x1E;
    KM_KBP_VKEY_S,    //        public static final int KEY_S = 0x1F;
    KM_KBP_VKEY_D,    //        public static final int KEY_D = 0x20;
    KM_KBP_VKEY_F,    //        public static final int KEY_F = 0x21;
    KM_KBP_VKEY_G,    //        public static final int KEY_G = 0x22;
    KM_KBP_VKEY_H,    //        public static final int KEY_H = 0x23;
    KM_KBP_VKEY_J,    //        public static final int KEY_J = 0x24;
    KM_KBP_VKEY_K,    //        public static final int KEY_K = 0x25;
    KM_KBP_VKEY_L,    //        public static final int KEY_L = 0x26;
    KM_KBP_VKEY_COLON,    //        public static final int KEY_SEMICOLON = 0x27;
    KM_KBP_VKEY_QUOTE,    //        public static final int KEY_APOSTROPHE = 0x28;
    KM_KBP_VKEY_BKQUOTE,    //        public static final int KEY_GRAVE = 0x29;
    0,      //        public static final int KEY_LEFTSHIFT = 0x2A;
    KM_KBP_VKEY_BKSLASH,    //        public static final int KEY_BACKSLASH = 0x2B;
    KM_KBP_VKEY_Z,    //        public static final int KEY_Z = 0x2C;
    KM_KBP_VKEY_X,    //        public static final int KEY_X = 0x2D;
    KM_KBP_VKEY_C,    //        public static final int KEY_C = 0x2E;
    KM_KBP_VKEY_V,    //        public static final int KEY_V = 0x2F;
    KM_KBP_VKEY_B,    //        public static final int KEY_B = 0x30;
    KM_KBP_VKEY_N,    //        public static final int KEY_N = 0x31;
    KM_KBP_VKEY_M,    //        public static final int KEY_M = 0x32;
    KM_KBP_VKEY_COMMA,    //        public static final int KEY_COMMA = 0x33;
    KM_KBP_VKEY_PERIOD,    //        public static final int KEY_DOT = 0x34;
    KM_KBP_VKEY_SLASH,    //        public static final int KEY_SLASH = 0x35;
    0,      //        public static final int KEY_RIGHTSHIFT = 0x36;
    KM_KBP_VKEY_NPSTAR,      //        public static final int KEY_KPASTERISK = 0x37;
    0,      //        public static final int KEY_LEFTALT = 0x38;
    KM_KBP_VKEY_SPACE,     //        public static final int KEY_SPACE = 0x39;
    0,      //        public static final int KEY_CAPSLOCK = 0x3A;
    KM_KBP_VKEY_F1,      //        public static final int KEY_F1 = 0x3B;
    KM_KBP_VKEY_F2,      //        public static final int KEY_F2 = 0x3C;
    KM_KBP_VKEY_F3,      //        public static final int KEY_F3 = 0x3D;
    KM_KBP_VKEY_F4,      //        public static final int KEY_F4 = 0x3E;
    KM_KBP_VKEY_F5,      //        public static final int KEY_F5 = 0x3F;
    KM_KBP_VKEY_F6,      //        public static final int KEY_F6 = 0x40;
    KM_KBP_VKEY_F7,      //        public static final int KEY_F7 = 0x41;
    KM_KBP_VKEY_F8,      //        public static final int KEY_F8 = 0x42;
    KM_KBP_VKEY_F9,      //        public static final int KEY_F9 = 0x43;
    KM_KBP_VKEY_F10,      //        public static final int KEY_F10 = 0x44;
    0,      //        public static final int KEY_NUMLOCK = 0x45;
    0,      //        public static final int KEY_SCROLLLOCK = 0x46;
    KM_KBP_VKEY_NP7,      //        public static final int KEY_KP7 = 0x47;
    KM_KBP_VKEY_NP8,      //        public static final int KEY_KP8 = 0x48;
    KM_KBP_VKEY_NP9,      //        public static final int KEY_KP9 = 0x49;
    KM_KBP_VKEY_NPMINUS,      //        public static final int KEY_KPMINUS = 0x4A;
    KM_KBP_VKEY_NP4,      //        public static final int KEY_KP4 = 0x4B;
    KM_KBP_VKEY_NP5,      //        public static final int KEY_KP5 = 0x4C;
    KM_KBP_VKEY_NP6,      //        public static final int KEY_KP6 = 0x4D;
    KM_KBP_VKEY_NPPLUS,      //        public static final int KEY_KPPLUS = 0x4E;
    KM_KBP_VKEY_NP1,      //        public static final int KEY_KP1 = 0x4F;
    KM_KBP_VKEY_NP2,      //        public static final int KEY_KP2 = 0x50;
    KM_KBP_VKEY_NP3,      //        public static final int KEY_KP3 = 0x51;
    KM_KBP_VKEY_NP0,      //        public static final int KEY_KP0 = 0x52;
    KM_KBP_VKEY_NPDOT,      //        public static final int KEY_KPDOT = 0x53;
    0,      //        padding 0x54;
    0,      //        public static final int KEY_ZENKAKUHANKAKU = 0x55;
    KM_KBP_VKEY_oE2,     //        public static final int KEY_102ND = 0x56;
    // additional on linux
    KM_KBP_VKEY_F11,      //        public static final int KEY_F11 = 0x57;
    KM_KBP_VKEY_F12      //        public static final int KEY_F12 = 0x58;

    // Many more KEYS currently not used by KMW...
  };

static gboolean ok_for_single_backspace(const km_kbp_action_item *action_items, int i, size_t num_actions)
{
    for (int j=i+1; j < num_actions; j++) {
        if (action_items[i].type == KM_KBP_IT_BACK || action_items[i].type == KM_KBP_IT_CHAR || action_items[i].type == KM_KBP_IT_EMIT_KEYSTROKE) {
            return FALSE;
        }
    }
    return TRUE;
}

static gboolean
ibus_keyman_engine_process_key_event (IBusEngine     *engine,
                                    guint           keyval,
                                    guint           keycode,
                                    guint           state)
{
    IBusKeymanEngine *keyman = (IBusKeymanEngine *) engine;

    g_message("DAR: ibus_keyman_engine_process_key_event - keyval=%02i, keycode=%02i, state=%02x", keyval, keycode, state);

    if (state & IBUS_RELEASE_MASK)
    {
        switch(keycode) {
            case KEYMAN_LCTRL:
                keyman->lctrl_pressed = FALSE;
                break;
            case KEYMAN_RCTRL:
                keyman->rctrl_pressed = FALSE;
                break;
            case KEYMAN_LALT:
                keyman->lalt_pressed = FALSE;
                break;
            case KEYMAN_RALT:
                keyman->ralt_pressed = FALSE;
                break;
            default:
                break;
        }
        return FALSE;
    }

    if (keycode < 0 || keycode > 255)
    {
        g_warning("keycode %d out of range", keycode);
        return FALSE;
    }

    if (keycode_to_vk[keycode] == 0) // key we don't handle
    {
        //save if a possible Ctrl or Alt modifier
        switch(keycode) {
            case KEYMAN_LCTRL:
                keyman->lctrl_pressed = TRUE;
                break;
            case KEYMAN_RCTRL:
                keyman->rctrl_pressed = TRUE;
                break;
            case KEYMAN_LALT:
                keyman->lalt_pressed = TRUE;
                break;
            case KEYMAN_RALT:
                keyman->ralt_pressed = TRUE;
                break;
            default:
                break;
        }
        return FALSE;
    }

    // keyman modifiers are different from X11/ibus
    uint16_t km_mod_state = 0;
    if (state & IBUS_SHIFT_MASK)
    {
        km_mod_state |= KM_KBP_MODIFIER_SHIFT;
    }
    if (state & IBUS_MOD5_MASK)
    {
        km_mod_state |= KM_KBP_MODIFIER_RALT;
        g_message("modstate KM_KBP_MODIFIER_RALT from IBUS_MOD5_MASK");
    }
    if (state & IBUS_MOD1_MASK)
    {
        if (keyman->ralt_pressed) {
            km_mod_state |= KM_KBP_MODIFIER_RALT;
            g_message("modstate KM_KBP_MODIFIER_RALT from ralt_pressed");
        }
        if (keyman->lalt_pressed) {
            km_mod_state |= KM_KBP_MODIFIER_LALT;
            g_message("modstate KM_KBP_MODIFIER_LALT from lalt_pressed");
        }
    }
    if (state & IBUS_CONTROL_MASK)
    {
        if (keyman->rctrl_pressed) {
            km_mod_state |= KM_KBP_MODIFIER_RCTRL;
            g_message("modstate KM_KBP_MODIFIER_RCTRL from rctrl_pressed");
        }
        if (keyman->lctrl_pressed) {
            km_mod_state |= KM_KBP_MODIFIER_LCTRL;
            g_message("modstate KM_KBP_MODIFIER_LCTRL from lctrl_pressed");
        }
    }
    g_message("before process key event");
    km_kbp_context *context = km_kbp_state_context(keyman->state);
    g_free(get_current_context_text(context));
    g_message("DAR: ibus_keyman_engine_process_key_event - km_mod_state=%x", km_mod_state);
    km_kbp_status event_status = km_kbp_process_event(keyman->state,
                                   keycode_to_vk[keycode], km_mod_state);
    context = km_kbp_state_context(keyman->state);
    g_message("after process key event");
    g_free(get_current_context_text(context));

    // km_kbp_state_action_items to get action items
    size_t num_action_items;
    gint numbytes;
    g_free(keyman->char_buffer);
    keyman->char_buffer = NULL;
    const km_kbp_action_item *action_items = km_kbp_state_action_items(keyman->state,
                                                     &num_action_items);

    for (int i = 0; i < num_action_items; i++)
    {
        switch(action_items[i].type)
        {
            case KM_KBP_IT_CHAR:
                g_message("CHAR action %d/%d", i+1, (int)num_action_items);
                if (g_unichar_type(action_items[i].character) == G_UNICODE_SURROGATE) {
                    if (keyman->firstsurrogate == 0) {
                        keyman->firstsurrogate = action_items[i].character;
                        g_message("first surrogate %d", keyman->firstsurrogate);
                    }
                    else {
                        glong items_read, items_written;
                        gunichar2 utf16_pair[2] = {keyman->firstsurrogate, action_items[i].character};
                        gchar *utf8_pair = g_utf16_to_utf8 (utf16_pair, 2,
                            &items_read,
                            &items_written,
                            NULL);
                        if (keyman->char_buffer == NULL) {
                            keyman->char_buffer = utf8_pair;
                        }
                        else {
                            gchar *new_buffer = g_strjoin("", keyman->char_buffer, utf8_pair, NULL);
                            g_free(keyman->char_buffer);
                            g_free(utf8_pair);
                            keyman->char_buffer = new_buffer;
                        }
                        keyman->firstsurrogate = 0;
                    }
                }
                else {
                    gchar *utf8 = (gchar *) g_new0(gchar, 12);
                    numbytes = g_unichar_to_utf8(action_items[i].character, utf8);
                    if (numbytes > 12) {
                        g_error("g_unichar_to_utf8 overflowing buffer");
                        g_free(utf8);
                    }
                    else {
                        g_message("unichar:U+%04x, bytes:%d, string:%s", action_items[i].character, numbytes, utf8);
                        if (keyman->char_buffer == NULL) {
                            g_message("setting buffer to converted unichar");
                            keyman->char_buffer = utf8;
                        }
                        else {
                            g_message("appending converted unichar to CHAR buffer");
                            gchar *new_buffer = g_strjoin("", keyman->char_buffer, utf8, NULL);
                            g_free(keyman->char_buffer);
                            g_free(utf8);
                            keyman->char_buffer = new_buffer;
                        }
                        g_message("CHAR buffer is now %s", keyman->char_buffer);
                    }
                }
                break;
            case KM_KBP_IT_MARKER:
                g_message("MARKER action %d/%d", i+1, (int)num_action_items);
                break;
            case KM_KBP_IT_ALERT:
                g_message("ALERT action %d/%d", i+1, (int)num_action_items);
                GdkDisplay *display = gdk_display_open(NULL);
                if (display != NULL) {
                    gdk_display_beep(display);
                    gdk_display_close(display);
                }
                break;
            case KM_KBP_IT_BACK:
                g_message("BACK action %d/%d", i+1, (int)num_action_items);
                if (keyman->char_buffer != NULL)
                {
                    // ibus_keyman_engine_commit_string(keyman, keyman->char_buffer);
                    g_message("removing one utf8 char from CHAR buffer");
                    glong end_pos = g_utf8_strlen(keyman->char_buffer, -1);
                    gchar *new_buffer;
                    if (end_pos == 1) {
                        new_buffer = NULL;
                        g_message("resetting CHAR buffer to NULL");
                    }
                    else {
                        new_buffer = g_utf8_substring(keyman->char_buffer, 0 , end_pos - 1);
                        g_message("changing CHAR buffer to :%s:", new_buffer);
                    }
                    if (g_strcmp0(keyman->char_buffer, new_buffer) == 0) {
                        g_message("oops, CHAR buffer hasn't changed");
                    }
                    g_free(keyman->char_buffer);
                    keyman->char_buffer = new_buffer;
                }
                else if (ok_for_single_backspace(action_items, i, num_action_items)) {
                    // single backspace can be handled by ibus as normal
                    g_message("no char actions, just single back");
                    return FALSE;
                }
                else {
                    g_message("DAR: ibus_keyman_engine_process_key_event - client_capabilities=%x, %x", engine->client_capabilities,  IBUS_CAP_SURROUNDING_TEXT);

                    if ((engine->client_capabilities & IBUS_CAP_SURROUNDING_TEXT) != 0) {
                        g_message("deleting surrounding text 1 char");
                        ibus_engine_delete_surrounding_text(engine, -1, 1);
                    } else {
                        g_message("forwarding backspace with reset context");
                        km_kbp_context_item *context_items;
                        km_kbp_context_get(km_kbp_state_context(keyman->state),
                            &context_items);
                        reset_context(engine);
                        forward_backspace(keyman, 0);
                        km_kbp_context_set(km_kbp_state_context(keyman->state),
                            context_items);
                        km_kbp_context_items_dispose(context_items);
                    }
                }
                break;
            case KM_KBP_IT_PERSIST_OPT:
                g_message("PERSIST_OPT action %d/%d", i+1, (int)num_action_items);
                break;
            case KM_KBP_IT_EMIT_KEYSTROKE:
                if (keyman->char_buffer != NULL)
                {
                    ibus_keyman_engine_commit_string(keyman, keyman->char_buffer);
                    g_free(keyman->char_buffer);
                    keyman->char_buffer = NULL;
                }
                g_message("EMIT_KEYSTROKE action %d/%d", i+1, (int)num_action_items);
                keyman->emitting_keystroke = TRUE;
                break;
            case KM_KBP_IT_INVALIDATE_CONTEXT:
                g_message("INVALIDATE_CONTEXT action %d/%d", i+1, (int)num_action_items);
                km_kbp_context_clear(km_kbp_state_context(keyman->state));
                reset_context(engine);
                break;
            case KM_KBP_IT_END:
                g_message("END action %d/%d", i+1, (int)num_action_items);
                keyman->firstsurrogate = 0;
                if (keyman->char_buffer != NULL)
                {
                    ibus_keyman_engine_commit_string(keyman, keyman->char_buffer);
                    g_free(keyman->char_buffer);
                    keyman->char_buffer = NULL;
                }
                if (keyman->emitting_keystroke) {
                    keyman->emitting_keystroke = FALSE;
                    return FALSE;
                }
                break;
            default:
                g_warning("Unknown action %d/%d(%d)", i+1, (int)num_action_items, action_items[i].type);
        }
    }
    context = km_kbp_state_context(keyman->state);
    g_message("after processing all actions");
    g_free(get_current_context_text(context));
    return TRUE;
 }

static void
ibus_keyman_engine_set_surrounding_text (IBusEngine *engine,
                                            IBusText    *text,
                                            guint       cursor_pos,
                                            guint       anchor_pos)
{
    gchar *surrounding_text;
    guint context_start = cursor_pos > MAXCONTEXT_ITEMS ? cursor_pos - MAXCONTEXT_ITEMS : 0;
    g_message("ibus_keyman_engine_set_surrounding_text");
    if (cursor_pos != anchor_pos){
        g_message("ibus_keyman_engine_set_surrounding_text: There is a selection");
    }
    parent_class->set_surrounding_text (engine, text, cursor_pos, anchor_pos);
    surrounding_text = g_utf8_substring(ibus_text_get_text(text), context_start, cursor_pos);
    g_message("surrounding context is:%u:%s:", cursor_pos - context_start, surrounding_text);
    g_free(surrounding_text);
    reset_context(engine);
}

// static void ibus_keyman_engine_set_cursor_location (IBusEngine             *engine,
//                                              gint                    x,
//                                              gint                    y,
//                                              gint                    w,
//                                              gint                    h)
// {
//     g_message("ibus_keyman_engine_set_cursor_location");
//     //ibus_keyman_engine_reset(engine);
//     parent_class->set_cursor_location (engine, x, y, w, h);
// }

static void
ibus_keyman_engine_focus_in (IBusEngine *engine)
{
    IBusKeymanEngine *keyman = (IBusKeymanEngine *) engine;

    g_message("ibus_keyman_engine_focus_in");
    ibus_engine_register_properties (engine, keyman->prop_list);

    reset_context(engine);
    parent_class->focus_in (engine);
}

static void
ibus_keyman_engine_focus_out (IBusEngine *engine)
{
    IBusKeymanEngine *keyman = (IBusKeymanEngine *) engine;

    g_message("ibus_keyman_engine_focus_out");
    km_kbp_context_clear(km_kbp_state_context(keyman->state));
    parent_class->focus_out (engine);
}

static void
ibus_keyman_engine_reset (IBusEngine *engine)
{
    g_message("ibus_keyman_engine_reset");
    parent_class->reset (engine);
    ibus_keyman_engine_focus_in (engine);
}



static void
ibus_keyman_engine_enable (IBusEngine *engine)
{
    const gchar *engine_name;
    IBusKeymanEngine *keyman = (IBusKeymanEngine *) engine;

    engine_name = ibus_engine_get_name (engine);
    g_assert (engine_name);
    g_message("WDG: ibus_keyman_engine_enable %s", engine_name);
    g_message("enabling surrounding context");
    ibus_engine_get_surrounding_text(engine, NULL, NULL, NULL);
    if (keyman->ldmlfile)
    {
        // own dbus name com.Keyman
        // expose properties LDMLFile and Name
        KeymanService *service = km_service_get_default();
        km_service_set_ldmlfile (service, keyman->ldmlfile);
        km_service_set_name (service, keyman->kb_name);
    }
    parent_class->enable (engine);
}

static void
ibus_keyman_engine_disable (IBusEngine *engine)
{
    const gchar *engine_name;

    engine_name = ibus_engine_get_name (engine);
    g_assert (engine_name);
    g_message("WDG: ibus_keyman_engine_disable %s", engine_name);
    ibus_keyman_engine_focus_out (engine);
    // stop owning dbus name com.Keyman
    KeymanService *service = km_service_get_default();
    km_service_set_ldmlfile (service, "");
    km_service_set_name (service, "None");
    // g_clear_object(&service);

    parent_class->disable (engine);
}

// static void
// ibus_keyman_engine_page_up (IBusEngine *engine)
// {
//     g_message("ibus_keyman_engine_page_up");
//     parent_class->page_up (engine);
//     reset_context(engine);
// }

// static void
// ibus_keyman_engine_page_down (IBusEngine *engine)
// {
//     g_message("ibus_keyman_engine_page_down");
//     parent_class->page_down (engine);
//     reset_context(engine);
// }

// static void
// ibus_keyman_engine_cursor_up (IBusEngine *engine)
// {
//     g_message("ibus_keyman_engine_cursor_up");
//     parent_class->cursor_up (engine);
//     reset_context(engine);
// }

// static void
// ibus_keyman_engine_cursor_down (IBusEngine *engine)
// {
//     g_message("ibus_keyman_engine_cursor_down");
//     parent_class->cursor_down (engine);
//     reset_context(engine);
// }

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

