/*
 *  AppleKeyMapUtils.c
 *  
 *  Created by Jief on 25 May 2018.
 *
 */

#include <Library/BaseLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/DebugLib.h>
#include <Library/PrintLib.h>
#include <Library/MemLogLib.h>

#include <IndustryStandard/AppleHid.h>
#include <Protocol/AppleKeyMapDatabase.h>

// DBG_TO: 0=no debug, 1=serial, 2=console 3=log
// serial requires
// [PcdsFixedAtBuild]
//  gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x07
//  gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0xFFFFFFFF
// in package DSC file

#ifdef CLOVER_DEBUG
#define DBG_APPLEKEYFEEDER 2
#endif

#if DBG_APPLEKEYFEEDER == 3
#define DBG(...) MemLog(FALSE, 0, __VA_ARGS__)
#elif DBG_APPLEKEYFEEDER == 2
#define DBG(...) AsciiPrint(__VA_ARGS__)
#elif DBG_APPLEKEYFEEDER == 1
#define DBG(...) DebugPrint(1, __VA_ARGS__)
#else
#define DBG(...)
#endif



EFI_GUID gAppleKeyMapDatabaseProtocolGuid = {0x584B9EBE, 0x80C1, 0x4BD6, {0x98, 0xB0, 0xA7, 0x78, 0x6E, 0xC2, 0xF2, 0xE2}};
APPLE_KEY_MAP_DATABASE_PROTOCOL *AppleKeyMapDb = NULL;
UINTN AppleKeyMapDbIndex = 0;

static EFI_STATUS getAppleKeyMapDb()
{
  EFI_STATUS Status = EFI_SUCCESS;

    Status = gBS->LocateProtocol (
                  &gAppleKeyMapDatabaseProtocolGuid,
                  NULL,
                  (VOID **)&AppleKeyMapDb
                  );
	if (!EFI_ERROR (Status))
	{
		Status = AppleKeyMapDb->CreateKeyStrokesBuffer (
								AppleKeyMapDb,
								6,
								&AppleKeyMapDbIndex
								);
		if (EFI_ERROR (Status)) {
			DBG("AppleKeyFeederEntrypoint: CreateKeyStrokesBuffer failed, Status=%x\n", Status);
		}
	}else{
		DBG("AppleKeyFeederEntrypoint: LocateProtocol failed, Status=%x\n", Status);
	}
	return Status;
}



static EFI_EVENT eventKeyRelease;

static VOID
EFIAPI
SendKeyRelease (
  IN    EFI_EVENT    Event,
  IN    VOID         *Context
  )
{
  EFI_STATUS Status;
//DBG("SendKeyRelease\n");

  APPLE_KEY appleKey; // APPLE_KEY is UINT16
    	Status = AppleKeyMapDb->SetKeyStrokeBufferKeys (
							   AppleKeyMapDb,
							   AppleKeyMapDbIndex,
							   (APPLE_MODIFIER_MAP)0,
							   0,
							   &appleKey
							   );
		if (EFI_ERROR (Status)) {
    		DBG("SendKeyRelease: SetKeyStrokeBufferKeys failed, Status=%x\n", Status);
		}

    	Status = gBS->SetTimer (
					  eventKeyRelease,
					  TimerCancel,
					  10*1000
					  );
		if (EFI_ERROR (Status)) {
    		DBG("SendKeyRelease: SetTimer failed, Status=%x\n", Status);
		}
}



static int MapKeyData2AppleKey(EFI_KEY_DATA* KeyData, APPLE_KEY* pKey, UINT8* pCurModifierMap)
{
	if ( KeyData->Key.UnicodeChar == 0 )
	{
		*pCurModifierMap = 0;
		switch (KeyData->Key.ScanCode) {
			case 0x01: // up arrow
				*pKey = 0x7052;
				return 1;
			case 0x02: // down arrow
				*pKey = 0x7051;
				return 1;
			case 0x03: // left arrow
				*pKey = 0x7050;
				return 1;
			case 0x04: // right arrow
				*pKey = 0x704F;
				return 1;
		}
		return 0;
	}
	if ( KeyData->Key.UnicodeChar >= 'a'  &&  KeyData->Key.UnicodeChar <= 'z' ) {
		*pKey = 0x7004 + ( KeyData->Key.UnicodeChar - 'a');
		*pCurModifierMap = 0;
		return 1;
	}
	if ( KeyData->Key.UnicodeChar >= 'A'  &&  KeyData->Key.UnicodeChar <= 'Z' ) {
		*pKey = 0x7004 + ( KeyData->Key.UnicodeChar - 'A');
		*pCurModifierMap = 2;
		return 1;
	}
	if ( KeyData->Key.UnicodeChar == '0' ) { // Could have put that in the switch, but wanted to make very clear that the 0 wasn't forgotten and that this : "KeyData->Key.UnicodeChar >= '1'" (instead of '0') is not a mistake !!!
		*pKey = 0x7027;
		return 1;
	}
	if ( KeyData->Key.UnicodeChar >= '1'  &&  KeyData->Key.UnicodeChar <= '9' ) {
		*pKey = 0x701E + ( KeyData->Key.UnicodeChar - '1');
		return 1;
	}

	*pCurModifierMap = 0;
	switch (KeyData->Key.UnicodeChar) {
		case 0x01: // up arrow
			*pKey = 0x7052;
			return 1;
		case 0x02: // down arrow
			*pKey = 0x7051;
			return 1;
		case 0x03: // left arrow
			*pKey = 0x7050;
			return 1;
		case 0x04: // right arrow
			*pKey = 0x704F;
			return 1;
		case 0x08: // backspace
			*pKey = 0x702A;
			return 1;
		case 0x0D: // return
			*pKey = 0x7028;
			return 1;
		case ' ': // return
			*pKey = 0x702C;
			return 1;
		case '!': // return
			*pKey = 0x701E;
			*pCurModifierMap = 2;
			return 1;
		case '"': // return
			*pKey = 0x7034;
			*pCurModifierMap = 2;
			return 1;
		case '#': // return
			*pKey = 0x7020;
			*pCurModifierMap = 2;
			return 1;
		case '$': // return
			*pKey = 0x7021;
			*pCurModifierMap = 2;
			return 1;
		case '%': // return
			*pKey = 0x7022;
			*pCurModifierMap = 2;
			return 1;
		case '&': // return
			*pKey = 0x7024;
			*pCurModifierMap = 2;
			return 1;
		case '\'': // return
			*pKey = 0x7034;
			return 1;
		case '(': // return
			*pKey = 0x7026;
			*pCurModifierMap = 2;
			return 1;
		case ')': // return
			*pKey = 0x7027;
			*pCurModifierMap = 2;
			return 1;
		case '*': // return
			*pKey = 0x7025;
			*pCurModifierMap = 2;
			return 1;
		case '+': // return
			*pKey = 0x702E;
			*pCurModifierMap = 2;
			return 1;
		case ',': // return
			*pKey = 0x7036;
			return 1;
		case '-': // return
			*pKey = 0x702D;
			return 1;
		case '.': // return
			*pKey = 0x7037;
			return 1;
		case '/': // return
			*pKey = 0x7038;
			return 1;

		case ':': // return
			*pKey = 0x7033;
			*pCurModifierMap = 2;
			return 1;
		case ';': // return
			*pKey = 0x7033;
			return 1;
		case '<': // return
			*pKey = 0x7036;
			*pCurModifierMap = 2;
			return 1;
		case '=': // return
			*pKey = 0x702E;
			return 1;
		case '>': // return
			*pKey = 0x7037;
			*pCurModifierMap = 2;
			return 1;
		case '?': // return
			*pKey = 0x7038;
			*pCurModifierMap = 2;
			return 1;
		case '@': // return
			*pKey = 0x701F;
			*pCurModifierMap = 2;
			return 1;


		case '[': // return
			*pKey = 0x702F;
			return 1;
		case '\\': // return
			*pKey = 0x7031;
			return 1;
		case ']': // return
			*pKey = 0x7030;
			return 1;
		case '^': // return
			*pKey = 0x7023;
			*pCurModifierMap = 2;
			return 1;
		case '_': // return
			*pKey = 0x702D;
			*pCurModifierMap = 2;
			return 1;
		case '`': // return
			*pKey = 0x7035;
			return 1;

		case '{': // return
			*pKey = 0x702F;
			*pCurModifierMap = 2;
			return 1;
		case '|': // return
			*pKey = 0x7031;
			*pCurModifierMap = 2;
			return 1;
		case '}': // return
			*pKey = 0x7030;
			*pCurModifierMap = 2;
			return 1;
		case '~': // return
			*pKey = 0x7035;
			*pCurModifierMap = 2;
			return 1;
	}
	return 0;
}


EFI_STATUS
EFIAPI
SendDataToAppleMap(IN EFI_KEY_DATA *KeyData)
{
//DBG("SendDataToAppleMap ScanCode=%x, Uchar=%x, ShiftState=%x, ToogleState=%x\n", KeyData->Key.ScanCode, KeyData->Key.UnicodeChar, KeyData->KeyState.KeyShiftState, KeyData->KeyState.KeyToggleState);

  EFI_STATUS Status;
  UINT8 CurModifierMap  = 0;
  UINTN NumberOfKeys = 1;
  APPLE_KEY appleKey; // APPLE_KEY is UINT16

	if ( !AppleKeyMapDb ) {
		Status = getAppleKeyMapDb();
    	if (EFI_ERROR (Status)) {
    		DBG("SendDataToAppleMap: getAppleKeyMapDb() failed, Status=%x\n", Status);
    	}
	}
	if ( !AppleKeyMapDb ) {
		DBG("SendDataToAppleMap: AppleKeyMapDb==NULL, Status=%x\n", EFI_NOT_FOUND);
		return EFI_NOT_FOUND;
	}

    if ( MapKeyData2AppleKey(KeyData, &appleKey, &CurModifierMap) )
    {
    	Status = AppleKeyMapDb->SetKeyStrokeBufferKeys (
							   AppleKeyMapDb,
							   AppleKeyMapDbIndex,
							   (APPLE_MODIFIER_MAP)CurModifierMap,
							   NumberOfKeys,
							   &appleKey
							   );
    	if (EFI_ERROR (Status)) {
    		DBG("SendDataToAppleMap: SetKeyStrokeBufferKeys failed, Status=%x\n", Status);
    		return Status;
    	}


		Status = gBS->SetTimer (
					  eventKeyRelease,
					  TimerPeriodic,
					  10*1000*15 // 15 ms
					  );
		if (EFI_ERROR (Status)) {
			// if this fail, we end up having the key repeated.
    		DBG("SendDataToAppleMap: SetTimer failed, Status=%x\n", Status);
    		return Status;
		}

    	return Status;
    }
DBG("SendDataToAppleMap: key not mapped, sorry\n");
    return EFI_NOT_FOUND;
}


/****************************************************************
 * Entry point
 ***************************************************************/

EFI_STATUS
EFIAPI
AppleKeyMapUtilsInit (
                    IN EFI_HANDLE           ImageHandle,
                    IN EFI_SYSTEM_TABLE		*SystemTable
                    )
{
DBG("AppleKeyMapUtilsInit\n");
  EFI_STATUS Status = EFI_SUCCESS;
  
	//
	// Setup a periodic timer, used for send a key release to apple key map
	//
	Status = gBS->CreateEvent (
				  EVT_TIMER | EVT_NOTIFY_SIGNAL,
				  TPL_NOTIFY,
				  SendKeyRelease,
				  NULL,
				  &eventKeyRelease
				  );
	if ( EFI_ERROR (Status) ) {
		DBG("AppleKeyMapUtilsInit: CreateEvent failed, Status=%x\n", Status);
	}

DBG("AppleKeyMapUtilsInit returns %x\n", Status);
  return Status;
}

