/***************************************************************************
  
  gkey.cpp
  
  (c) 2004-2006 - Daniel Campos Fernández <dcamposf@gmail.com>
  (c) Benoît Minisini <benoit.minisini@gambas-basic.org>

  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., 51 Franklin Street, Fifth Floor, Boston,
  MA 02110-1301, USA.
  
***************************************************************************/

#define __GKEY_CPP

#include <ctype.h>
#include <time.h>
#include <unistd.h>

#include "widgets.h"
#include "gapplication.h"
#include "gtrayicon.h"
#include "gdesktop.h"
#include "gmainwindow.h"
#include "gkey.h"
#include "CKey.h"

//#define DEBUG_IM 1

/*************************************************************************

gKey

**************************************************************************/

int gKey::_valid = 0;
bool gKey::_canceled = false;
GdkEventKey gKey::_event;
int gKey::_last_key_press = 0;
int gKey::_last_key_release = 0;

static GtkIMContext *_im_context = NULL;
static char *_im_default_slave = NULL;
static bool _im_has_input_method = FALSE;
static gControl *_im_control = NULL;
static bool _im_no_commit = false;
static GdkWindow *_im_window = NULL;
static bool _im_is_xim = FALSE;
static bool _im_ignore_event = FALSE;
static bool _im_got_commit = FALSE;

//#define MAX_CODE 16
//static uint _key_code[MAX_CODE] = { 0 };


//char *_im_text = NULL;

const char *gKey::text()
{
	if (!_valid || _event.length == 0)
		return 0;
	else
		return _event.string;
}

static int keyval_to_code(int keyval)
{
	if (keyval >= GDK_a && keyval <= GDK_z)
		return keyval + GDK_A - GDK_a;
	else if (keyval == GDK_Alt_R)
		return GDK_Alt_L;
	else if (keyval == GDK_Control_R)
		return GDK_Control_L;
	else if (keyval == GDK_Shift_R)
		return GDK_Shift_L;
	else if (keyval == GDK_Super_L || keyval == GDK_Meta_R || keyval == GDK_Super_R)
		return GDK_Meta_L;
	else if (keyval >= GDK_KEY_KP_Space && keyval <= GDK_KEY_KP_Divide)
	{
		switch (keyval)
		{
			case GDK_KEY_KP_Space: return ' ';
			case GDK_KEY_KP_Tab: return '\t';
			case GDK_KEY_KP_F1: return GDK_KEY_F1;
			case GDK_KEY_KP_F2: return GDK_KEY_F2;
			case GDK_KEY_KP_F3: return GDK_KEY_F3;
			case GDK_KEY_KP_F4: return GDK_KEY_F4;
			case GDK_KEY_KP_Home: return GDK_KEY_Home;
			case GDK_KEY_KP_Left: return GDK_KEY_Left;
			case GDK_KEY_KP_Up: return GDK_KEY_Up;
			case GDK_KEY_KP_Right: return GDK_KEY_Right;
			case GDK_KEY_KP_Down: return GDK_KEY_Down;
			case GDK_KEY_KP_Page_Up: return GDK_KEY_Page_Up;
			case GDK_KEY_KP_Page_Down: return GDK_KEY_Page_Down;
			case GDK_KEY_KP_End: return GDK_KEY_End;
			case GDK_KEY_KP_Begin: return GDK_KEY_Begin;
			case GDK_KEY_KP_Insert: return GDK_KEY_Insert;
			case GDK_KEY_KP_Delete: return GDK_KEY_Delete;
			case GDK_KEY_KP_Equal: return '=';
			case GDK_KEY_KP_Multiply: return '*';
			case GDK_KEY_KP_Add: return '+';
			case GDK_KEY_KP_Subtract: return '-';
			case GDK_KEY_KP_Decimal: return '.';
			case GDK_KEY_KP_Divide: return '/';
		}
	}
	
	int unicode = gdk_keyval_to_unicode(keyval);
	if (unicode >= 32 && unicode < 127)
		return unicode;
	
	return keyval;
}


int gKey::code()
{
	if (!_valid)
		return 0;
	
	return keyval_to_code(_event.keyval);
}

int gKey::state()
{
	if (!_valid)
		return 0;
	else
		return _event.state;
}

bool gKey::shift()
{
	return state() & GDK_SHIFT_MASK;
}

bool gKey::control()
{
	return state() & GDK_CONTROL_MASK;
}

bool gKey::alt()
{
	return state() & GDK_MOD1_MASK;
}

bool gKey::meta()
{
	return state() & (GDK_META_MASK | GDK_SUPER_MASK);
}

bool gKey::normal()
{
	return (state() & (GDK_MOD1_MASK | GDK_CONTROL_MASK | GDK_META_MASK | GDK_SUPER_MASK | GDK_SHIFT_MASK)) == 0;
}

#if 0
int gKey::fromString(const char *str)
{
	char *lstr;
	int key;
	
	if (!str || !*str)
		return 0;
	
	lstr = g_ascii_strup(str, -1);
	key = gdk_keyval_from_name(lstr);
	g_free(lstr);
	if (key && key != GDK_KEY_VoidSymbol) return key;

	lstr = g_ascii_strdown(str, -1);
	key = gdk_keyval_from_name(lstr);
	g_free(lstr);
	if (key && key != GDK_KEY_VoidSymbol) return key;

	key = gdk_keyval_from_name(str);
	if (key && key != GDK_KEY_VoidSymbol) return key;

	if (!str[1] && isascii(str[0]))
		return str[0];
	else
		return 0;
}
#endif

int gKey::fromString(const char *str)
{
	int code;
	
	if (!str || !*str)
		return 0;
	
	if (*g_utf8_find_next_char(str, NULL))
	{
		if (GB.StrCaseCmp(str, "SHIFT") == 0)
			return GDK_KEY_Shift_L;
		else if (GB.StrCaseCmp(str, "CONTROL") == 0 || GB.StrCaseCmp(str, "CTRL") == 0)
			return GDK_KEY_Control_L;
		else if (GB.StrCaseCmp(str, "ALT") == 0)
			return GDK_KEY_Alt_L;
		else if (GB.StrCaseCmp(str, "META") == 0)
			return GDK_KEY_Meta_L;
		else
			return 0;
	}
	
	code = gdk_unicode_to_keyval(g_utf8_get_char(str));
	if (code & 0x01000000)
		return 0;
	
	if (code >= 'a' && code <= 'z')
		code += 'A' - 'a';
	
	return code;
}

void gKey::disable()
{
	_valid--;
	if (_valid)
		return;
	
	_valid = false;
	//_event.keyval = 0;
	//_event.state = 0;
	//g_free(_event.string);
}

bool gKey::enable(gControl *control, GdkEventKey *event)
{
	bool f = false;
	
	_valid++;
	_canceled = false;

	if (event)
	{
		_im_no_commit = false;

		_event = *event;
		_event.window = _im_window;
		
		if (_event.keyval == GDK_Alt_L || _event.keyval == GDK_Alt_R)
			_event.state ^= GDK_MOD1_MASK;
		else if (_event.keyval == GDK_Control_L || _event.keyval == GDK_Control_R)
			_event.state ^= GDK_CONTROL_MASK;
		else if (_event.keyval == GDK_Meta_L || _event.keyval == GDK_Meta_R || _event.keyval == GDK_Super_L || _event.keyval == GDK_Super_R)
			_event.state ^= GDK_META_MASK;
		else if (_event.keyval == GDK_Shift_L || _event.keyval == GDK_Shift_R)
			_event.state ^= GDK_SHIFT_MASK;

		if (gKey::mustIgnoreEvent(event))
			return true;

		if (control == _im_control)
		{
			#if DEBUG_IM
			fprintf(stderr, "gKey::enable: [%p] flag = %d event->string = %d\n", event, (event->state & (1 << 25)) != 0, *event->string);
			#endif

#if 0
			if (_im_slave_is_xim)
			{
				/*if (_im_xim_abort == 2)
				{
					f = true;
					_im_xim_abort = 0;
				}
				else*/
				{
					GdkEventKey save = *event;
					if (save.string)
						save.string = g_strdup(save.string);

					f = gtk_im_context_filter_keypress(_im_context, event);
					*event = save;
					_im_xim_abort++;
				}
			}
			else
				f = gtk_im_context_filter_keypress(_im_context, event);
#endif

			if (!_im_has_input_method)
			{
				initContext();
				f = gtk_im_context_filter_keypress(_im_context, event);
			}

			#if DEBUG_IM
			fprintf(stderr, "gKey::enable: [%p] filter -> %d\n", event, f);
			#endif
		}
	}
	
	return f || _canceled;
}

bool gKey::mustIgnoreEvent(GdkEventKey *event)
{
	bool ignore;

	if (!_im_has_input_method)
		ignore =  false;
	else
		ignore = (event->type == GDK_KEY_PRESS) && (event->keyval == 0 || !event->string || ((uchar)*event->string >= 32 && ((event->keyval & 0xFF00) != 0xFF00)));

#if DEBUG_IM
	fprintf(stderr, "gKey::mustIgnoreEvent -> %d  event = { keyval = %08X, string = %p len = %d }\n", ignore, event->keyval, event->string, event->length);
#endif
	return ignore;
}

void gcb_im_commit(GtkIMContext *context, const char *str, gControl *control)
{
	bool disable = false;

	if (!control)
		control = _im_control;
	
	#if DEBUG_IM
	fprintf(stderr, "cb_im_commit: \"%s\"  control = %p  _im_no_commit = %d  gKey::valid = %d\n", str, control, _im_no_commit, gKey::isValid());
	#endif

	// Not called from a key press event!
	if (!control)
		return;

	if (!gKey::isValid())
	{
		gKey::enable(control, NULL);
		gKey::_event.keyval = gKey::_last_key_press;
		disable = true;
	}

	gKey::_canceled = gKey::raiseEvent(gEvent_KeyPress, control, str);
#if DEBUG_IM
	fprintf(stderr, "cb_im_commit: canceled = %d\n", gKey::_canceled);
#endif

	if (disable)
		gKey::disable();

	_im_no_commit = true;
}

static gboolean hook_commit(GSignalInvocationHint *ihint, guint n_param_values, const GValue *param_values, gpointer data)
{
	_im_got_commit = TRUE;
	return true;
}

void gKey::initContext()
{
	if (_im_context)
		return;

#if DEBUG_IM
	fprintf(stderr ,"gKey::initContext\n");
#endif

	_im_context = gtk_im_multicontext_new();
	gtk_im_context_set_client_window (_im_context, _im_window);

	_im_default_slave = g_strdup(gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(_im_context)));

  g_signal_connect(_im_context, "commit", G_CALLBACK(gcb_im_commit), NULL);

	g_signal_add_emission_hook(g_signal_lookup("commit", GTK_TYPE_IM_CONTEXT), (GQuark)0, hook_commit, (gpointer)0, NULL);
}

void gKey::resetContext()
{
	initContext();
	gtk_im_context_reset(_im_context);
	gtk_im_context_set_client_window (_im_context, 0);
	gtk_im_context_reset(_im_context);
	gtk_im_context_focus_out(_im_context);
	gtk_im_context_reset(_im_context);
}

void gKey::init()
{
	GdkWindowAttr attr;

	attr.event_mask = GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK;
	attr.width = attr.height = 10;
	attr.wclass = GDK_INPUT_OUTPUT;
	attr.window_type = GDK_WINDOW_TOPLEVEL;

	_im_window = gdk_window_new(NULL, &attr, 0);
}

void gKey::exit()
{
	disable();
	if (_im_context)
	{
		g_free(_im_default_slave);
		g_object_unref(_im_context);
	}
}

void gKey::setActiveControl(gControl *control)
{
	const char *slave;
	GtkIMContext *context;

	if (_im_control == control)
		return;
	
	if (_im_control)
	{
#if DEBUG_IM
		fprintf(stderr, "gtk_im_context_focus_out\n");
#endif
		if (!_im_has_input_method)
			resetContext();
		_im_control = NULL;
	}
	
	if (control)
	{
		_im_control = control;
		
		if (!control->hasInputMethod())
		{
#if DEBUG_IM
			fprintf(stderr, "no input method\n");
#endif
			initContext();
			_im_has_input_method = FALSE;
			gtk_im_context_reset(_im_context);
			gtk_im_context_set_client_window (_im_context, gtk_widget_get_window(control->widget));
			gtk_im_context_reset(_im_context);
			gtk_im_context_focus_in(_im_context);
			gtk_im_context_reset(_im_context);
			//slave = gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(_im_context));
			_im_is_xim = FALSE;
		}
		else
		{
			resetContext();
			_im_has_input_method = TRUE;
			context = control->getInputMethod();
			if (context && GTK_IS_IM_MULTICONTEXT(context))
			{
#if DEBUG_IM
				fprintf(stderr, "multiple input method\n");
#endif
				slave = gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(context));
				_im_is_xim = slave && strcmp(slave, "xim") == 0;
			}
			else
			{
#if DEBUG_IM
				fprintf(stderr, "single input method\n");
#endif
				_im_is_xim = FALSE;
			}
		}

		_im_ignore_event = FALSE;

#if DEBUG_IM
		fprintf(stderr,"\n------------------------\n");
		fprintf(stderr, "gtk_im_context_focus_in: %s _im_has_input_method = %d\n", control ? control->name() : "-", _im_has_input_method);
#endif
	}
}

static bool raise_key_event_to_parent_window(gControl *control, int type)
{
	gMainWindow *win;

	while (control->parent())
	{
		win = control->parent()->window();
		if (CB_control_can_raise(win, type))
		{
			//fprintf(stderr, "onKeyEvent: %d %p %s\n", type, win, win->name());
			if (CB_control_key(win, type))
				return true;
		}

		control = win;
	}

	return false;
}

#if 0
static bool can_raise(GdkEventKey *event)
{
	int i;

	if (event->type == GDK_KEY_PRESS)
	{
		for (i = 0; i < MAX_CODE; i++)
		{
			if (event->keyval == _key_code[i])
				return false;
		}
		for (i = 0; i < MAX_CODE; i++)
		{
			if (!_key_code[i])
			{
				//fprintf(stderr, "store key %d\n", event->keyval);
				_key_code[i] = event->keyval;
				break;
			}
		}
		return true;
	}
	else
	{
		for (i = 0; i < MAX_CODE; i++)
		{
			if (event->keyval == _key_code[i])
			{
				//fprintf(stderr, "remove key %d\n", event->keyval);
				_key_code[i] = 0;
				return true;
			}
		}
		return false;
	}
}
#endif

bool gKey::raiseEvent(int type, gControl *control, const char *text)
{
	bool parent_got_it = false;
	bool cancel = false;
	bool handled = false;

#if DEBUG_IM
	fprintf(stderr, "gKey::raiseEvent %s to %p %s\n", type == gEvent_KeyPress ? "KeyPress" : "KeyRelease", control, control->name());
#endif
					
	if (text && *text)
		_event.string = (gchar *)text;

	//if (!can_raise(&_event))
	//	return false;

__KEY_TRY_PROXY:

	if (!parent_got_it)
	{
		parent_got_it = true;

		if (gApplication::onKeyEvent)
			cancel = gApplication::onKeyEvent(type);

		if (!cancel)
			cancel = raise_key_event_to_parent_window(control, type);
	}

	if (!cancel && CB_control_can_raise(control, type))
	{
		//fprintf(stderr, "gEvent_KeyPress on %p %s\n", control, control->name());
		//fprintf(stderr, "onKeyEvent: %p %d %p %s\n", event, type, control, control->name());
		#if DEBUG_IM
			fprintf(stderr, "--> %s\n", control->name());
		#endif
		handled = true;
		cancel = CB_control_key(control, type);
	}

	if (cancel)
	{
		#if DEBUG_IM
			fprintf(stderr, "--> cancel\n");
		#endif
		return true;
	}

	if (control->_proxy_for)
	{
		control = control->_proxy_for;
		goto __KEY_TRY_PROXY;
	}

	if (!handled)
	{
		control = control->parent();
		if (control && !control->isWindow())
			goto __KEY_TRY_PROXY;
	}
	
	return false;
}

static bool check_button(gControl *w)
{
	return w && w->isReallyVisible() && w->isEnabled();
}

gboolean gcb_key_event(GtkWidget *widget, GdkEvent *event, gControl *control)
{
	gMainWindow *win;
	int type;
	bool cancel;

#if DEBUG_IM
	fprintf(stderr, "gcb_key_event: %s for %p %s / active = %p %s\n", event->type == GDK_KEY_PRESS ? "GDK_KEY_PRESS" : "GDK_KEY_RELEASE", control, control->name(), gApplication::activeControl(), gApplication::activeControl() ? gApplication::activeControl()->name() : "-");
#endif

	if (gApplication::grabControl())
		control = gApplication::grabControl();
	else
	{
		// If there is no active control, we send the event if the control is a top level window
		if (gApplication::activeControl() || !control->isTopLevel())
		{
			if (!control || control != gApplication::activeControl())
				return false;
		}
	}
	
#if DEBUG_IM
	fprintf(stderr, "handle it\n");
#endif

	//if (event->type == GDK_KEY_PRESS)
	//	fprintf(stderr, "GDK_KEY_PRESS: control = %p %s %p %08X\n", control, control ? control->name() : "", event, event->key.state);

	if (_im_is_xim)
	{
		_im_ignore_event = !_im_ignore_event;
		if (_im_ignore_event)
		{
#if DEBUG_IM
			fprintf(stderr, "...but ignore it\n");
#endif
			return false;
		}
	}

	type =  (event->type == GDK_KEY_PRESS) ? gEvent_KeyPress : gEvent_KeyRelease;

	if (gKey::enable(control, &event->key))
	{
		gKey::disable();
		return gKey::canceled() || !_im_has_input_method;
	}

	if (gKey::mustIgnoreEvent(&event->key))
	{
		gKey::disable();
		return true;
	}

	cancel = gKey::raiseEvent(type, control, NULL);
	gKey::disable();

	if (cancel)
		return true;

	if (event->type == GDK_KEY_PRESS)
	{
		if (event->key.keyval == GDK_Tab || event->key.keyval == GDK_KEY_KP_Tab)
		{
			gcb_focus(widget, gKey::shift() ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD, control->topLevel());
			return true;
		}
	}
	
	win = control->window();
	
	for(;;)
	{
		if (event->key.keyval == GDK_Escape)
		{
			if (control->_grab)
			{
				gApplication::exitLoop(control);
				return true;
			}

			if (check_button(win->_cancel))
			{
				#if DEBUG_IM
					fprintf(stderr, "gcb_key_event: cancel button\n");
				#endif
				//win->_cancel->setFocus();
				win->_cancel->animateClick(type == gEvent_KeyRelease);
				return true;
			}
		}
		else if (event->key.keyval == GDK_Return || event->key.keyval == GDK_KP_Enter)
		{
			if (check_button(win->_default) && !control->eatReturnKey())
			{
				#if DEBUG_IM
					fprintf(stderr, "gcb_key_event: default button\n");
				#endif
				//win->_default->setFocus();
				win->_default->animateClick(type == gEvent_KeyRelease);
				return true;
			}
		}
		
		if (win->isTopLevel())
			break;
		
		win = win->parent()->window();
	}

	if (control->_grab)
	{
		#if DEBUG_IM
		fprintf(stderr, "gcb_key_event: grab\n");
		#endif
		return true;
	}

	return false;
}

bool gKey::gotCommit()
{
	bool ret = _im_got_commit;
	_im_got_commit = FALSE;
	return ret;
}

const char *gKey::send(const char *text, int state)
{
	gControl *control;
	GdkEvent* event;
	GdkKeymapKey* keys;
	gint n_keys;
	int code;
	
	if (_valid)
		return "Ongoing keyboard event";
	
	control = gApplication::activeControl();
	if (!control)
		return "No active control";
	
	code = CKEY_get_keyval_from_name(text);
	if (!code)
		return NULL;
	
	if (!gdk_keymap_get_entries_for_keyval(gdk_keymap_get_for_display(gdk_display_get_default()), code, &keys, &n_keys))
		return NULL;
	
	event = gdk_event_new(GDK_KEY_PRESS);
	event->key.window = gtk_widget_get_window(control->widget);
	g_object_ref(event->key.window);
	event->key.send_event = TRUE;
	event->key.time = GDK_CURRENT_TIME;
	event->key.state = state;
	event->key.keyval = code;
	event->key.hardware_keycode = keys[0].keycode;
	event->key.group = keys[0].group;
	
	if (!*g_utf8_find_next_char(text, NULL))
	{
		if (code >= 'A' && code <= 'Z')
		{
			if ((state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_SUPER_MASK | GDK_META_MASK)) == 0)
			{
				if (state & GDK_SHIFT_MASK)
					event->key.keyval = code;
				else
					event->key.keyval = code + 'a' - 'A';
				
				event->key.string = g_strdup(text);
				event->key.length = 1;
				*event->key.string = event->key.keyval;
			}
			else if ((state & (GDK_SHIFT_MASK | GDK_MOD1_MASK | GDK_SUPER_MASK | GDK_META_MASK)) == 0)
			{
				if (state & GDK_CONTROL_MASK)
				{
					event->key.string = g_strdup(text);
					event->key.length = 1;
					*event->key.string = code - 'A' + 1;
				}
			}
		}
		else
		{
			event->key.string = g_strdup(text);
			event->key.length = strlen(text);
		}
	}
	else
	{
		code = gdk_keyval_to_unicode(code);
		if (code >= 1 && code <= 127)
		{
			event->key.string = g_strdup(" ");
			*event->key.string = code;
			event->key.length = 1;
		}
	}
	
	if (!event->key.string)
	{
		event->key.string = g_strdup("");
		event->key.length = 0;
	}

#if GTK3
	
	GdkDisplay *display = gdk_window_get_display(event->any.window);
	GdkDevice *device;
	
#if GDK_MAJOR_VERSION > 3 || (GDK_MAJOR_VERSION == 3 && GDK_MINOR_VERSION >= 20)

	device = gdk_seat_get_keyboard(gdk_display_get_default_seat(display));

#else
	
	device = gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(display));
	device = gdk_device_get_associated_device(device);
	
#endif

	gdk_event_set_device(event, device);
	
#endif
	
	gApplication::handleEvent(event);
	
	event->type = GDK_KEY_RELEASE;

	gApplication::handleEvent(event);
	
	gdk_event_free(event);
	
	return NULL;
}
