#include "keyboarddevice.h"
#include "macro.h"
#include "logger.h"


#define POSTMESSAGE_NORMAL          0x00000001 /* for normal messages */
#define POSTMESSAGE_EXTENDED        0x01000001 /* for extended messages */
#define POSTMESSAGE_UP_NORMAL       0xC0000001 /* for normal keyup messages */
#define POSTMESSAGE_UP_EXTENDED     0xC1000001 /* for extended keyup messages */
#define POSTMESSAGE_ALT_LPARAM      (1 << 29)

#ifndef VK_ALT
    #define VK_ALT                      164
#endif

KeyboardDevice::KeyboardDevice()
{
    delay = 50; // 50ms default
    attachedHwnd = NULL;

}

HWND KeyboardDevice::getAttachedHwnd()
{
    return attachedHwnd;
}

void KeyboardDevice::setAttachedInput(HWND hwnd)
{
    attachedHwnd = hwnd;
}

void KeyboardDevice::setDelay(unsigned int _delay)
{
    delay = _delay;
}

bool KeyboardDevice::keyIsExtended(unsigned int key)
{
    unsigned int extendedList[] = { VK_INSERT, VK_DELETE, VK_HOME, VK_END,
        VK_DIVIDE, VK_LWIN, VK_RWIN, VK_PRIOR, VK_NEXT, VK_LEFT, VK_RIGHT,
        VK_UP, VK_DOWN, 0
    };

    for(unsigned int i = 0; extendedList[i] != 0; i++)
        if( extendedList[i] == key )
            return true;

    return false;
}

void KeyboardDevice::keyHold(unsigned int key, unsigned int modifier)
{
    unsigned int scan = key;
    if( MapVirtualKey(key, 0) != 0 )
        scan = MapVirtualKey(key, 0);

    LPARAM lparam;
    INPUT inp;
    inp.type = INPUT_KEYBOARD;

    if( keyIsExtended(key) ) {
        lparam = (scan << 16 ) | POSTMESSAGE_EXTENDED;
        inp.ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_SCANCODE;
    } else {
        lparam = (scan << 16 ) | POSTMESSAGE_NORMAL;
        inp.ki.dwFlags = KEYEVENTF_SCANCODE;
    }

    inp.ki.wScan = scan;
    inp.ki.dwExtraInfo = lparam;


    if( attachedHwnd && !IsWindow(attachedHwnd) )
        Macro::instance()->forcedDetach();


    if( attachedHwnd ) {
        if( modifier ) {
            BYTE ks[256] = { 0 };
            GetKeyboardState(ks);

            if( modifier == VK_ALT ) // Remap from left alt to actual "Menu"
                modifier = VK_MENU;

            // Set key down state
            if( !(ks[modifier] & 0x80) )
                ks[modifier] |= 0x80;

            SetKeyboardState(ks);
        }

        PostMessage(attachedHwnd, WM_KEYDOWN, key, lparam);
            //SendMessage(attachedHwnd, WM_KEYDOWN, key, lparam);
    } else {
        SendInput(1, &inp, sizeof(INPUT));
    }
}

void KeyboardDevice::keyRelease(unsigned int key, unsigned int modifier)
{
    unsigned int scan = key;
    if( MapVirtualKey(key, 0) != 0 )
        scan = MapVirtualKey(key, 0);

    LPARAM lparam;
    INPUT inp;
    inp.type = INPUT_KEYBOARD;

    if( keyIsExtended(key) ) {
        lparam = (scan << 16) | POSTMESSAGE_UP_EXTENDED;
        inp.ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP; }
    else {
        lparam = (scan << 16) | POSTMESSAGE_UP_NORMAL;
        inp.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP;
    }

    inp.ki.wScan = scan;
    inp.ki.dwExtraInfo = lparam;


    if( attachedHwnd && !IsWindow(attachedHwnd) )
        Macro::instance()->forcedDetach();


    if( attachedHwnd )
	{
        PostMessage(attachedHwnd, WM_KEYUP, key, lparam);
        //SendMessage(attachedHwnd, WM_KEYUP, key, lparam);
	}
    else
        SendInput(1, &inp, sizeof(INPUT));
}

void KeyboardDevice::keyPress(unsigned int key, unsigned int modifier)
{
    keyHold(key, modifier);
	Sleep(delay);
    keyRelease(key, modifier);
}

bool KeyboardDevice::keyPressed(unsigned int key)
{
    if ( keyConsolePressed(key) )
        return true;

    if( GetAsyncKeyState(key) )
        return true;
    else
        return false;
}

bool KeyboardDevice::keyPressedLocal(unsigned int key)
{
    HWND focus = GetForegroundWindow();

    if( focus == getAppHwnd() || focus == attachedHwnd )
    {
        if ( keyConsolePressed(key) )
            return true;

        if( GetAsyncKeyState(key))
            return true;
        else
            return false;
    }
    else
        return false;
}

bool KeyboardDevice::keyConsolePressed(unsigned int key)
{
    HANDLE hStdin;
    INPUT_RECORD buf[128];
    ULONG count = 0;

    hStdin = GetStdHandle(STD_INPUT_HANDLE);

    PeekConsoleInput( hStdin, buf, 128, &count);

    for (ULONG i = 0; i < count; i++)
    {
        switch(buf[i].EventType)
        {
            case KEY_EVENT: // keyboard input
                if(buf[i].Event.KeyEvent.bKeyDown)
                {
                    //printf("Key pressed: 0x%04X\n", buf[i].Event.KeyEvent.wVirtualKeyCode);
                    //printf("Key checked: 0x%04X\n", key);

                    if (key == buf[i].Event.KeyEvent.wVirtualKeyCode)
                    {
                        FlushConsoleInputBuffer(hStdin);

                        return true;
                    }
                }

                break;
        }
    }

    return false;
}

void KeyboardDevice::type(std::string message)
{ //http://www.cambiaresearch.com/c4/702b8cd1-e5b0-42e6-83ac-25f0306e3e25/Javascript-Char-Codes-Key-Codes.aspx
    for(unsigned int i = 0; i < message.length(); i++)
    {
        char thischar = message.at(i);
        if( thischar == '\n' ) {
            keyPress(VK_RETURN);
            continue;
        }

        char vk = VkKeyScan(thischar);
        //char keyscan = MapVirtualKey(vk, 0);

        int capitol = ( thischar >= 'A' && thischar <= 'Z' );
        int needshift = (thischar >= '!' && thischar <= '&') ||
            (thischar >= '(' && thischar <= '+') ||
            (thischar == ':') || (thischar == '<') || (thischar == '>') ||
            (thischar == '?') || (thischar == '@') || (thischar == '^') ||
            (thischar == '_') || (thischar >= '{' && thischar <= '~');

        if( capitol || needshift )
            keyHold(VK_SHIFT);

        /*
        keybd_event(vk, keyscan, 0, 0);
        Sleep(delay);
        keybd_event(vk, keyscan, KEYEVENTF_KEYUP, 0);
        Sleep(delay);
        */

        keyPress(vk);

        if( capitol || needshift )
            keyRelease(VK_SHIFT);
    }
}

void KeyboardDevice::clearBuffer()
{
    while( kbhit() ) getch();
}


std::string KeyboardDevice::getKeyName(unsigned int key)
{
    UINT scan = MapVirtualKey(key, 0);
    LPARAM lparam;

    if (scan != 0)
        lparam = scan << 16;
    else
    {
        // Ugly fallback when mapping keys fails (e.g. on WINE for the keypad)
        switch (key)
        {
            case VK_NUMPAD0:
                return std::string("NumPad 0");
            case VK_NUMPAD1:
                return std::string("NumPad 1");
            case VK_NUMPAD2:
                return std::string("NumPad 2");
            case VK_NUMPAD3:
                return std::string("NumPad 3");
            case VK_NUMPAD4:
                return std::string("NumPad 4");
            case VK_NUMPAD5:
                return std::string("NumPad 5");
            case VK_NUMPAD6:
                return std::string("NumPad 6");
            case VK_NUMPAD7:
                return std::string("NumPad 7");
            case VK_NUMPAD8:
                return std::string("NumPad 8");
            case VK_NUMPAD9:
                return std::string("NumPad 9");
            case VK_ADD:
                return std::string("NumPad +");
            case VK_SUBTRACT:
                return std::string("NumPad -");
            case VK_DIVIDE:
                return std::string("NumPad /");
            case VK_MULTIPLY:
                return std::string("NumPad *");
            case VK_DECIMAL:
                return std::string("NumPad .");
        }
    }

    if( keyIsExtended(key) )
        lparam |= POSTMESSAGE_EXTENDED;

    char buf[256];

    GetKeyNameText(lparam, buf, 256);
    return std::string((char*)buf);
}

