/*
  Name:             syskbdnt32
  Copyright:        Copyright (C) SIL International.
  Documentation:
  Description:
  Create Date:      10 Sep 2008

  Modified Date:    4 May 2010
  Authors:          mcdurdin
  Related Files:
  Dependencies:

  Bugs:
  Todo:
  Notes:
  History:          10 Sep 2008 - mcdurdin - I1635 - Initial version
                    11 Mar 2009 - mcdurdin - I1894 - Fix threading bugs introduced in I1888
                    30 Nov 2009 - mcdurdin - I934 - Prep for x64 - change UINT to WORD for vkeys
                    12 Mar 2010 - mcdurdin - I934 - x64 - Complete
                    12 Mar 2010 - mcdurdin - I2229 - Remove hints and warnings
                    04 May 2010 - mcdurdin - I2351 - Robustness - verify _td return value
*/

////////////////////////////////////////////////////////////////////////////
//
// Use Windows NT/2000 keyboard DLLs for mapping virtual keys to characters
//
////////////////////////////////////////////////////////////////////////////

#include "pch.h"
#include "../../engine/keyman32/kbd.h"	/* DDK kbdlayout */


typedef PKBDTABLES (WINAPI *PKBDLAYERDESCRIPTORFUNC)(VOID);

static HMODULE hKbdLibrary = NULL;
static PKBDTABLES KbdTables = NULL;

BOOL LoadNewLibrary_NT(PWSTR filename) {
  if(hKbdLibrary) FreeLibrary(hKbdLibrary);

	hKbdLibrary = LoadLibrary(filename);
	if(!hKbdLibrary) {
	  LogError(L"LoadNewLibrary: Exit -- could not load library");
		return FALSE;
	}

  PKBDLAYERDESCRIPTORFUNC KbdLayerDescriptorFunc = (PKBDLAYERDESCRIPTORFUNC) GetProcAddress(hKbdLibrary, "KbdLayerDescriptor");
	if(KbdLayerDescriptorFunc)
	{
		KbdTables = (*KbdLayerDescriptorFunc)();
		if(KbdTables) {
			return TRUE;
    }
	}

	FreeLibrary(hKbdLibrary);

	hKbdLibrary = NULL;
	KbdTables = NULL;

  LogError(L"LoadNewLibrary: Exit -- failed to find function");
	return FALSE;
}

WCHAR CharFromVK_NT(WORD VKey, UINT ShiftFlags, WCHAR *PDeadKey) {
	PVK_TO_WCHARS1 pv;
	PVK_TO_WCHAR_TABLE pvt;
	int i;

	/* MCD 02/12/2002: Fix bug with number pad keys when numlock is on */

  if(IsNumberPadKey(VKey)) {
    return 0;
  }

	/* Get the appropriate table column for shift state */

	int shift = -1, shiftidx = 0, capslockbit = 0;

	for(PVK_TO_BIT pvtb = KbdTables->pCharModifiers->pVkToBit; pvtb->Vk; pvtb++) {
		switch(pvtb->Vk) {
		case VK_MENU:
			if(ShiftFlags & (LALTFLAG|RALTFLAG)) shiftidx |= pvtb->ModBits; break;
		case VK_SHIFT:
			if(ShiftFlags & CAPITALFLAG) capslockbit = pvtb->ModBits;
			if(ShiftFlags & (K_SHIFTFLAG)) shiftidx |= pvtb->ModBits; break;
		case VK_CONTROL:
			if(ShiftFlags & (LCTRLFLAG|RCTRLFLAG)) shiftidx |= pvtb->ModBits; break;
		}
  }

	/* If the shift modifier is unused, don't produce a character */

	if(shiftidx > KbdTables->pCharModifiers->wMaxModBits ||
			KbdTables->pCharModifiers->ModNumber[shiftidx] == 0xF) {
		return 0;
	}

	shift = KbdTables->pCharModifiers->ModNumber[shiftidx];

	/* Get the appropriate virtual key */

	for(i = 0, pvt = KbdTables->pVkToWcharTable; pvt->pVkToWchars; i++, pvt++) {
		for(pv = pvt->pVkToWchars; pv->VirtualKey;) {
			if(pv->VirtualKey == VKey) {
				if(shift >= pvt->nModifications) {
					return 0;
        }

				/* Check for Caps Lock */
				if(ShiftFlags & CAPITALFLAG) {
					if(pv->Attributes & CAPLOK) {
						/* Invert the state of the SHIFT key */
						shift = KbdTables->pCharModifiers->ModNumber[shiftidx ^ capslockbit];
          } else if(pv->Attributes & SGCAPS) {
						pv++; /* Next keystroke has the appropriate capitalised character */
          }
				}

				/* Found the keystroke combination, return the appropriate character (if existing) */
				switch(pv->wch[shift]) {
				case WCH_DEAD:
					pv = (PVK_TO_WCHARS1) ((BYTE *)pv + pvt->cbSize);
          *PDeadKey = pv->wch[shift];
					return 0xFFFF; /* Don't queue as WM_KEYDOWN -- don't output either */
				case WCH_NONE:
					return 0x0000; /* Not a valid key, queue as WM_KEYDOWN */
				}

				return (pv->wch[shift] > 31 && pv->wch[shift] != 127) ? pv->wch[shift] : 0; /* Don't generate 'special chars'; I911 - fix ctrl+bksp */
			}
			pv = (PVK_TO_WCHARS1) ((BYTE *)pv + pvt->cbSize);
		}
	}

	return 0;
}


WORD ModificationToShift_NT(int mod) {
  WORD ShiftFlags = 0;

  int shiftidx = -1;
  for(int i = 0; i <= KbdTables->pCharModifiers->wMaxModBits; i++) {
    if(KbdTables->pCharModifiers->ModNumber[i] == mod) {
      shiftidx = i;
      break;
    }
  }

  if(shiftidx == -1) return 0xFFFF;

	for(PVK_TO_BIT pvtb = KbdTables->pCharModifiers->pVkToBit; pvtb->Vk; pvtb++) {
    if(shiftidx & pvtb->ModBits) {
      switch(pvtb->Vk) {
        case VK_MENU: ShiftFlags |= RALTFLAG; break;
        case VK_SHIFT: ShiftFlags |= K_SHIFTFLAG; break;
        case VK_CONTROL: ShiftFlags |= LCTRLFLAG; break;
      }
    }
  }

  return ShiftFlags;
}

WORD CharToUSVK_NT(WORD ch, WORD *shift)
{
	PVK_TO_WCHARS1 pv;
	PVK_TO_WCHAR_TABLE pvt;
	int i, j;

	/* Find the appropriate keyboard tables */

	if(!KbdTables) return 0;				/* Could not load keyboard DLL */

	/* Map the character back to the virtual key */

	for(i = 0, pvt = KbdTables->pVkToWcharTable; pvt->pVkToWchars; i++, pvt++)
		for(pv = pvt->pVkToWchars; pv->VirtualKey;)
		{
			for(j = 0; j < pvt->nModifications; j++)
				if(pv->wch[j] == ch)
				{
					/* Return the new virtual key */
          *shift = ModificationToShift_NT(j);
          if(*shift == 0xFFFF) return 0;
          return pv->VirtualKey;
				}
			pv = (PVK_TO_WCHARS1) ((BYTE *)pv + pvt->cbSize);
		}

	/* Remapping not found */
	return 0;
}


int GetDeadkeys_NT(WORD DeadKey, WORD *OutputPairs) {
  WORD *p = OutputPairs, shift;
	for(int i = 0; KbdTables->pDeadKey[i].dwBoth; i++) {
		if(HIWORD(KbdTables->pDeadKey[i].dwBoth) == DeadKey) {
			WORD vk = CharToUSVK_NT(LOWORD(KbdTables->pDeadKey[i].dwBoth), &shift);
      if(vk != 0) {
        *p++ = vk;
        *p++ = shift;
        *p++ = KbdTables->pDeadKey[i].wchComposed;
      } else {
        LogError(L"Warning: complex deadkey not supported.");
      }
		}
	}
  *p = 0;
  return (INT_PTR)(p-OutputPairs);
}

WORD VKUSToVKUnderlyingLayout_NT(WORD VKey) {

	/* Num lock test -- MCD: 02/12/2002 Fix num lock not working bug */

	if((VKey >= VK_NUMPAD0 && VKey <= VK_NUMPAD9) || VKey == VK_DECIMAL) return VKey;

	/* Find the appropriate keyboard tables */

	if(!KbdTables)				/* Could not load keyboard DLL */
		return VKey;

  if(VKey > 255) return VKey;

	UINT scan = USVirtualKeyToScanCode[VKey];
	if(scan == 0 || scan >= KbdTables->bMaxVSCtoVK) return VKey;
	return KbdTables->pusVSCtoVK[scan];
}

WORD VKUnderlyingLayoutToVKUS_NT(WORD VKey) {

	/* Num lock test -- MCD: 02/12/2002 Fix num lock not working bug */

	if((VKey >= VK_NUMPAD0 && VKey <= VK_NUMPAD9) || VKey == VK_DECIMAL) return VKey;

	/* Find the appropriate keyboard tables */

	if(!KbdTables)				/* Could not load keyboard DLL */
		return VKey;

  UINT scan;
  for(scan = 1; scan <= KbdTables->bMaxVSCtoVK; scan++) {
    if(KbdTables->pusVSCtoVK[scan] == VKey) {
      break;
    }
  }
  if(scan > KbdTables->bMaxVSCtoVK || scan > 127) {
    // no translation.
    return VKey;
  }

  return ScanCodeToUSVirtualKey[scan];
}
