/*
  Name:             AIWin2000Unicode
  Copyright:        Copyright (C) SIL International.
  Documentation:    
  Description:      
  Create Date:      22 Jan 2007

  Modified Date:    9 Aug 2015
  Authors:          mcdurdin
  Related Files:    
  Dependencies:     

  Bugs:             
  Todo:             
  Notes:            
  History:          22 Jan 2007 - mcdurdin - Fix for K_NPENTER
                    13 Jul 2007 - mcdurdin - I934 - Prep fox x64
                    23 Aug 2007 - mcdurdin - I719 - Fix Alt+LeftShift and Word interactions
                    14 Jun 2008 - mcdurdin - I1389 - Supp chars on Vista+ default to single BKSP
                    20 Jul 2008 - mcdurdin - I1546 - Language switch not working with some keyboard IDs
                    16 Jan 2009 - mcdurdin - I1512 - SendInput support
                    27 Jan 2009 - mcdurdin - I1797 - Add fallback for AIWin2000 app integration
                    11 Dec 2009 - mcdurdin - I934 - x64 - Initial version
                    12 Mar 2010 - mcdurdin - I934 - x64 - Complete
                    12 Mar 2010 - mcdurdin - I2229 - Remove hints and warnings
                    15 Jun 2010 - mcdurdin - I2426 - Remove incorrectl KEYMAN_CHARFLAG
                    24 Jun 2010 - mcdurdin - I2436 - Add space to context for AIWin2000Unicode when not matched
                    27 Aug 2012 - mcdurdin - I3438 - V9.0 - Add support for custom virtual keys Created
                    18 Feb 2012 - mcdurdin - I3242 - Arrow keys do not function correctly in Internet Explorer
                    26 Feb 2012 - mcdurdin - I2787 - Reset keyboard state for a key when key down is sent to avoid confusion for Chrome
                    04 Nov 2012 - mcdurdin - I3527 - V9.0 - Merge of I3242 - Arrow keys do not function correctly in Internet Explorer
                    04 Nov 2012 - mcdurdin - I3528 - V9.0 - Merge of I2787 - Reset keyboard state for a key when key down is sent to avoid confusion for Chrome
                    17 Dec 2013 - mcdurdin - I4006 - V9.0 - Remove old aiDefault code
                    24 Apr 2014 - mcdurdin - I4196 - V9.0 - wm_kmmoreposting must be refactored for TIP work as it is not sequential
                    01 May 2014 - mcdurdin - I4128 - V9.0 - Shift states still not working with unprocessed keys in V9
                    23 Jun 2014 - mcdurdin - I4287 - V9.0 - Remove extraneous AppIntegration class type tests
                    13 Aug 2014 - mcdurdin - I4370 - Deadkeys are still not working in Winword TIP mode
                    14 Aug 2014 - mcdurdin - I4378 - V9.0 - Rapid typing in legacy mode breaks Keyman input
                    13 Oct 2014 - mcdurdin - I4452 - V9.0 - Chinese keyboard is not working correctly
                    31 Dec 2014 - mcdurdin - I4548 - V9.0 - When Alt is down, release of Ctrl, Shift is not detectable within TIP in some languages
                    09 Aug 2015 - mcdurdin - I4844 - Tidy up PostDummyKeyEvent calls
*/
#include "pch.h"   // I4128   // I4287
#include "serialkeyeventclient.h"

#define KEYMAN_MOREPOST	"WM_KMMOREPOST"

#ifndef WM_UNICHAR
#define WM_UNICHAR	0x0109
#define UNICODE_NOCHAR	0xFFFF
#endif

AIWin2000Unicode::AIWin2000Unicode()
{
	context = new AppContext;
}

AIWin2000Unicode::~AIWin2000Unicode()
{
	delete context;
}

/* Information functions */


BOOL AIWin2000Unicode::CanHandleWindow(HWND ahwnd)
{
  UNREFERENCED_PARAMETER(ahwnd);
	return TRUE;
}

BOOL AIWin2000Unicode::HandleWindow(HWND ahwnd)
{ 
	if(hwnd != ahwnd)
	{
		hwnd = ahwnd; 
		context->Reset();
	}
	return TRUE; 
}

BOOL AIWin2000Unicode::IsWindowHandled(HWND ahwnd)
{
	return (hwnd == ahwnd);
}

BOOL AIWin2000Unicode::IsUnicode() 
{ 
  BOOL Result = IsWindowUnicode(hwnd);
  SendDebugMessageFormat(0, sdmAIDefault, 0, "IsWindowUnicode=%s", Result ? "Yes" : "No");
	return Result; 
}

/* Context functions */

void AIWin2000Unicode::ReadContext()
{
}
	
void AIWin2000Unicode::AddContext(WCHAR ch)  //I2436
{
  context->Add(ch);
}

void AIWin2000Unicode::ResetContext()
{
	context->Reset();
}

WCHAR *AIWin2000Unicode::ContextBuf(int n)
{
	return context->Buf(n);
}

WCHAR *AIWin2000Unicode::ContextBufMax(int n)
{
	return context->BufMax(n);
}

void AIWin2000Unicode::SetContext(const WCHAR* buf)
{
  return context->Set(buf);
}
	
BYTE SavedKbdState[256];

BOOL AIWin2000Unicode::SendActions()   // I4196
{
	if(*Globals::hwndIM()) {
    PostMessage(*Globals::hwndIM(), wm_keymanim_contextchanged, 0, 0);
  }
	return PostKeys();
}

BOOL AIWin2000Unicode::QueueAction(int ItemType, DWORD dwData)
{
	int result = AppIntegration::QueueAction(ItemType, dwData);

  //SendDebugMessageFormat(hwnd, sdmAIDefault, 0, "App::QueueAction ItemType=%d dwData=%x", ItemType, dwData);
	
	switch(ItemType)
	{
	case QIT_VKEYDOWN:
		break;

	case QIT_DEADKEY:
		context->Add(UC_SENTINEL);
		context->Add(CODE_DEADKEY);
		context->Add((WORD) dwData);
		break;

	case QIT_CHAR:
		context->Add((WORD) dwData);
		break;

	case QIT_BACK:
		if(dwData & BK_BACKSPACE)
			while(context->CharIsDeadkey()) context->Delete();
		//if(dwData == CODE_DEADKEY) break;
		context->Delete();
		if(dwData & BK_BACKSPACE)
			while(context->CharIsDeadkey()) context->Delete();
		break;
	}

	return result;
}

BOOL AIWin2000Unicode::QueueDebugInformation(int ItemType, LPGROUP Group, LPKEY Rule, PWSTR fcontext, PWSTR foutput, DWORD_PTR dwExtraFlags)
{
  UNREFERENCED_PARAMETER(ItemType);
  UNREFERENCED_PARAMETER(Group);
  UNREFERENCED_PARAMETER(Rule);
  UNREFERENCED_PARAMETER(fcontext);
  UNREFERENCED_PARAMETER(foutput);
  UNREFERENCED_PARAMETER(dwExtraFlags);
	return TRUE;
}

// I1512 - SendInput with VK_PACKET for greater robustness

BOOL AIWin2000Unicode::PostKeys()
{	
  PKEYMAN64THREADDATA _td = ThreadGlobals();
  if(!_td) {
    return FALSE;
  }

  if(QueueSize == 0) 
	{
		return TRUE;
	}

  if (_td->CustomPostKeyCallback != nullptr) {
    BOOL res = _td->CustomPostKeyCallback(Queue, QueueSize);
    QueueSize = 0;
    return res;
  }

  int n = 0;
	/* 6.0.153.0: Fix repeat state for virtual keys */

  LPINPUT pInputs = new INPUT[QueueSize*100]; // TODO: Tidy this up. Horrid doing a junky alloc like this each event
  int i = 0;

  for(; n < QueueSize; n++)
  {
	  switch(Queue[n].ItemType) {
	  case QIT_VKEYDOWN:
		  if((Queue[n].dwData & QVK_KEYMASK) == 0x05) Queue[n].dwData = (Queue[n].dwData & QVK_FLAGMASK) | VK_RETURN; // I649  // I3438
  		
		  /* 6.0.153.0: Fix repeat state for virtual keys */

      if((Queue[n].dwData & QVK_KEYMASK) <= VK__MAX)  // I3438
      {
        pInputs[i].type = INPUT_KEYBOARD;
      pInputs[i].ki.wVk = (WORD)Queue[n].dwData & 0xFF;  // I3242 - noted as unclean but was not cause of the issue  // I3438   // I3527

        pInputs[i].ki.wScan = SCAN_FLAG_KEYMAN_KEY_EVENT;
        pInputs[i].ki.dwFlags = ((Queue[n].dwData & QVK_EXTENDED) ? KEYEVENTF_EXTENDEDKEY : 0);  // I3438

        pInputs[i].ki.time = 0;
        pInputs[i++].ki.dwExtraInfo = 0; //KEYEVENT_EXTRAINFO_KEYMAN;   // I4370   // I4378
      }

      //// I2787 - Reset keyboard state so Chrome doesn't get confused with VK_RETURN   // I3528
      {
        BYTE keystate[256];
        GetKeyboardState(keystate);
        if(keystate[Queue[n].dwData & 0xFF] & 0x80)
        {
          keystate[Queue[n].dwData & 0xFF] = 0;
          SetKeyboardState(keystate);
        }
      }
      //// I2787 - END CHROME TEST   // I3528

		  break;
	  case QIT_VKEYUP:
		  if((Queue[n].dwData & QVK_KEYMASK) == 0x05) Queue[n].dwData = (Queue[n].dwData & QVK_FLAGMASK) | VK_RETURN; // I649  // I3438

      if((Queue[n].dwData & QVK_KEYMASK) <= VK__MAX)  // I3438
      {
        pInputs[i].type = INPUT_KEYBOARD;
        pInputs[i].ki.wVk = (WORD) Queue[n].dwData & 0xFF;  // I3242 - noted as unclean but was not cause of the issue  // I3438   // I3527
        pInputs[i].ki.wScan = SCAN_FLAG_KEYMAN_KEY_EVENT;
        pInputs[i].ki.dwFlags = KEYEVENTF_KEYUP | ((Queue[n].dwData & QVK_EXTENDED) ? KEYEVENTF_EXTENDEDKEY : 0);  // I3438
        pInputs[i].ki.time = 0;
        pInputs[i++].ki.dwExtraInfo = 0; //KEYEVENT_EXTRAINFO_KEYMAN;   // I4370   // I4378
      }

		  break;
	  case QIT_VSHIFTDOWN:
		  break;
	  case QIT_VSHIFTUP:
		  break;
	  case QIT_CHAR:
      pInputs[i].type = INPUT_KEYBOARD;
      pInputs[i].ki.wVk = 0;
      pInputs[i].ki.wScan = (WORD) Queue[n].dwData;
      pInputs[i].ki.dwFlags = KEYEVENTF_UNICODE;
      pInputs[i].ki.time = 0;
      pInputs[i++].ki.dwExtraInfo = 0; //KEYEVENT_EXTRAINFO_KEYMAN;   // I4370   // I4378

      pInputs[i].type = INPUT_KEYBOARD;
      pInputs[i].ki.wVk = 0;
      pInputs[i].ki.wScan = (WORD) Queue[n].dwData;
      pInputs[i].ki.dwFlags = KEYEVENTF_KEYUP | KEYEVENTF_UNICODE;
      pInputs[i].ki.time = 0;
      pInputs[i++].ki.dwExtraInfo = 0; //KEYEVENT_EXTRAINFO_KEYMAN;   // I4370   // I4378

      break;
	  case QIT_DEADKEY:
		  break;
	  case QIT_BELL:
		  MessageBeep(MB_ICONASTERISK); //(UINT) -1);
		  break;
	  case QIT_BACK:
		  if(Queue[n].dwData & BK_DEADKEY) break;
		  if(Addin_ProcessBackspace(hwnd)) break;

      pInputs[i].type = INPUT_KEYBOARD;
      pInputs[i].ki.wVk = VK_BACK;
      pInputs[i].ki.wScan = SCAN_FLAG_KEYMAN_KEY_EVENT;
      pInputs[i].ki.dwFlags = ((Queue[n].dwData & 0x0100) ? KEYEVENTF_EXTENDEDKEY : 0);
      pInputs[i].ki.time = 0;
      pInputs[i++].ki.dwExtraInfo = 0; //KEYEVENT_EXTRAINFO_KEYMAN;   // I4370   // I4378

      pInputs[i].type = INPUT_KEYBOARD;
      pInputs[i].ki.wVk = VK_BACK;
      pInputs[i].ki.wScan = SCAN_FLAG_KEYMAN_KEY_EVENT;
      pInputs[i].ki.dwFlags = KEYEVENTF_KEYUP | ((Queue[n].dwData & 0x0100) ? KEYEVENTF_EXTENDEDKEY : 0);
      pInputs[i].ki.time = 0;
      pInputs[i++].ki.dwExtraInfo = 0; //KEYEVENT_EXTRAINFO_KEYMAN;   // I4370   // I4378

      break;
	  }
  }

	QueueSize = 0;

  if(ShouldDebug(sdmAIDefault)) {
    for(int j = 0; j < i; j++ ) {   // I4548
      SendDebugMessageFormat(0, sdmAIDefault, 0, "App::PostKeys: sending input [i=%d, input[%d]=vk:%s scan:%x flags:%x", i, j, Debug_VirtualKey(pInputs[j].ki.wVk), pInputs[j].ki.wScan, pInputs[j].ki.dwFlags);
    }
  }

  SetLastError(0);

  if(i > 0) {   // I4452
    _td->pSerialKeyEventClient->SignalServer(pInputs, i);
  }

  SendDebugMessageFormat(0, sdmAIDefault, 0, "App::PostKeys: sending input finished");

  delete[] pInputs;

  return TRUE;
}
