#include <stdio.h>
#include <ctype.h>
#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <kbd.h>

static const char *
vk_name (int vk)
{
  switch (vk)
    {
#define CASE(vk) \
      case VK_##vk: return #vk
      CASE (LBUTTON);
      CASE (RBUTTON);
      CASE (CANCEL);
      CASE (MBUTTON);
      CASE (XBUTTON1);
      CASE (XBUTTON2);
      CASE (BACK);
      CASE (TAB);
      CASE (CLEAR);
      CASE (RETURN);
      CASE (SHIFT);
      CASE (CONTROL);
      CASE (MENU);
      CASE (PAUSE);
      CASE (CAPITAL);
      CASE (KANA);
      CASE (JUNJA);
      CASE (FINAL);
      CASE (KANJI);
      CASE (ESCAPE);
      CASE (CONVERT);
      CASE (NONCONVERT);
      CASE (ACCEPT);
      CASE (MODECHANGE);
      CASE (SPACE);
      CASE (PRIOR);
      CASE (NEXT);
      CASE (END);
      CASE (HOME);
      CASE (LEFT);
      CASE (UP);
      CASE (RIGHT);
      CASE (DOWN);
      CASE (SELECT);
      CASE (PRINT);
      CASE (EXECUTE);
      CASE (SNAPSHOT);
      CASE (INSERT);
      CASE (DELETE);
      CASE (HELP);
      CASE (LWIN);
      CASE (RWIN);
      CASE (APPS);
      CASE (SLEEP);
      CASE (NUMPAD0);
      CASE (NUMPAD1);
      CASE (NUMPAD2);
      CASE (NUMPAD3);
      CASE (NUMPAD4);
      CASE (NUMPAD5);
      CASE (NUMPAD6);
      CASE (NUMPAD7);
      CASE (NUMPAD8);
      CASE (NUMPAD9);
      CASE (MULTIPLY);
      CASE (ADD);
      CASE (SEPARATOR);
      CASE (SUBTRACT);
      CASE (DECIMAL);
      CASE (DIVIDE);
      CASE (F1);
      CASE (F2);
      CASE (F3);
      CASE (F4);
      CASE (F5);
      CASE (F6);
      CASE (F7);
      CASE (F8);
      CASE (F9);
      CASE (F10);
      CASE (F11);
      CASE (F12);
      CASE (F13);
      CASE (F14);
      CASE (F15);
      CASE (F16);
      CASE (F17);
      CASE (F18);
      CASE (F19);
      CASE (F20);
      CASE (F21);
      CASE (F22);
      CASE (F23);
      CASE (F24);
      CASE (NUMLOCK);
      CASE (SCROLL);
      CASE (LSHIFT);
      CASE (RSHIFT);
      CASE (LCONTROL);
      CASE (RCONTROL);
      CASE (LMENU);
      CASE (RMENU);
      CASE (BROWSER_BACK);
      CASE (BROWSER_FORWARD);
      CASE (BROWSER_REFRESH);
      CASE (BROWSER_STOP);
      CASE (BROWSER_SEARCH);
      CASE (BROWSER_FAVORITES);
      CASE (BROWSER_HOME);
      CASE (VOLUME_MUTE);
      CASE (VOLUME_DOWN);
      CASE (VOLUME_UP);
      CASE (MEDIA_NEXT_TRACK);
      CASE (MEDIA_PREV_TRACK);
      CASE (MEDIA_STOP);
      CASE (MEDIA_PLAY_PAUSE);
      CASE (LAUNCH_MAIL);
      CASE (LAUNCH_MEDIA_SELECT);
      CASE (LAUNCH_APP1);
      CASE (LAUNCH_APP2);
      CASE (OEM_1);
      CASE (OEM_PLUS);
      CASE (OEM_COMMA);
      CASE (OEM_MINUS);
      CASE (OEM_PERIOD);
      CASE (OEM_2);
      CASE (OEM_3);
      CASE (OEM_4);
      CASE (OEM_5);
      CASE (OEM_6);
      CASE (OEM_7);
      CASE (OEM_8);
      CASE (OEM_102);
      CASE (PROCESSKEY);
      CASE (PACKET);
      CASE (ATTN);
      CASE (CRSEL);
      CASE (EXSEL);
      CASE (EREOF);
      CASE (PLAY);
      CASE (ZOOM);
      CASE (NONAME);
      CASE (PA1);
      CASE (OEM_CLEAR);
#undef CASE
    }

  if ((vk >= 'A' && vk <= 'Z') ||
      (vk >= '0' && vk <= '9'))
    {
      static char c[2];

      c[0] = vk;
      c[1] = '\0';

      return c;
    }

  return "?";
}

static const char *
modbits_to_string (BYTE modbits)
{
  static char buf[100];
  char *p = buf;
  
#define BIT(n)					\
  if (modbits & KBD##n)				\
    {						\
      if (p > buf)				\
	p += sprintf (p, "+");			\
      p += sprintf (p, #n);			\
    }
  BIT (SHIFT);
  BIT (CTRL);
  BIT (ALT);
  BIT (KANA);
  BIT (ROYA);
  BIT (LOYA);
  BIT (GRPSELTAP);
#undef BIT
  return buf;
}

static void
print_modifiers (PMODIFIERS pmod)
{
  PVK_TO_BIT pvktobit = pmod->pVkToBit;
  int i;

  printf (" VkToBit:\n");
  while (pvktobit->Vk)
    {
      printf ("  %s: %s\n", vk_name (pvktobit->Vk), modbits_to_string (pvktobit->ModBits));
      pvktobit++;
    }

  printf (" MaxModBits: %d\n", pmod->wMaxModBits);

  printf (" ModNumber:\n");
  for (i = 0; i < (1 << pmod->wMaxModBits); i++)
    {
      if (pmod->ModNumber[i] == SHFT_INVALID)
	printf ("  SHFT_INVALID");
      else
	printf ("           %3d", pmod->ModNumber[i]);
      printf (" %s\n", modbits_to_string (i));
    }
}

static const char *
wchar_name (WCHAR wc)
{
  static char buf[10];

  if (wc == ' ')
    return "SPACE";

  if (isascii (wc) && isprint (wc))
    {
      buf[0] = wc;
      buf[1] = '\0';

      return buf;
    }

  sprintf (buf, "U+%04X", wc);

  return buf;
}
  
static void
print_vk_to_wchar_table (PVK_TO_WCHAR_TABLE pvktowchartable)
{
  int tableno = 0;

  while (pvktowchartable->cbSize != 0)
    {
      PVK_TO_WCHARS1 pvktowc;

      printf (" %d:\n", tableno++);
      printf ("  nModifications: %d\n", pvktowchartable->nModifications);
      printf ("  cbSize: %d\n", (int) (unsigned char) pvktowchartable->cbSize);

      pvktowc = pvktowchartable->pVkToWchars;
      while (pvktowc->VirtualKey != 0)
	{
	  int i;

	  printf ("  %s", vk_name (pvktowc->VirtualKey));
	  if (pvktowc->Attributes == CAPLOK)
	    printf (" CAPLOK");
	  else if (pvktowc->Attributes == SGCAPS)
	    printf (" SGCAPS");
	  else if (pvktowc->Attributes != 0)
	    printf (" %#x", pvktowc->Attributes);
	  printf (":");
	  for (i = 0; i < pvktowchartable->nModifications; i++)
	    {
	      if (pvktowc->wch[i] == WCH_NONE)
		printf (" NONE");
	      else if (pvktowc->wch[i] == WCH_DEAD)
		printf (" DEAD");
	      else if (pvktowc->wch[i] == WCH_LGTR)
		printf (" LGTR");
	      else
		printf (" %s", wchar_name (pvktowc->wch[i]));
	    }
	  printf ("\n");

#if 0
	  pvktowc = (PVK_TO_WCHARS1) ((char *) pvktowc + sizeof (VK_TO_WCHARS1) + ((1 << pvktowchartable->nModifications)-1)*sizeof (WCHAR));
#else
	  pvktowc = (PVK_TO_WCHARS1) (((char *) pvktowc) + pvktowchartable->cbSize);
#endif
	}
      pvktowchartable++;
    }
}

static void
print_dead_key (PDEADKEY pdeadkey)
{
  while (pdeadkey->dwBoth != 0)
    {
      printf ("  %s +", wchar_name ((WCHAR) (pdeadkey->dwBoth >> 16)));
      printf ("  %s:", wchar_name ((WCHAR) pdeadkey->dwBoth));
      printf (" %s %x\n", wchar_name (pdeadkey->wchComposed), pdeadkey->uFlags);
      pdeadkey++;
    }
}

static void
print_key_names (PVSC_LPWSTR p)
{
  while (p->vsc != 0)
    {
      printf ("  %02X: %S\n", p->vsc, p->pwsz);
      p++;
    }
}

static void
print_keyboard (PKBDTABLES pkbd)
{
  printf ("CharModifiers:\n");
  print_modifiers (pkbd->pCharModifiers);
  printf ("VkToWcharTable[]:\n");
  print_vk_to_wchar_table (pkbd->pVkToWcharTable);
  if (pkbd->pDeadKey != NULL)
    {
      printf ("DeadKey:\n");
      print_dead_key (pkbd->pDeadKey);
    }
  printf ("KeyNames:\n");
  print_key_names (pkbd->pKeyNames);
  if (pkbd->pKeyNamesExt != NULL)
    {
      printf ("KeyNamesExt:\n");
      print_key_names (pkbd->pKeyNamesExt);
    }
  if (pkbd->pKeyNamesDead != NULL)
    {
      WCHAR **wcp = pkbd->pKeyNamesDead;

      printf ("KeyNamesDead:\n");
      while (*wcp != NULL)
	{
	  printf (" %C: %S\n", *wcp[0], *wcp + 1);
	  wcp++;
	}
    }
}

int
main (int argc, char **argv)
{
  HMODULE kbd;
  typedef PKBDTABLES(CALLBACK* KbdLayerDescriptor_t)(VOID);

  KbdLayerDescriptor_t KbdLayerDescriptor;

  if (argc != 2)
    fprintf (stderr, "Usage: kbddump FOO.dll\n"), exit (1);

  kbd = LoadLibrary (argv[1]);

  if (kbd == NULL)
    fprintf (stderr, "Could not load %s\n", argv[1]), exit (1);

  KbdLayerDescriptor = (KbdLayerDescriptor_t) GetProcAddress (kbd, "KbdLayerDescriptor");
  if (KbdLayerDescriptor == NULL)
    fprintf (stderr, "Could not find KbdLayerDescriptor in %s\n", argv[1]), exit (1);

  print_keyboard (KbdLayerDescriptor ());

  return 0;
}

