/* Note that the functions up to update_keymap() are directly from
 * gdkkeys-win32.c. This file is a test bed for gdkkeys-win32.c...
 */

#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <windows.h>

#define DEBUGGING

#define GDK_NOTE(type,action)		     G_STMT_START { \
    { action; };			     } G_STMT_END

static gboolean _gdk_keyboard_has_altgr = FALSE;
static GdkModifierType gdk_shift_modifiers = GDK_SHIFT_MASK;
static guint _scancode_rshift = 0;
static HKL _gdk_input_locale;
guint _gdk_keymap_serial = 0;

static guint *keysym_tab = NULL;

static void
handle_special (guint  vk,
		guint *ksymp,
		gint   shift)

{
  switch (vk)
    {
    case VK_CANCEL:
      *ksymp = GDK_Cancel; break;
    case VK_BACK:
      *ksymp = GDK_BackSpace; break;
    case VK_TAB:
      if (shift & 0x1)
	*ksymp = GDK_ISO_Left_Tab;
      else
	*ksymp = GDK_Tab;
      break;
    case VK_CLEAR:
      *ksymp = GDK_Clear; break;
    case VK_RETURN:
      *ksymp = GDK_Return; break;
    case VK_SHIFT:
    case VK_LSHIFT:
      *ksymp = GDK_Shift_L; break;
    case VK_CONTROL:
    case VK_LCONTROL:
      *ksymp = GDK_Control_L; break;
    case VK_MENU:
    case VK_LMENU:
      *ksymp = GDK_Alt_L; break;
    case VK_PAUSE:
      *ksymp = GDK_Pause; break;
    case VK_ESCAPE:
      *ksymp = GDK_Escape; break;
    case VK_PRIOR:
      *ksymp = GDK_Prior; break;
    case VK_NEXT:
      *ksymp = GDK_Next; break;
    case VK_END:
      *ksymp = GDK_End; break;
    case VK_HOME:
      *ksymp = GDK_Home; break;
    case VK_LEFT:
      *ksymp = GDK_Left; break;
    case VK_UP:
      *ksymp = GDK_Up; break;
    case VK_RIGHT:
      *ksymp = GDK_Right; break;
    case VK_DOWN:
      *ksymp = GDK_Down; break;
    case VK_SELECT:
      *ksymp = GDK_Select; break;
    case VK_PRINT:
      *ksymp = GDK_Print; break;
    case VK_EXECUTE:
      *ksymp = GDK_Execute; break;
    case VK_INSERT:
      *ksymp = GDK_Insert; break;
    case VK_DELETE:
      *ksymp = GDK_Delete; break;
    case VK_HELP:
      *ksymp = GDK_Help; break;
    case VK_LWIN:
      *ksymp = GDK_Meta_L; break;
    case VK_RWIN:
      *ksymp = GDK_Meta_R; break;
    case VK_APPS:
      *ksymp = GDK_Menu; break;
    case VK_MULTIPLY:
      *ksymp = GDK_KP_Multiply; break;
    case VK_ADD:
      *ksymp = GDK_KP_Add; break;
    case VK_SEPARATOR:
      *ksymp = GDK_KP_Separator; break;
    case VK_SUBTRACT:
      *ksymp = GDK_KP_Subtract; break;
    case VK_DIVIDE:
      *ksymp = GDK_KP_Divide; break;
    case VK_NUMPAD0:
      *ksymp = GDK_KP_0; break;
    case VK_NUMPAD1:
      *ksymp = GDK_KP_1; break;
    case VK_NUMPAD2:
      *ksymp = GDK_KP_2; break;
    case VK_NUMPAD3:
      *ksymp = GDK_KP_3; break;
    case VK_NUMPAD4:
      *ksymp = GDK_KP_4; break;
    case VK_NUMPAD5:
      *ksymp = GDK_KP_5; break;
    case VK_NUMPAD6:
      *ksymp = GDK_KP_6; break;
    case VK_NUMPAD7:
      *ksymp = GDK_KP_7; break;
    case VK_NUMPAD8:
      *ksymp = GDK_KP_8; break;
    case VK_NUMPAD9:
      *ksymp = GDK_KP_9; break;
    case VK_F1:
      *ksymp = GDK_F1; break;
    case VK_F2:
      *ksymp = GDK_F2; break;
    case VK_F3:
      *ksymp = GDK_F3; break;
    case VK_F4:
      *ksymp = GDK_F4; break;
    case VK_F5:
      *ksymp = GDK_F5; break;
    case VK_F6:
      *ksymp = GDK_F6; break;
    case VK_F7:
      *ksymp = GDK_F7; break;
    case VK_F8:
      *ksymp = GDK_F8; break;
    case VK_F9:
      *ksymp = GDK_F9; break;
    case VK_F10:
      *ksymp = GDK_F10; break;
    case VK_F11:
      *ksymp = GDK_F11; break;
    case VK_F12:
      *ksymp = GDK_F12; break;
    case VK_F13:
      *ksymp = GDK_F13; break;
    case VK_F14:
      *ksymp = GDK_F14; break;
    case VK_F15:
      *ksymp = GDK_F15; break;
    case VK_F16:
      *ksymp = GDK_F16; break;
    case VK_F17:
      *ksymp = GDK_F17; break;
    case VK_F18:
      *ksymp = GDK_F18; break;
    case VK_F19:
      *ksymp = GDK_F19; break;
    case VK_F20:
      *ksymp = GDK_F20; break;
    case VK_F21:
      *ksymp = GDK_F21; break;
    case VK_F22:
      *ksymp = GDK_F22; break;
    case VK_F23:
      *ksymp = GDK_F23; break;
    case VK_F24:
      *ksymp = GDK_F24; break;
    case VK_NUMLOCK:
      *ksymp = GDK_Num_Lock; break;
    case VK_SCROLL:
      *ksymp = GDK_Scroll_Lock; break;
    case VK_RSHIFT:
      *ksymp = GDK_Shift_R; break;
    case VK_RCONTROL:
      *ksymp = GDK_Control_R; break;
    case VK_RMENU:
      *ksymp = GDK_Alt_R; break;
    }
}

static void
set_shift_vks (guchar *key_state,
	       gint    shift)
{
  switch (shift)
    {
    case 0:
      key_state[VK_SHIFT] = 0;
      key_state[VK_CONTROL] = key_state[VK_MENU] = 0;
      break;
    case 1:
      key_state[VK_SHIFT] = 0x80;
      key_state[VK_CONTROL] = key_state[VK_MENU] = 0;
      break;
    case 2:
      key_state[VK_SHIFT] = 0;
      key_state[VK_CONTROL] = key_state[VK_MENU] = 0x80;
      break;
    case 3:
      key_state[VK_SHIFT] = 0x80;
      key_state[VK_CONTROL] = key_state[VK_MENU] = 0x80;
      break;
    }
}

static void
reset_after_dead (guchar key_state[256])
{
  guchar temp_key_state[256];
  wchar_t wcs[2];

  memmove (temp_key_state, key_state, sizeof (key_state));

  temp_key_state[VK_SHIFT] =
    temp_key_state[VK_CONTROL] =
    temp_key_state[VK_MENU] = 0;

  ToUnicodeEx (VK_SPACE, MapVirtualKey (VK_SPACE, 0),
	       temp_key_state, wcs, G_N_ELEMENTS (wcs),
	       0, _gdk_input_locale);
}

static void
handle_dead (guint  keysym,
	     guint *ksymp)
{
  switch (keysym)
    {
#define MAP(in, out) \
  case in: *ksymp = GDK_##out; break
      MAP ('"', diaeresis);
      MAP ('\'', dead_acute);
      MAP (GDK_asciicircum, dead_circumflex);
      MAP (GDK_grave, dead_grave);
      MAP (GDK_asciitilde, dead_tilde);
      MAP (GDK_diaeresis, dead_diaeresis);
      MAP (GDK_degree, dead_abovering);
      MAP (GDK_acute, dead_acute);
      MAP (GDK_periodcentered, dead_abovedot);
      MAP (GDK_cedilla, dead_cedilla);
      MAP (GDK_breve, dead_breve);
      MAP (GDK_ogonek, dead_ogonek);
      MAP (GDK_caron, dead_caron);
      MAP (GDK_doubleacute, dead_doubleacute);
      MAP (GDK_abovedot, dead_abovedot);
      MAP (0x1000384, dead_acute); /* Greek tonos */
      MAP (GDK_Greek_accentdieresis, Greek_accentdieresis);
#undef MAP
    default:
      /* By default use the keysym as such. This takes care of for
       * instance the dead U+09CD (BENGALI VIRAMA) on the ekushey
       * Bengali layout.
       */
      *ksymp = keysym;
      break;
    }
}

static void
update_keymap (void)
{
  static guint current_serial = 0;
  guchar key_state[256];
  guchar temp_key_state[256];
  guint scancode;
  guint vk, vk2;
  gboolean capslock_tested = FALSE;
  static const char *shift_state_name[4] =
    {
      "",
      "Shift",
      "CtrlAlt",
      "Shift+CtrlAlt"
    };

  if (keysym_tab != NULL && current_serial == _gdk_keymap_serial)
    return;

  current_serial = _gdk_keymap_serial;

  if (keysym_tab == NULL)
    keysym_tab = g_new (guint, 4*256);

  memset (key_state, 0, sizeof (key_state));

  _gdk_keyboard_has_altgr = FALSE;
  gdk_shift_modifiers = GDK_SHIFT_MASK;

#ifdef DEBUGGING
  g_print ("Mapping from virtual keys to keysyms:\n");
  g_print ("   vk, sc, shift: ksysyms\n");
#endif

  for (vk = 0; vk < 256; vk++)
    {
      gint shift;

      if ((scancode = MapVirtualKey (vk, 0)) == 0 &&
	  vk != VK_DIVIDE)
	{
#ifdef DEBUGGING
	  g_print (" %02X: none\n", vk);
#endif
	  keysym_tab[vk*4+0] =
	    keysym_tab[vk*4+1] =
	    keysym_tab[vk*4+2] =
	    keysym_tab[vk*4+3] = GDK_VoidSymbol;
	  continue;
	}

      if (vk == VK_RSHIFT)
	_scancode_rshift = scancode;

      key_state[vk] = 0x80;
      for (shift = 0; shift < 4; shift++)
	{
	  guint *ksymp = keysym_tab + vk*4 + shift;

	  set_shift_vks (key_state, shift);

	  *ksymp = 0;

	  /* First, handle those virtual keys that we always want
	   * as special GDK_* keysyms, even if ToAsciiEx might
	   * turn some them into a ASCII character (like TAB and
	   * ESC).
	   */
	  handle_special (vk, ksymp, shift);

	  if (*ksymp == 0)
	    {
	      wchar_t wcs[10];
	      gint k;

	      wcs[0] = wcs[1] = 0;
	      k = ToUnicodeEx (vk, scancode, key_state,
			       wcs, G_N_ELEMENTS (wcs),
			       0, _gdk_input_locale);
#ifdef DEBUGGING
	      g_print (" %02X, %3d %13s: %2d",
		       vk, scancode, shift_state_name[shift], k);
	      if (k > 0)
		{
		  gint i;

		  for (i = 0; i < k; i++)
		    g_print (" %s", gdk_keyval_name (gdk_unicode_to_keyval (wcs[i])));
		}
#endif
	      if (k == 1)
		{
		  *ksymp = gdk_unicode_to_keyval (wcs[0]);
		}
	      else if (k == -1)
		{
		  guint keysym = gdk_unicode_to_keyval (wcs[0]);

#ifdef DEBUGGING
		  g_print (" %s\n", gdk_keyval_name (keysym));
#endif

#if 0 /* This code snippet was added at some point to gdkkeys-win32.c,
       * but it doesn't seem to matter whether it is here or not
       * anyway...
       */
		  /* It is a dead key, and it has been stored in the
		   * keyboard layout's state by ToUnicodeEx(). Make
		   * the keyboard layout forget it by calling
		   * ToUnicodeEx() once more, with the virtual key
		   * code and scancode for the spacebar, without shift
		   * or AltGr. Otherwise the next call to ToAsciiEx()
		   * with a different key would try to combine with
		   * the dead key.
		   */
		  reset_after_dead (key_state);
#endif
		  /* Use dead keysyms instead of "undead" ones */
		  handle_dead (keysym, ksymp);

		  /* Investigate dead key behaviour */
		  for (vk2 = 0; vk2 < 256; vk2++)
		    {
		      gint j, k2;
		      guint scancode2 = MapVirtualKey (vk2, 0);

		      j = ToUnicodeEx (vk, scancode, key_state,
				       wcs, G_N_ELEMENTS (wcs),
				       0, _gdk_input_locale);

		      memmove (temp_key_state, key_state, sizeof (key_state));
		      temp_key_state[VK_SHIFT] =
			temp_key_state[VK_CONTROL] =
			temp_key_state[VK_MENU] = 0;

		      k2 = ToUnicodeEx (vk2, scancode2, temp_key_state,
					wcs, G_N_ELEMENTS (wcs),
					0, _gdk_input_locale);
		      if (k2 > 1)
			break;
		    }

		  for (vk2 = 0; vk2 < 256; vk2++)
		    {
		      gint j, k2;
		      guint scancode2 = MapVirtualKey (vk2, 0);

		      j = ToUnicodeEx (vk, scancode, key_state,
				       wcs, G_N_ELEMENTS (wcs),
				       0, _gdk_input_locale);
#if 0
		      g_assert (j == k);
#endif
		      memmove (temp_key_state, key_state, sizeof (key_state));
		      temp_key_state[VK_SHIFT] =
			temp_key_state[VK_CONTROL] =
			temp_key_state[VK_MENU] = 0;

		      k2 = ToUnicodeEx (vk2, scancode2, temp_key_state,
					wcs, G_N_ELEMENTS (wcs),
					0, _gdk_input_locale);
#ifdef DEBUGGING
		      if (k2 > 0)
			{
			  gint i;

			  g_print ("   %02X: %d", vk2, k2);
			  for (i = 0; i < k2; i++)
			    g_print (" %s", gdk_keyval_name (gdk_unicode_to_keyval (wcs[i])));
			  g_print ("\n");
			}
#endif
		    }
		}
	      else if (k == 0)
		{
		  /* Seems to be necessary to "reset" the keyboard layout
		   * in this case, too. Otherwise problems on NT4.
		   */
		  reset_after_dead (key_state);
		}
	      else
		{
#ifdef DEBUGGING
		  GDK_NOTE (EVENTS,
			    g_print ("ToUnicodeEx returns %d "
				     "for vk:%02X, sc:%02X%s%s\n",
				     k, vk, scancode,
				     (shift&0x1 ? " shift" : ""),
				     (shift&0x2 ? " altgr" : "")));
#endif
		}
	      g_print ("\n");
	    }
	  if (*ksymp == 0)
	    *ksymp = GDK_VoidSymbol;
	}
      key_state[vk] = 0;

      /* Check if keyboard has an AltGr key by checking if
       * the mapping with Control+Alt is different.
       */
      if (!_gdk_keyboard_has_altgr)
	if ((keysym_tab[vk*4 + 2] != GDK_VoidSymbol &&
	     keysym_tab[vk*4] != keysym_tab[vk*4 + 2]) ||
	    (keysym_tab[vk*4 + 3] != GDK_VoidSymbol &&
	     keysym_tab[vk*4 + 1] != keysym_tab[vk*4 + 3]))
	  _gdk_keyboard_has_altgr = TRUE;

      if (!capslock_tested)
	{
	  /* Can we use this virtual key to determine the CapsLock
	   * key behaviour: CapsLock or ShiftLock? If it generates
	   * keysyms for printable characters and has a shifted
	   * keysym that isn't just the upperacase of the
	   * unshifted keysym, check the behaviour of VK_CAPITAL.
	   */
	  if (g_unichar_isgraph (gdk_keyval_to_unicode (keysym_tab[vk*4 + 0])) &&
	      keysym_tab[vk*4 + 1] != keysym_tab[vk*4 + 0] &&
	      g_unichar_isgraph (gdk_keyval_to_unicode (keysym_tab[vk*4 + 1])) &&
	      keysym_tab[vk*4 + 1] != gdk_keyval_to_upper (keysym_tab[vk*4 + 0]))
	    {
	      guchar chars[2];

	      capslock_tested = TRUE;

	      key_state[VK_SHIFT] = 0;
	      key_state[VK_CONTROL] = key_state[VK_MENU] = 0;
	      key_state[VK_CAPITAL] = 1;

	      if (ToAsciiEx (vk, scancode, key_state,
			     (LPWORD) chars, 0, _gdk_input_locale) == 1)
		{
		  if (chars[0] >= GDK_space &&
		      chars[0] <= GDK_asciitilde &&
		      chars[0] == keysym_tab[vk*4 + 1])
		    {
		      /* CapsLock acts as ShiftLock */
		      gdk_shift_modifiers |= GDK_LOCK_MASK;
		    }
		}
	      key_state[VK_CAPITAL] = 0;
	    }    
	}
    }
}

void
hello (void)
{
  guint scancode;
  char buf[KL_NAMELENGTH];

  g_print ("Keyboard layout handle: %p\n", GetKeyboardLayout (0));

  if (!GetKeyboardLayoutName (buf))
    g_print ("GetKeyboardLayoutName failed.\n");

  g_print ("Keyboard layout: %s\n", buf);

  scancode = MapVirtualKey (VK_DIVIDE, 0);
  g_print ("Mapping of VK_DIVIDE: scancode %d\n", scancode);

  _gdk_input_locale = GetKeyboardLayout (0);
  _gdk_keymap_serial++;
  update_keymap ();
}

int
main (int argc, char *argv[])
{
  GtkWidget *window;
  GtkWidget *button;

  gtk_init (&argc, &argv);
  
  window = g_object_connect (g_object_new (gtk_window_get_type (),
					     "user_data", NULL,
					     "type", GTK_WINDOW_TOPLEVEL,
					     "title", "hello world",
					     "border_width", 10,
					     NULL),
			     "signal::destroy", gtk_main_quit, NULL,
			     NULL);
  button = g_object_connect (g_object_new (gtk_button_get_type (),
					     "GtkButton::label", "hello world",
					     "GtkWidget::parent", window,
					     "GtkWidget::visible", TRUE,
					     NULL),
			     "signal::clicked", hello, NULL,
			     NULL);
  gtk_widget_show (window);

  gtk_main ();

  return 0;
}
