
#import <Cocoa/Cocoa.h>
#include <Carbon/Carbon.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtkkeyhash.h>

/* Modified gdk/quartz/gdkevents-quartz.c to make this not static.
 * Function calls: gdk_keymap_translate_keyboard_state()
 *                 _gdk_quartz_keys_is_modifier()
 *                 gdk_keyval_to_unicode()
 * and internal static function
 *                 get_keyboard_modifiers_from_ns_event()
 * It also sets the event->key.group to 1 if NSAlternateKeyMask is true
 */
extern void fill_key_event (GdkWindow *window, GdkEvent *event,
			    NSEvent *nsevent, GdkEventType type);
typedef struct {
  unsigned short keyCode;
  NSUInteger modFlags;
  NSString *charVal;  // The string corresponding to the key code + modifiers
  NSString *baseChar; // The string corresponding to the key code without mods
  guint keyval;       // The GDK key value that we expect to be returned
  GdkModifierType modmask;  // The GDK modifier mask that we expect
  gchar gdkString[4];  // The unicode string we get from the key table
  guint16 hardware_keycode;  //This should match the input key code
  guint8 group;  // Character set for Option/Alt pressed.
  gchar* hashvalue; //The key binding/accelerator that we expect for the key combination.
} keymapval;

/* These are the test-cases. The first 4 check that unmodified keys
 * aren't matched in the keyhash, which was reported in
 * https://bugzilla.gnome.org/show_bug.cgi?id=605799; Test 4 checks
 * that a dead key is returned, which of course fails, because
 * dead-key handling isn't included in gdkkeys-quartz.c. The last two
 * tests look for special unicode keys in the NSEvent
 * documentation. They don't seem to be actually emitted, at least in
 * this simulation. */
keymapval items[] = {
  //0
  { 17, 0, @"t", @"t", GDK_KEY_t, 0, "t" , 0, 0, "t" },
  //1
  { 17, NSShiftKeyMask, @"T", @"T", GDK_KEY_T, GDK_SHIFT_MASK, "T" , 0, 0, "T" },
  //2
  { 17, NSAlternateKeyMask, @"þ", @"t", GDK_KEY_thorn, GDK_MOD5_MASK, "þ", 0, 1 , "þ"},
  //3
  { 17, NSAlternateKeyMask | NSShiftKeyMask, @"Þ", @"T", GDK_KEY_Thorn, GDK_MOD5_MASK | GDK_SHIFT_MASK, "Þ", 0, 1, "Þ" },
  //4
  { 11, NSAlternateKeyMask, @"˘", @"b", GDK_KEY_dead_breve, GDK_MOD5_MASK, "˘", 0, 1, "˘" },
  //5
  { 17, NSCommandKeyMask, @"t", @"t", GDK_KEY_t, GDK_MOD1_MASK, "t", 0, 0, "Cmd-t" },
  //6
  { 17, NSControlKeyMask, @"t", @"t", GDK_KEY_t, GDK_CONTROL_MASK, "t", 0, 0, "Ctrl-t" },
  //7
  { 17, NSCommandKeyMask | NSAlternateKeyMask, @"t", @"t", GDK_KEY_thorn, GDK_MOD1_MASK | GDK_MOD5_MASK, "þ", 0, 1, "Cmd-Opt-t" },
  //8
  { 17, NSCommandKeyMask | NSControlKeyMask, @"t", @"t", GDK_KEY_t, GDK_MOD1_MASK | GDK_CONTROL_MASK, "t", 0, 0, "Cmd-Ctrl-t" },
  //9
  { 17, NSCommandKeyMask | NSShiftKeyMask, @"T", @"T", GDK_KEY_T, GDK_MOD1_MASK | GDK_SHIFT_MASK, "T", 0, 0, "Cmd-Shift-t" },
  //10
  { 17, NSControlKeyMask | NSAlternateKeyMask, @"t", @"t", GDK_KEY_thorn, GDK_CONTROL_MASK | GDK_MOD5_MASK, "þ", 0, 1, "Ctrl-Opt-t" },
  //11
  { 17, NSControlKeyMask | NSShiftKeyMask, @"T", @"T", GDK_KEY_T, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "T", 0, 0, "Ctrl-Shift-t" },
  //12
  { 17, NSCommandKeyMask | NSAlternateKeyMask | NSShiftKeyMask, @"t", @"t", GDK_KEY_Thorn, GDK_MOD1_MASK | GDK_MOD5_MASK | GDK_SHIFT_MASK, "Þ", 0, 1, "Cmd-Opt-Shift-t" },
  //13
  { 17, NSControlKeyMask | NSAlternateKeyMask | NSShiftKeyMask, @"t", @"t", GDK_KEY_Thorn, GDK_CONTROL_MASK | GDK_MOD5_MASK | GDK_SHIFT_MASK, "Þ", 0, 1, "Ctrl-Opt-Shift-t" },
  //14
  { 17, NSCommandKeyMask | NSAlternateKeyMask | NSControlKeyMask, @"t", @"t", GDK_KEY_thorn, GDK_MOD1_MASK | GDK_MOD5_MASK | GDK_CONTROL_MASK, "þ", 0, 1, "Cmd-Opt-Ctrl-t" },
  //15
  { 17, NSCommandKeyMask | NSAlternateKeyMask | NSShiftKeyMask | NSControlKeyMask, @"t", @"t", GDK_KEY_Thorn, GDK_MOD1_MASK | GDK_MOD5_MASK | GDK_SHIFT_MASK | GDK_CONTROL_MASK, "Þ", 0, 1, "Cmd-Ctrl-Opt-Shift-t" },
  //16
  { 124, 0, @"\0xf703", @"\0xF703", GDK_KEY_Right, 0, "r", 0, 0, "Right-Arrow" },
  //17
  { 124, NSAlternateKeyMask | NSShiftKeyMask, @"\0xf703", @"\0xF703", GDK_KEY_Right, GDK_MOD5_MASK | GDK_SHIFT_MASK, "r", 0, 1, "Opt-Right-Arrow" },
 
};

static GdkKeymap *keyMap = NULL;

#define MAXSTRLEN 43

/* Print the Modifier key state */
char*
print_state (GdkModifierType state, char *string, size_t strsize)
{

  memset (string, 0, strsize);
  if (state & GDK_SHIFT_MASK)
    strncat(string, "Shift|", strsize - 1 - strlen(string));
  if (state & GDK_CONTROL_MASK)
    strncat(string, "Control|", strsize - 1 - strlen(string));
  if (state & GDK_MOD1_MASK)
    strncat(string, "Mod1|", strsize - 1 - strlen(string));
  if (state & GDK_MOD2_MASK)
    strncat(string, "Mod2|", strsize - 1 - strlen(string));
  if (state & GDK_MOD3_MASK)
    strncat(string, "Mod3|", strsize - 1 - strlen(string));
  if (state & GDK_MOD4_MASK)
    strncat(string, "Mod4|", strsize - 1 - strlen(string));
  if (state & GDK_MOD5_MASK)
    strncat(string, "Mod5|", strsize - 1 - strlen(string));
  if (state & GDK_SUPER_MASK)
    strncat(string, "Super|", strsize - 1 - strlen(string));
  if (state & GDK_HYPER_MASK)
    strncat(string, "Hyper|", strsize - 1 - strlen(string));
  if (state & GDK_META_MASK)
    strncat(string, "Meta|", strsize - 1 - strlen(string));
  return string;
}

/* Load the keyhashes (bindings, if you like) from the testcases,
 * starting with 5, into the keyhash. The first 4 have no modifier
 * keys and are intended to test for spurious matches.
 */
static void
load_keyhash(GtkKeyHash *keyHash)
{
  int i;
  for ( i = 5; i < sizeof(items)/sizeof(keymapval); i++)
    {
      GdkModifierType mask = items[i].modmask;
      if (mask & GDK_MOD1_MASK)
	{
	  mask &= ~GDK_MOD1_MASK;
	  mask |= GDK_META_MASK;
	}
      if (mask & GDK_MOD5_MASK)
	{
	  mask &= ~GDK_MOD5_MASK;
	  mask |= GDK_SUPER_MASK;
	}

      _gtk_key_hash_add_entry(keyHash, items[i].keyval, items[i].modmask,
			      items[i].hashvalue);
    }
}

static void
test_fill_events(NSEventType type)
{
  gint i;
  NSPoint loc = {0.0, 0.0};
  NSGraphicsContext *context = [NSGraphicsContext currentContext];
  NSTimeInterval time =  [NSDate timeIntervalSinceReferenceDate];
  /* type must be one of NSKeyDown, NSKeyUp, or NSFlagsChanged. The
     last is used to signal that a modifier key has been pressed, but
     no operating key has been. It's needed for detecting that
     caps_lock has changed state, and is useful for things like the
     keyboard viewer, which display the symbols that will be typed
     given the current set of modifiers pressed. It has no analog in
     Gdk. */
  GdkEventType gdkType = (type == NSKeyDown ? GDK_KEY_PRESS : 
			  (type == NSKeyUp ? GDK_KEY_RELEASE :
			   GDK_NOTHING)); 
  GtkKeyHash *keyHash = _gtk_key_hash_new(keyMap, NULL);
  load_keyhash(keyHash);
  /* Test each keymapval by creating an NSEvent and filling the
   * corresponding GDKEvent, then see if gtk_key_hash_lookup finds the
   * value we want.
   */
  for (i = 0; i < sizeof(items)/sizeof(keymapval); i++) 
    {
      NSEvent *nsevent = [NSEvent keyEventWithType: type location: loc
				  modifierFlags: items[i].modFlags 
				  timestamp: time
				  windowNumber: 0
				  context: context
				  characters: items[i].charVal
				  charactersIgnoringModifiers: items[i].baseChar
				  isARepeat: NO
				  keyCode: items[i].keyCode];
      GdkEvent *event = gdk_event_new(gdkType);
      char expectedStr[MAXSTRLEN], gotStr[MAXSTRLEN];
      GdkModifierType state;
      GSList *lookup_list, *list;

      fill_key_event(NULL, event, nsevent, gdkType);
      printf("Item %d: %s\n", i, items[i].hashvalue);
      if (event->key.state != items[i].modmask)
	printf ("\tstate doesn't match: Expected %s got %s\n",
		print_state(items[i].modmask, expectedStr, MAXSTRLEN),
		print_state(event->key.state, gotStr, MAXSTRLEN));
      if(event->key.keyval != items[i].keyval)
	printf ("\tkeyvals don't match: Expected 0x%x got 0x%x\n", 
		items[i].keyval, event->key.keyval);
      if (g_strcmp0(event->key.string, items[i].gdkString) != 0 )
	printf ("\tstrings don't match: Expected %s got %s\n", 
		items[i].gdkString, event->key.string);
      if (event->key.hardware_keycode != items[i].keyCode)
	printf ("\tkey codes don't match: Expected 0x%x got 0x%x\n",
		items[i].keyCode, event->key.hardware_keycode);
      if (event->key.group != items[i].group)
	printf ("\tgroups don't match: Expected 0x%x got 0x%x\n",
		items[i].group, event->key.group);

      lookup_list = _gtk_key_hash_lookup (keyHash, event->key.hardware_keycode,
					  event->key.state,
					  gtk_accelerator_get_default_mod_mask (),
					  event->key.group);
      if (g_slist_length(lookup_list) == 0)
	printf("\tNo matching keyhash entries\n");
      for (list = lookup_list; list != NULL; list = g_slist_next(list))
	printf("\tFound matching keyhash entry %s\n", (gchar *)list->data);
      g_slist_free(lookup_list);
      gdk_event_free(event);
    }
  _gtk_key_hash_free(keyHash);
}


main (int argc, char** argv) 
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  gtk_init(&argc, &argv);
  keyMap = gdk_keymap_get_for_display(gdk_display_get_default());
  test_fill_events(NSKeyUp);
  [pool drain];
}
