// KeyboardManagerEditor.cpp : Defines the entry point for the application.
//

#include "pch.h"
#include "KeyboardManagerEditor.h"

#include <common/utils/winapi_error.h>
#include <common/utils/logger_helper.h>
#include <common/utils/ProcessWaiter.h>
#include <common/utils/UnhandledExceptionHandler_x64.h>

#include <trace.h>

#include <keyboardmanager/common/KeyboardEventHandlers.h>

#include <EditKeyboardWindow.h>
#include <EditShortcutsWindow.h>
#include <KeyboardManagerState.h>

std::unique_ptr<KeyboardManagerEditor> editor = nullptr;
const std::wstring instanceMutexName = L"Local\\PowerToys_KBMEditor_InstanceMutex";

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);

    LoggerHelpers::init_logger(KeyboardManagerConstants::ModuleName, L"Editor", LogSettings::keyboardManagerLoggerName);
    InitUnhandledExceptionHandler_x64();
    Trace::RegisterProvider();

    auto mutex = CreateMutex(nullptr, true, instanceMutexName.c_str());
    if (mutex == nullptr)
    {
        Logger::error(L"Failed to create mutex. {}", get_last_error_or_default(GetLastError()));
    }
    else
    {
        Logger::trace(L"Created/Opened {} mutex", instanceMutexName);
    }

    if (GetLastError() == ERROR_ALREADY_EXISTS)
    {
        Logger::info(L"KBM editor instance is already running");
        return 0;
    }

    int numArgs;
    LPWSTR* cmdArgs = CommandLineToArgvW(GetCommandLineW(), &numArgs);

    KeyboardManagerEditorType type = KeyboardManagerEditorType::KeyEditor;
    if (!IsDebuggerPresent())
    {
        if (cmdArgs == nullptr)
        {
            Logger::error(L"Keyboard Manager Editor cannot start as a standalone application");
            return -1;
        }

        if (numArgs < 2)
        {
            Logger::error(L"Invalid arguments on Keyboard Manager Editor start");
            return -1;
        }
    }

    if (numArgs > 1)
    {
        type = static_cast<KeyboardManagerEditorType>(_wtoi(cmdArgs[1]));
    }
    
    if (numArgs == 3)
    {
        std::wstring pid = std::wstring(cmdArgs[2]);
        Logger::trace(L"Editor started from the settings with pid {}", pid);
        if (!pid.empty())
        {
            auto mainThreadId = GetCurrentThreadId();
            ProcessWaiter::OnProcessTerminate(pid, [mainThreadId](int err) {
                if (err != ERROR_SUCCESS)
                {
                    Logger::error(L"Failed to wait for parent process exit. {}", get_last_error_or_default(err));
                }

                Logger::trace(L"Parent process exited. Exiting KeyboardManager editor");
                PostThreadMessage(mainThreadId, WM_QUIT, 0, 0);
            });
        }
    }

    editor = std::make_unique<KeyboardManagerEditor>(hInstance);
    if (!editor->StartLowLevelKeyboardHook())
    {
        DWORD errorCode = GetLastError();
        show_last_error_message(L"SetWindowsHookEx", errorCode, L"PowerToys - Keyboard Manager Editor");
        auto errorMessage = get_last_error_message(errorCode);
        Logger::error(L"Unable to start keyboard hook: {}", errorMessage.has_value() ? errorMessage.value() : L"");
        Trace::Error(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"start_lowlevel_keyboard_hook.SetWindowsHookEx");
        
        return -1;
    }
    
    editor->OpenEditorWindow(type);
    
    editor = nullptr;

    Trace::UnregisterProvider();
    return 0;
}

KeyboardManagerEditor::KeyboardManagerEditor(HINSTANCE hInst) :
    hInstance(hInst)
{
    bool loadedSuccessful = mappingConfiguration.LoadSettings();
    if (!loadedSuccessful)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(500));

        // retry once
        mappingConfiguration.LoadSettings();
    }

    StartLowLevelKeyboardHook();
}

KeyboardManagerEditor::~KeyboardManagerEditor()
{
    UnhookWindowsHookEx(hook);
}

bool KeyboardManagerEditor::StartLowLevelKeyboardHook()
{
#if defined(DISABLE_LOWLEVEL_HOOKS_WHEN_DEBUGGED)
    if (IsDebuggerPresent())
    {
        return true;
    }
#endif

    hook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyHookProc, GetModuleHandle(NULL), NULL);
    return (hook != nullptr);
}

void KeyboardManagerEditor::OpenEditorWindow(KeyboardManagerEditorType type)
{
    switch (type)
    {
    case KeyboardManagerEditorType::KeyEditor:
        CreateEditKeyboardWindow(hInstance, keyboardManagerState, mappingConfiguration);
        break;
    case KeyboardManagerEditorType::ShortcutEditor:
        CreateEditShortcutsWindow(hInstance, keyboardManagerState, mappingConfiguration);
    }
}

intptr_t KeyboardManagerEditor::HandleKeyboardHookEvent(LowlevelKeyboardEvent* data) noexcept
{
    // If the Detect Key Window is currently activated, then suppress the keyboard event
    Helpers::KeyboardHookDecision singleKeyRemapUIDetected = keyboardManagerState.DetectSingleRemapKeyUIBackend(data);
    if (singleKeyRemapUIDetected == Helpers::KeyboardHookDecision::Suppress)
    {
        return 1;
    }
    else if (singleKeyRemapUIDetected == Helpers::KeyboardHookDecision::SkipHook)
    {
        return 0;
    }

    // If the Detect Shortcut Window from Remap Keys is currently activated, then suppress the keyboard event
    Helpers::KeyboardHookDecision remapKeyShortcutUIDetected = keyboardManagerState.DetectShortcutUIBackend(data, true);
    if (remapKeyShortcutUIDetected == Helpers::KeyboardHookDecision::Suppress)
    {
        return 1;
    }
    else if (remapKeyShortcutUIDetected == Helpers::KeyboardHookDecision::SkipHook)
    {
        return 0;
    }

    // If the Detect Shortcut Window is currently activated, then suppress the keyboard event
    Helpers::KeyboardHookDecision shortcutUIDetected = keyboardManagerState.DetectShortcutUIBackend(data, false);
    if (shortcutUIDetected == Helpers::KeyboardHookDecision::Suppress)
    {
        return 1;
    }
    else if (shortcutUIDetected == Helpers::KeyboardHookDecision::SkipHook)
    {
        return 0;
    }

    return 0;
}

// Hook procedure definition
LRESULT KeyboardManagerEditor::KeyHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    LowlevelKeyboardEvent event;
    if (nCode == HC_ACTION)
    {
        event.lParam = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
        event.wParam = wParam;
        if (editor->HandleKeyboardHookEvent(&event) == 1)
        {
            // Reset Num Lock whenever a NumLock key down event is suppressed since Num Lock key state change occurs before it is intercepted by low level hooks
            if (event.lParam->vkCode == VK_NUMLOCK && (event.wParam == WM_KEYDOWN || event.wParam == WM_SYSKEYDOWN) && event.lParam->dwExtraInfo != KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG)
            {
                KeyboardEventHandlers::SetNumLockToPreviousState(editor->GetInputHandler());
            }
            return 1;
        }
    }

    return CallNextHookEx(hook, nCode, wParam, lParam);
}
