#include <windows.h>
#include <atomic>
#include <mutex>
#include "config.h"
#include "messages.h"
using namespace std;

#ifdef HHKM_UT
#include <vector>
extern vector<INPUT> sent_inputs;
void UT_SendInput(UINT, LPINPUT input, int)
{
    sent_inputs.push_back(*input);
}
#define SendInputInternal UT_SendInput
#else
#define SendInputInternal SendInput
#endif

#ifdef _DEBUG
#include <sstream>
#include "scan_codes.h"
void DebugSendInput(UINT n, LPINPUT input, int size)
{
    SendInputInternal(n, input, size);
    wstringstream wss;
    auto k = TOSCEXKEY(input->ki.wScan, input->ki.dwFlags & KEYEVENTF_EXTENDEDKEY);
    wss << hex << "SI: " << (input->ki.dwFlags & KEYEVENTF_KEYUP ? "U " : "D ") << HHKM::SCEXKeyToHEX(k);
    if (HHKM::ScanCodeName[k])
    {
        wss << " (" << HHKM::ScanCodeName[k] << ")";
    }
    wss << endl;
    OutputDebugString(wss.str().c_str());
}
#define SENDINPUT DebugSendInput
#else
#define SENDINPUT SendInput
#endif

#ifndef HHKM_UT
extern HHOOK the_hook;
extern HWND hMain;
extern bool do_not_drop_keys;
extern atomic<bool> configChanged;
wstring GetCurrentConfigPath();
bool IsAnyToEditHasFocus();
#endif
extern bool enableMapping;

HHKM::Config cfg_for_hook;
static HHKM::Config& cfg = cfg_for_hook;

static set<HHKM::SCEXKey> pressed_modifiers;    
static HHKM::FromKey last_from_key; 
static bool last_key_is_up = true;  
static auto last_from_key_down_time = GetTickCount64(); 
static HHKM::SCEXKey last_down_key;

#ifdef HHKM_UT
void Reset()
#else
static void Reset()
#endif
{
    pressed_modifiers.clear();
    last_from_key.Reset();
    last_key_is_up = true;
    last_from_key_down_time = 0;
    last_down_key = 0;
}

#ifdef _DEBUG
static void DebugPrint(const HHKM::FromKey& fk, const HHKM::ToKeys& tks)
{
    wstringstream wss;
    wss << HHKM::KeyToReadable(fk.normal_key, fk.modifiers, cfg) << " (";
    wss << HHKM::KeyToHEX(fk.normal_key, fk.modifiers) << ") ->";
    for (auto& tk : tks.tks)
    {
        wss << " ";
        wss << HHKM::KeyToReadable(tk.normal_key, tk.modifiers) << " (";
        wss << HHKM::KeyToHEX(tk.normal_key, tk.modifiers) << ")";
    }
    wss << " LKU: " << last_key_is_up << " DR: " << tks.disable_repeat << " DC: " << tks.double_click;
    wss << endl;
    OutputDebugString(wss.str().c_str());
}
#endif

static optional<HHKM::SCEXKey> GetValidLastPressKey(const HHKM::FromKey& fk)
{
    if (fk.normal_key) return fk.normal_key;

    for (auto k : fk.modifiers)
    {
        if (!HHKM::IsSystemModifier(k))
        {
            return k;
        }
    }

#define CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(sm) if (fk.modifiers.count(sm)) return sm;

    CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(WIN_R);
    CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(WIN_L);
    CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(ALT_R);
    CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(ALT_L);
    CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(CTRL_R);
    CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(CTRL_L);
    CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(SHIFT_R);
    CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(SHIFT_L);

    return {};
}

static void SendKeyDown(const HHKM::ToKeys& tks)
{
    INPUT i{ 0 };
    i.type = INPUT_KEYBOARD;

    for (auto& tk : tks.tks)
    {

        if (!tk.modifiers.empty())
        {
#define CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(mk) \
            if (tk.modifiers.end() != tk.modifiers.find(mk))\
            {\
                i.ki.wScan = SCEXKEY_SC(mk);\
                i.ki.dwFlags = KEYEVENTF_SCANCODE | (SCEXKEY_EX(mk) ? KEYEVENTF_EXTENDEDKEY : 0);\
                SENDINPUT(1, &i, sizeof(INPUT));\
            }

            CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(SHIFT_L);
            CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(SHIFT_R);
            CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(CTRL_L);
            CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(CTRL_R);
            CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(ALT_L);
            CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(ALT_R);
            CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(WIN_L);
            CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(WIN_R);
        }

        if (tk.normal_key)
        {
            i.ki.wScan = SCEXKEY_SC(*tk.normal_key);
            i.ki.dwFlags = KEYEVENTF_SCANCODE | (SCEXKEY_EX(*tk.normal_key) ? KEYEVENTF_EXTENDEDKEY : 0);
            SENDINPUT(1, &i, sizeof(INPUT));
        }
    }
}

static void SendKeyUp(const HHKM::ToKeys& tks)
{
    INPUT i{ 0 };
    i.type = INPUT_KEYBOARD;

    for (auto it = tks.tks.rbegin(); it != tks.tks.rend(); ++it)
    {
        auto& tk = *it;

        if (tk.normal_key)
        {
            i.ki.wScan = SCEXKEY_SC(*tk.normal_key);
            i.ki.dwFlags = KEYEVENTF_SCANCODE | (SCEXKEY_EX(*tk.normal_key) ? KEYEVENTF_EXTENDEDKEY : 0) | KEYEVENTF_KEYUP;
            SENDINPUT(1, &i, sizeof(INPUT));
        }

        set<HHKM::SCEXKey> system_modifiers;
        auto modifiers_copy = tk.modifiers;

#define EXTRACT_SYSTEM_MODIFIERS(mk) \
        if (modifiers_copy.end() != modifiers_copy.find(mk))\
        {\
            modifiers_copy.erase(mk);\
            system_modifiers.insert(mk);\
        }

        EXTRACT_SYSTEM_MODIFIERS(SHIFT_L);
        EXTRACT_SYSTEM_MODIFIERS(SHIFT_R);
        EXTRACT_SYSTEM_MODIFIERS(CTRL_L);
        EXTRACT_SYSTEM_MODIFIERS(CTRL_R);
        EXTRACT_SYSTEM_MODIFIERS(ALT_L);
        EXTRACT_SYSTEM_MODIFIERS(ALT_R);
        EXTRACT_SYSTEM_MODIFIERS(WIN_L);
        EXTRACT_SYSTEM_MODIFIERS(WIN_R);

        for (auto& mk : modifiers_copy)
        {
            i.ki.wScan = SCEXKEY_SC(mk);
            i.ki.dwFlags = KEYEVENTF_SCANCODE | (SCEXKEY_EX(mk) ? KEYEVENTF_EXTENDEDKEY : 0) | KEYEVENTF_KEYUP;
            SENDINPUT(1, &i, sizeof(INPUT));
        }

#define CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(mk) \
        if (system_modifiers.end() != system_modifiers.find(mk))\
        {\
            i.ki.wScan = SCEXKEY_SC(mk);\
            i.ki.dwFlags = KEYEVENTF_SCANCODE | (SCEXKEY_EX(mk) ? KEYEVENTF_EXTENDEDKEY : 0) | KEYEVENTF_KEYUP;\
            SENDINPUT(1, &i, sizeof(INPUT));\
        }

        CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(WIN_R);
        CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(WIN_L);
        CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(ALT_R);
        CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(ALT_L);
        CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(CTRL_R);
        CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(CTRL_L);
        CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(SHIFT_R);
        CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(SHIFT_L);
    }
}

void InjectEmptyKey()
{
    static HHKM::ToKeys tks;
    static once_flag init;
    call_once(init, [&] {
        HHKM::ToKeys::ToKey tk;
        tk.normal_key = TOSCEXKEY(0, false);
        tks.tks.push_back(tk);
    });
    SendKeyDown(tks);
    SendKeyUp(tks);
}

LRESULT CALLBACK HHKMHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (HC_ACTION == nCode)
    {
#ifndef HHKM_UT
        if (configChanged.exchange(false))
        {
#ifdef _DEBUG
            OutputDebugStringA("HOOK Proc reload config\r\n");
#endif
            cfg.Load(GetCurrentConfigPath().c_str());
            Reset();
        }
#endif
        auto hs = reinterpret_cast<LPKBDLLHOOKSTRUCT>(lParam);

        if (hs->flags & LLKHF_INJECTED) {}
        else
        {
            auto scexkey = TOSCEXKEY(hs->scanCode, hs->flags & LLKHF_EXTENDED);
            auto keyup = bool(hs->flags & LLKHF_UP);
            auto is_current_key_a_modifier = cfg.IsModifier(scexkey);
#ifndef HHKM_UT

            if (IsAnyToEditHasFocus())
            {
                is_current_key_a_modifier = HHKM::IsSystemModifier(scexkey);
            }
#endif
            if (keyup)
            {
                pressed_modifiers.erase(scexkey);   
            }
            else
            {
                if (is_current_key_a_modifier) pressed_modifiers.insert(scexkey);
            }
#ifndef HHKM_UT
            if (IsWindowVisible(hMain)) SendMessage(hMain, HHKM_WM_KEY_EVENT, (WPARAM)&pressed_modifiers, lParam);
            if (GetForegroundWindow() == hMain ||
                GetParent(GetForegroundWindow()) == hMain ||
                GetParent(GetParent(GetForegroundWindow())) == hMain)   
            {
                if (do_not_drop_keys)
                {

                }
                else
                {

                    return 1;
                }
            }
#endif
            optional<bool> drop_key;
            if (enableMapping)
            {
                {

                    auto& remapping = cfg.GetRemapping();
                    if (remapping.end() != remapping.find(scexkey))
                    {
                        auto remapped_key = remapping.at(scexkey);

                        if (keyup)
                        {
                            if (cfg.IsModifier(remapped_key)) pressed_modifiers.erase(remapped_key);
                        }
                        else
                        {
                            if (is_current_key_a_modifier) pressed_modifiers.erase(scexkey);
                            if (cfg.IsModifier(remapped_key)) pressed_modifiers.insert(remapped_key);

                        }

                        scexkey = remapped_key;
                        is_current_key_a_modifier = cfg.IsModifier(scexkey);
                    }
                }

                if (keyup)
                {

                    bool do_send_keyup = false;
                    if (!is_current_key_a_modifier)
                    {
                        if (last_from_key.normal_key &&
                            *last_from_key.normal_key == scexkey)
                        {
                            do_send_keyup = true;
                        }
                    }
                    else
                    {
                        if (last_from_key.modifiers.end() != last_from_key.modifiers.find(scexkey))
                        {
                            do_send_keyup = true;
                        }
                    }

                    if (do_send_keyup)
                    {
                        auto& mapping = cfg.GetMapping();
                        if (mapping.end() != mapping.find(last_from_key))
                        {

                            auto& tk = mapping.at(last_from_key);

                            if (tk.down_sent)
                            {

                                SendKeyUp(tk);
                                tk.down_sent = false;
                            }
                        }

                        last_from_key.Reset();
                        if (!drop_key) drop_key = true;
                    }
                }
                else    
                {
                    HHKM::FromKey fk;
                    fk.modifiers = pressed_modifiers;
                    if (!is_current_key_a_modifier) fk.normal_key = scexkey;

                    auto& mapping = cfg.GetMapping();
                    if (mapping.end() != mapping.find(fk))
                    {

                        auto& tk = mapping.at(fk);

#ifdef _DEBUG
                        DebugPrint(fk, tk);
#endif  
                        bool send_key_down = true;

                        if (!last_key_is_up && tk.disable_repeat && last_from_key == fk) send_key_down = false;

                        if (tk.double_click)
                        {

                            if (last_key_is_up && last_down_key == scexkey && (GetTickCount64() - last_from_key_down_time) < cfg.double_click_time_interval)
                            {

                            }
                            else
                            {
                                send_key_down = false;  

                            }
                        }

                        if (send_key_down)
                        {

                            for (auto& k : fk.modifiers)
                            {
                                if (cfg.not_drop_modifiers.count(k))
                                {

                                    InjectEmptyKey();

                                    HHKM::ToKeys modifiers_to_key_up;
                                    HHKM::ToKeys::ToKey tk;
                                    for (auto& k : fk.modifiers)
                                    {
                                        if (cfg.not_drop_modifiers.count(k))
                                        {
                                            tk.modifiers.insert(k);
                                        }
                                    }
                                    modifiers_to_key_up.tks.push_back(tk);
                                    SendKeyUp(modifiers_to_key_up);

                                    break;
                                }
                            }

                            SendKeyDown(tk);
                            tk.down_sent = true;
                        }

                        last_from_key = fk;

                        if (tk.double_click && send_key_down)
                        {

                            last_from_key_down_time = 0;
                        }
                        else
                        {
                            last_from_key_down_time = GetTickCount64();
                        }

                        if (!drop_key) drop_key = true;   
                    }
                    last_down_key = scexkey;
                }
            }
            else
            {
                Reset();    
            }

            if (!drop_key && is_current_key_a_modifier)
            {
                if (cfg.not_drop_modifiers.count(scexkey))
                {
                    drop_key = false;
                }
                else
                {
                    drop_key = true;   
                }
            }

            last_key_is_up = keyup;

            if (drop_key && *drop_key) return 1;
        }
    }
#ifndef HHKM_UT
    return CallNextHookEx(the_hook, nCode, wParam, lParam);
#else
    return 0;
#endif
}
#include <windows.h>
#include <atomic>
#include <mutex>
#include "config.h"
#include "messages.h"
using namespace std;

#ifdef HHKM_UT
#include <vector>
extern vector<INPUT> sent_inputs;
void UT_SendInput(UINT, LPINPUT input, int)
{
    sent_inputs.push_back(*input);
}
#define SendInputInternal UT_SendInput
#else
#define SendInputInternal SendInput
#endif

#ifdef _DEBUG
#include <sstream>
#include "scan_codes.h"
void DebugSendInput(UINT n, LPINPUT input, int size)
{
    SendInputInternal(n, input, size);
    wstringstream wss;
    auto k = TOSCEXKEY(input->ki.wScan, input->ki.dwFlags & KEYEVENTF_EXTENDEDKEY);
    wss << hex << "SI: " << (input->ki.dwFlags & KEYEVENTF_KEYUP ? "U " : "D ") << HHKM::SCEXKeyToHEX(k);
    if (HHKM::ScanCodeName[k])
    {
        wss << " (" << HHKM::ScanCodeName[k] << ")";
    }
    wss << endl;
    OutputDebugString(wss.str().c_str());
}
#define SENDINPUT DebugSendInput
#else
#define SENDINPUT SendInput
#endif


#ifndef HHKM_UT
extern HHOOK the_hook;
extern HWND hMain;
extern bool do_not_drop_keys;
extern atomic<bool> configChanged;
wstring GetCurrentConfigPath();
bool IsAnyToEditHasFocus();
#endif
extern bool enableMapping;

HHKM::Config cfg_for_hook;
static HHKM::Config& cfg = cfg_for_hook;

static set<HHKM::SCEXKey> pressed_modifiers;    //all modifiers
static HHKM::FromKey last_from_key; //for keyup, we need to know which mapping hit before key up
static bool last_key_is_up = true;  //for disable repeat
static auto last_from_key_down_time = GetTickCount64(); //for double click
static HHKM::SCEXKey last_down_key;

#ifdef HHKM_UT
void Reset()
#else
static void Reset()
#endif
{
    pressed_modifiers.clear();
    last_from_key.Reset();
    last_key_is_up = true;
    last_from_key_down_time = 0;
    last_down_key = 0;
}

#ifdef _DEBUG
static void DebugPrint(const HHKM::FromKey& fk, const HHKM::ToKeys& tks)
{
    wstringstream wss;
    wss << HHKM::KeyToReadable(fk.normal_key, fk.modifiers, cfg) << " (";
    wss << HHKM::KeyToHEX(fk.normal_key, fk.modifiers) << ") ->";
    for (auto& tk : tks.tks)
    {
        wss << " ";
        wss << HHKM::KeyToReadable(tk.normal_key, tk.modifiers) << " (";
        wss << HHKM::KeyToHEX(tk.normal_key, tk.modifiers) << ")";
    }
    wss << " LKU: " << last_key_is_up << " DR: " << tks.disable_repeat << " DC: " << tks.double_click;
    wss << endl;
    OutputDebugString(wss.str().c_str());
}
#endif

static optional<HHKM::SCEXKey> GetValidLastPressKey(const HHKM::FromKey& fk)
{
    if (fk.normal_key) return fk.normal_key;
    
    //return first !system modifier
    for (auto k : fk.modifiers)
    {
        if (!HHKM::IsSystemModifier(k))
        {
            return k;
        }
    }
    
#define CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(sm) if (fk.modifiers.count(sm)) return sm;

    CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(WIN_R);
    CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(WIN_L);
    CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(ALT_R);
    CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(ALT_L);
    CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(CTRL_R);
    CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(CTRL_L);
    CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(SHIFT_R);
    CHECK_IF_SYSTEM_MODIFIER_EXIST_AND_RETURN(SHIFT_L);

    return {};
}

static void SendKeyDown(const HHKM::ToKeys& tks)
{
    INPUT i{ 0 };
    i.type = INPUT_KEYBOARD;

    for (auto& tk : tks.tks)
    {
        //if tk has modifiers, press modifiers first
        //1st press system modifier
        if (!tk.modifiers.empty())
        {
#define CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(mk) \
            if (tk.modifiers.end() != tk.modifiers.find(mk))\
            {\
                i.ki.wScan = SCEXKEY_SC(mk);\
                i.ki.dwFlags = KEYEVENTF_SCANCODE | (SCEXKEY_EX(mk) ? KEYEVENTF_EXTENDEDKEY : 0);\
                SENDINPUT(1, &i, sizeof(INPUT));\
            }

            CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(SHIFT_L);
            CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(SHIFT_R);
            CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(CTRL_L);
            CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(CTRL_R);
            CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(ALT_L);
            CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(ALT_R);
            CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(WIN_L);
            CHECK_AND_SEND_SYSTEM_MODIFIER_KEY(WIN_R);
        }

        //2nd press noraml key

        if (tk.normal_key)
        {
            i.ki.wScan = SCEXKEY_SC(*tk.normal_key);
            i.ki.dwFlags = KEYEVENTF_SCANCODE | (SCEXKEY_EX(*tk.normal_key) ? KEYEVENTF_EXTENDEDKEY : 0);
            SENDINPUT(1, &i, sizeof(INPUT));
        }
    }
}

static void SendKeyUp(const HHKM::ToKeys& tks)
{
    INPUT i{ 0 };
    i.type = INPUT_KEYBOARD;

    for (auto it = tks.tks.rbegin(); it != tks.tks.rend(); ++it)
    {
        auto& tk = *it;

        //reverse order as keydown
        //1st is normal key
        if (tk.normal_key)
        {
            i.ki.wScan = SCEXKEY_SC(*tk.normal_key);
            i.ki.dwFlags = KEYEVENTF_SCANCODE | (SCEXKEY_EX(*tk.normal_key) ? KEYEVENTF_EXTENDEDKEY : 0) | KEYEVENTF_KEYUP;
            SENDINPUT(1, &i, sizeof(INPUT));
        }

        //2nd is user defined modifier
        set<HHKM::SCEXKey> system_modifiers;
        auto modifiers_copy = tk.modifiers;

#define EXTRACT_SYSTEM_MODIFIERS(mk) \
        if (modifiers_copy.end() != modifiers_copy.find(mk))\
        {\
            modifiers_copy.erase(mk);\
            system_modifiers.insert(mk);\
        }

        EXTRACT_SYSTEM_MODIFIERS(SHIFT_L);
        EXTRACT_SYSTEM_MODIFIERS(SHIFT_R);
        EXTRACT_SYSTEM_MODIFIERS(CTRL_L);
        EXTRACT_SYSTEM_MODIFIERS(CTRL_R);
        EXTRACT_SYSTEM_MODIFIERS(ALT_L);
        EXTRACT_SYSTEM_MODIFIERS(ALT_R);
        EXTRACT_SYSTEM_MODIFIERS(WIN_L);
        EXTRACT_SYSTEM_MODIFIERS(WIN_R);

        //now the modifiers_copy only has user defined modifiers
        for (auto& mk : modifiers_copy)
        {
            i.ki.wScan = SCEXKEY_SC(mk);
            i.ki.dwFlags = KEYEVENTF_SCANCODE | (SCEXKEY_EX(mk) ? KEYEVENTF_EXTENDEDKEY : 0) | KEYEVENTF_KEYUP;
            SENDINPUT(1, &i, sizeof(INPUT));
        }

        //3rd is system modifier
#define CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(mk) \
        if (system_modifiers.end() != system_modifiers.find(mk))\
        {\
            i.ki.wScan = SCEXKEY_SC(mk);\
            i.ki.dwFlags = KEYEVENTF_SCANCODE | (SCEXKEY_EX(mk) ? KEYEVENTF_EXTENDEDKEY : 0) | KEYEVENTF_KEYUP;\
            SENDINPUT(1, &i, sizeof(INPUT));\
        }

        //reverse system modifier order
        CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(WIN_R);
        CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(WIN_L);
        CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(ALT_R);
        CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(ALT_L);
        CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(CTRL_R);
        CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(CTRL_L);
        CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(SHIFT_R);
        CHECK_AND_SEND_SYSTEM_MODIFIER_KEY_UP(SHIFT_L);
    }
}

void InjectEmptyKey()
{
    static HHKM::ToKeys tks;
    static once_flag init;
    call_once(init, [&] {
        HHKM::ToKeys::ToKey tk;
        tk.normal_key = TOSCEXKEY(0, false);
        tks.tks.push_back(tk);
    });
    SendKeyDown(tks);
    SendKeyUp(tks);
}

LRESULT CALLBACK HHKMHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (HC_ACTION == nCode)
    {
#ifndef HHKM_UT
        if (configChanged.exchange(false))
        {
#ifdef _DEBUG
            OutputDebugStringA("HOOK Proc reload config\r\n");
#endif
            cfg.Load(GetCurrentConfigPath().c_str());
            Reset();
        }
#endif
        auto hs = reinterpret_cast<LPKBDLLHOOKSTRUCT>(lParam);

        if (hs->flags & LLKHF_INJECTED) {}
        else
        {
            auto scexkey = TOSCEXKEY(hs->scanCode, hs->flags & LLKHF_EXTENDED);
            auto keyup = bool(hs->flags & LLKHF_UP);
            auto is_current_key_a_modifier = cfg.IsModifier(scexkey);
#ifndef HHKM_UT
            //special case: when current focus HWND is TO editor in Add Mapping dlg
            //  the acceptable modifiers should be system modifiers only
            if (IsAnyToEditHasFocus())
            {
                is_current_key_a_modifier = HHKM::IsSystemModifier(scexkey);
            }
#endif
            if (keyup)
            {
                pressed_modifiers.erase(scexkey);   //if scexkey not a pressed modifier, the erase() will do nothing
            }
            else
            {
                if (is_current_key_a_modifier) pressed_modifiers.insert(scexkey);
            }
#ifndef HHKM_UT
            if (IsWindowVisible(hMain)) SendMessage(hMain, HHKM_WM_KEY_EVENT, (WPARAM)&pressed_modifiers, lParam);
            if (GetForegroundWindow() == hMain ||
                GetParent(GetForegroundWindow()) == hMain ||
                GetParent(GetParent(GetForegroundWindow())) == hMain)   //support check max 3 deep window layer
            {
                if (do_not_drop_keys)
                {
                    //when GUI need input something
                }
                else
                {
                    //when the GUI window have focus, drop the key, mapping not working
                    return 1;
                }
            }
#endif
            optional<bool> drop_key;
            if (enableMapping)
            {
                {
                    //remapping
                    auto& remapping = cfg.GetRemapping();
                    if (remapping.end() != remapping.find(scexkey))
                    {
                        auto remapped_key = remapping.at(scexkey);

                        if (keyup)
                        {
                            if (cfg.IsModifier(remapped_key)) pressed_modifiers.erase(remapped_key);
                        }
                        else
                        {
                            if (is_current_key_a_modifier) pressed_modifiers.erase(scexkey);
                            if (cfg.IsModifier(remapped_key)) pressed_modifiers.insert(remapped_key);

                        }

                        scexkey = remapped_key;
                        is_current_key_a_modifier = cfg.IsModifier(scexkey);
                    }
                }

                
                if (keyup)
                {
                    //current key is any of last mapped key, then send up
                    bool do_send_keyup = false;
                    if (!is_current_key_a_modifier)
                    {
                        if (last_from_key.normal_key &&
                            *last_from_key.normal_key == scexkey)
                        {
                            do_send_keyup = true;
                        }
                    }
                    else
                    {
                        if (last_from_key.modifiers.end() != last_from_key.modifiers.find(scexkey))
                        {
                            do_send_keyup = true;
                        }
                    }

                    if (do_send_keyup)
                    {
                        auto& mapping = cfg.GetMapping();
                        if (mapping.end() != mapping.find(last_from_key))
                        {
                            //normally, last_from_key is in mapping
                            //so this if always in
                            auto& tk = mapping.at(last_from_key);

                            if (tk.down_sent)
                            {
                                //only send key up if the key down already sent
                                SendKeyUp(tk);
                                tk.down_sent = false;
                            }
                        }

                        //if current FromKey has any not drop system modifier keys, we already make them keyup, here we can make them press again, if any of them not release yet
                        //e.g. Ctrl config as not drop, Ctrl + S mapping to Shift + Ctrl + S
                        //  1. press Ctrl, because not drop Ctrl, the system know the Ctrl pressed
                        //  2. press S, now we have a key mapping hit
                        //      we inject an empty key down and up
                        //      make Ctrl key up
                        //      send key down of Shift, Ctrl and S
                        //      drop this S key down
                        //  3. release S, because we store the last Ctrl + S in `last_from_key`, we know to send key up for Shift, Ctrl and S
                        //          FIXME: the Ctrl key not release yet, so here we send the key down for Ctrl to restore the real Ctrl state
                        //      drop this S key up
                        //  4. release Ctrl, just send the key up

                        //FIXME: or we do not restore the keydown state, because we already intercepted the key combination
                        //for (auto& k : last_from_key.modifiers)
                        //{
                        //    if (pressed_modifiers.count(k) && cfg.not_drop_modifiers.count(k))
                        //    {
                        //
                        //        HHKM::ToKeys modifiers_need_to_restore_key_down_state;
                        //        HHKM::ToKeys::ToKey tk;
                        //        for (auto& k : last_from_key.modifiers)
                        //        {
                        //            if (pressed_modifiers.count(k) && cfg.not_drop_modifiers.count(k))
                        //            {
                        //                tk.modifiers.insert(k);
                        //            }
                        //        }
                        //        modifiers_need_to_restore_key_down_state.tks.push_back(tk);
                        //        SendKeyDown(modifiers_need_to_restore_key_down_state);
                        //
                        //        break;
                        //    }
                        //}

                        last_from_key.Reset();
                        if (!drop_key) drop_key = true;
                    }
                }
                else    //keydown
                {
                    HHKM::FromKey fk;
                    fk.modifiers = pressed_modifiers;
                    if (!is_current_key_a_modifier) fk.normal_key = scexkey;

                    auto& mapping = cfg.GetMapping();
                    if (mapping.end() != mapping.find(fk))
                    {
                        //there is a mapping, send the key down
                        auto& tk = mapping.at(fk);

#ifdef _DEBUG
                        DebugPrint(fk, tk);
#endif  
                        bool send_key_down = true;

                        //if mapped key disable repeat
                        if (!last_key_is_up && tk.disable_repeat && last_from_key == fk) send_key_down = false;

                        if (tk.double_click)
                        {
                            //double click pass condition:
                            //  disable repeat, so last key should be key up
                            //  click the same button twice
                            //  click time interval should less than the double click threshold
                            if (last_key_is_up && last_down_key == scexkey && (GetTickCount64() - last_from_key_down_time) < cfg.double_click_time_interval)
                            {
                                //double click check pass
                            }
                            else
                            {
                                send_key_down = false;  //need double click, but not meet the hit condition
                                //TODO: if support not drop normal key if not pass double click check, set drop_key = false;
                                //  remember to make same process in key up, or the key up will lost
                            }
                        }

                        if (send_key_down)
                        {
                            //if any system modifiers pressed and any of them do not drop, we need to let the system think they're done
                            //  1. inject empty key to "give" them a function
                            //  2. then make them key up
                            for (auto& k : fk.modifiers)
                            {
                                if (cfg.not_drop_modifiers.count(k))
                                {
                                    //1.
                                    InjectEmptyKey();

                                    //2.
                                    HHKM::ToKeys modifiers_to_key_up;
                                    HHKM::ToKeys::ToKey tk;
                                    for (auto& k : fk.modifiers)
                                    {
                                        if (cfg.not_drop_modifiers.count(k))
                                        {
                                            tk.modifiers.insert(k);
                                        }
                                    }
                                    modifiers_to_key_up.tks.push_back(tk);
                                    SendKeyUp(modifiers_to_key_up);

                                    break;
                                }
                            }

                            SendKeyDown(tk);
                            tk.down_sent = true;
                        }

                        last_from_key = fk;

                        if (tk.double_click && send_key_down)
                        {
                            //after send a double click key down, the next double click should also need 2 fast press
                            //e.g.  fast press A A, send one double click
                            //      fast press A A A, send one double click
                            //      fast press A A A A, send two double click
                            //to implement this trick, just set the last_from_key_down_time to a much small value
                            //  at leat GetTickCount64() - cfg.double_click_time_interval
                            //  but, since the tick cout is always increase, set to 0 is much easy and fast
                            last_from_key_down_time = 0;
                        }
                        else
                        {
                            last_from_key_down_time = GetTickCount64();
                        }

                        if (!drop_key) drop_key = true;   //current keys mapped to other keys, so current keys should not send
                    }
                    last_down_key = scexkey;
                }
            }
            else
            {
                Reset();    //switch to disable is a good place to reset status
            }

            if (!drop_key && is_current_key_a_modifier)
            {
                if (cfg.not_drop_modifiers.count(scexkey))
                {
                    drop_key = false;
                }
                else
                {
                    drop_key = true;   //any other modifier will lost it's original meaning
                }
            }

            last_key_is_up = keyup;

            if (drop_key && *drop_key) return 1;
        }
    }
#ifndef HHKM_UT
    return CallNextHookEx(the_hook, nCode, wParam, lParam);
#else
    return 0;
#endif
}
