#include "pch.h"
#include "KeyboardManagerEditorLibraryWrapper.h"
#include <algorithm>
#include <cstring>
#include <vector>
#include <string>
#include <memory>

#include <common/utils/logger_helper.h>
#include <keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.h>
#include <keyboardmanager/KeyboardManagerEditorLibrary/EditorHelpers.h>
#include <common/interop/keyboard_layout.h>

extern "C"
{
    void* CreateMappingConfiguration()
    {
        return new MappingConfiguration();
    }

    void DestroyMappingConfiguration(void* config)
    {
        delete static_cast<MappingConfiguration*>(config);
    }

    bool LoadMappingSettings(void* config)
    {
        return static_cast<MappingConfiguration*>(config)->LoadSettings();
    }

    bool SaveMappingSettings(void* config)
    {
        return static_cast<MappingConfiguration*>(config)->SaveSettingsToFile();
    }

    wchar_t* AllocateAndCopyString(const std::wstring& str)
    {
        size_t len = str.length();
        wchar_t* buffer = new wchar_t[len + 1];
        wcscpy_s(buffer, len + 1, str.c_str());
        return buffer;
    }

    int GetSingleKeyRemapCount(void* config)
    {
        auto mapping = static_cast<MappingConfiguration*>(config);
        return static_cast<int>(mapping->singleKeyReMap.size());
    }

    bool GetSingleKeyRemap(void* config, int index, SingleKeyMapping* mapping)
    {
        auto mappingConfig = static_cast<MappingConfiguration*>(config);

        std::vector<std::pair<DWORD, KeyShortcutTextUnion>> allMappings;

        for (const auto& kv : mappingConfig->singleKeyReMap)
        {
            allMappings.push_back(kv);
        }

        if (index < 0 || index >= allMappings.size())
        {
            return false;
        }

        const auto& kv = allMappings[index];
        mapping->originalKey = static_cast<int>(kv.first);

        // Remap to single key
        if (kv.second.index() == 0)
        {
            mapping->targetKey = AllocateAndCopyString(std::to_wstring(std::get<DWORD>(kv.second)));
            mapping->isShortcut = false;
        }
        // Remap to shortcut
        else if (kv.second.index() == 1)
        {
            mapping->targetKey = AllocateAndCopyString(std::get<Shortcut>(kv.second).ToHstringVK().c_str());
            mapping->isShortcut = true;
        }
        else
        {
            mapping->targetKey = AllocateAndCopyString(L"");
            mapping->isShortcut = false;
        }

        return true;
    }

    int GetSingleKeyToTextRemapCount(void* config)
    {
        auto mapping = static_cast<MappingConfiguration*>(config);
        return static_cast<int>(mapping->singleKeyToTextReMap.size());
    }

    bool GetSingleKeyToTextRemap(void* config, int index, KeyboardTextMapping* mapping)
    {
        auto mappingConfig = static_cast<MappingConfiguration*>(config);

        if (index < 0 || index >= mappingConfig->singleKeyToTextReMap.size())
        {
            return false;
        }

        auto it = mappingConfig->singleKeyToTextReMap.begin();
        std::advance(it, index);

        mapping->originalKey = static_cast<int>(it->first);
        std::wstring text = std::get<std::wstring>(it->second);
        mapping->targetText = AllocateAndCopyString(text);

        return true;
    }

    int GetShortcutRemapCountByType(void* config, int operationType)
    {
        auto mapping = static_cast<MappingConfiguration*>(config);
        int count = 0;

        for (const auto& kv : mapping->osLevelShortcutReMap)
        {
            bool shouldCount = false;

            if (operationType == 0)
            {
                if ((kv.second.targetShortcut.index() == 0) ||
                    (kv.second.targetShortcut.index() == 1 &&
                     std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::RemapShortcut))
                {
                    shouldCount = true;
                }
            }
            else if (operationType == 1)
            {

                if (kv.second.targetShortcut.index() == 1 &&
                    std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
                {
                    shouldCount = true;
                }
            }
            else if (operationType == 2)
            {
                if (kv.second.targetShortcut.index() == 1 &&
                    std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
                {
                    shouldCount = true;
                }
            }
            else if (operationType == 3)
            {
                if (kv.second.targetShortcut.index() == 2)
                {
                    shouldCount = true;
                }
            }

            if (shouldCount)
            {
                count++;
            }
        }

        for (const auto& appMap : mapping->appSpecificShortcutReMap)
        {
            for (const auto& shortcutKv : appMap.second)
            {
                bool shouldCount = false;

                if (operationType == 0)
                {
                    if ((shortcutKv.second.targetShortcut.index() == 0) ||
                        (shortcutKv.second.targetShortcut.index() == 1 &&
                         std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::RemapShortcut))
                    {
                        shouldCount = true;
                    }
                }
                else if (operationType == 1)
                {
                    if (shortcutKv.second.targetShortcut.index() == 1 &&
                        std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
                    {
                        shouldCount = true;
                    }
                }
                else if (operationType == 2)
                {
                    if (shortcutKv.second.targetShortcut.index() == 1 &&
                        std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
                    {
                        shouldCount = true;
                    }
                }
                else if (operationType == 3)
                {
                    if (shortcutKv.second.targetShortcut.index() == 2)
                    {
                        shouldCount = true;
                    }
                }

                if (shouldCount)
                {
                    count++;
                }
            }
        }

        return count;
    }

bool GetShortcutRemapByType(void* config, int operationType, int index, ShortcutMapping* mapping)
    {
        auto mappingConfig = static_cast<MappingConfiguration*>(config);

        std::vector<std::tuple<Shortcut, KeyShortcutTextUnion, std::wstring>> filteredMappings;

        for (const auto& kv : mappingConfig->osLevelShortcutReMap)
        {
            bool shouldAdd = false;

            switch (operationType)
            {
            case 0: // RemapShortcut
                if ((kv.second.targetShortcut.index() == 0) ||
                    (kv.second.targetShortcut.index() == 1 &&
                     std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::RemapShortcut))
                {
                    shouldAdd = true;
                }
                break;

            case 1: // RunProgram
                if (kv.second.targetShortcut.index() == 1 &&
                    std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
                {
                    shouldAdd = true;
                }
                break;

            case 2: // OpenURI
                if (kv.second.targetShortcut.index() == 1 &&
                    std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
                {
                    shouldAdd = true;
                }
                break;

            case 3:
                if (kv.second.targetShortcut.index() == 2)
                {
                    shouldAdd = true;
                }
                break;

            default:
                break;
            }

            if (shouldAdd)
            {
                filteredMappings.push_back(std::make_tuple(kv.first, kv.second.targetShortcut, L""));
            }
        }

        for (const auto& appKv : mappingConfig->appSpecificShortcutReMap)
        {
            for (const auto& shortcutKv : appKv.second)
            {
                bool shouldAdd = false;

                switch (operationType)
                {
                case 0: // RemapShortcut
                    if ((shortcutKv.second.targetShortcut.index() == 0) ||
                        (shortcutKv.second.targetShortcut.index() == 1 &&
                         std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::RemapShortcut))
                    {
                        shouldAdd = true;
                    }
                    break;

                case 1: // RunProgram
                    if (shortcutKv.second.targetShortcut.index() == 1 &&
                        std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
                    {
                        shouldAdd = true;
                    }
                    break;

                case 2: // OpenURI
                    if (shortcutKv.second.targetShortcut.index() == 1 &&
                        std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
                    {
                        shouldAdd = true;
                    }
                    break;

                case 3:
                    if (shortcutKv.second.targetShortcut.index() == 2)
                    {
                        shouldAdd = true;
                    }
                    break;

                default:
                    break;
                }

                if (shouldAdd)
                {
                    filteredMappings.push_back(std::make_tuple(
                        shortcutKv.first, shortcutKv.second.targetShortcut, appKv.first));
                }
            }
        }

        if (index < 0 || index >= filteredMappings.size())
        {
            return false;
        }

        const auto& [origShortcut, targetShortcutUnion, app] = filteredMappings[index];

        std::wstring origKeysStr = origShortcut.ToHstringVK().c_str();
        mapping->originalKeys = AllocateAndCopyString(origKeysStr);
        mapping->targetApp = AllocateAndCopyString(app);

        if (targetShortcutUnion.index() == 0)
        {
            DWORD targetKey = std::get<DWORD>(targetShortcutUnion);
            mapping->targetKeys = AllocateAndCopyString(std::to_wstring(targetKey));
            mapping->operationType = 0;
            mapping->targetText = AllocateAndCopyString(L"");
            mapping->programPath = AllocateAndCopyString(L"");
            mapping->programArgs = AllocateAndCopyString(L"");
            mapping->uriToOpen = AllocateAndCopyString(L"");
        }
        else if (targetShortcutUnion.index() == 1)
        {
            Shortcut targetShortcut = std::get<Shortcut>(targetShortcutUnion);
            std::wstring targetKeysStr = targetShortcut.ToHstringVK().c_str();

            mapping->operationType = static_cast<int>(targetShortcut.operationType);

            switch (targetShortcut.operationType)
            {
            case Shortcut::OperationType::RunProgram:
                mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
                mapping->targetText = AllocateAndCopyString(L"");
                mapping->programPath = AllocateAndCopyString(targetShortcut.runProgramFilePath);
                mapping->programArgs = AllocateAndCopyString(targetShortcut.runProgramArgs);
                mapping->uriToOpen = AllocateAndCopyString(L"");
                break;

            case Shortcut::OperationType::OpenURI:
                mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
                mapping->targetText = AllocateAndCopyString(L"");
                mapping->programPath = AllocateAndCopyString(L"");
                mapping->programArgs = AllocateAndCopyString(L"");
                mapping->uriToOpen = AllocateAndCopyString(targetShortcut.uriToOpen);
                break;

            default:
                mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
                mapping->targetText = AllocateAndCopyString(L"");
                mapping->programPath = AllocateAndCopyString(L"");
                mapping->programArgs = AllocateAndCopyString(L"");
                mapping->uriToOpen = AllocateAndCopyString(L"");
                break;
            }
        }
        else if (targetShortcutUnion.index() == 2)
        {
            std::wstring text = std::get<std::wstring>(targetShortcutUnion);
            mapping->targetKeys = AllocateAndCopyString(L"");
            mapping->operationType = 0;
            mapping->targetText = AllocateAndCopyString(text);
            mapping->programPath = AllocateAndCopyString(L"");
            mapping->programArgs = AllocateAndCopyString(L"");
            mapping->uriToOpen = AllocateAndCopyString(L"");
        }

        return true;
    }

    int GetShortcutRemapCount(void* config)
    {
        auto mapping = static_cast<MappingConfiguration*>(config);
        int count = static_cast<int>(mapping->osLevelShortcutReMap.size());

        for (const auto& appMap : mapping->appSpecificShortcutReMap)
        {
            count += static_cast<int>(appMap.second.size());
        }

        return count;
    }

    bool GetShortcutRemap(void* config, int index, ShortcutMapping* mapping)
    {
        auto mappingConfig = static_cast<MappingConfiguration*>(config);

        std::vector<std::tuple<Shortcut, KeyShortcutTextUnion, std::wstring>> allMappings;

        for (const auto& kv : mappingConfig->osLevelShortcutReMap)
        {
            allMappings.push_back(std::make_tuple(kv.first, kv.second.targetShortcut, L""));
        }

        for (const auto& appKv : mappingConfig->appSpecificShortcutReMap)
        {
            for (const auto& shortcutKv : appKv.second)
            {
                allMappings.push_back(std::make_tuple(
                    shortcutKv.first, shortcutKv.second.targetShortcut, appKv.first));
            }
        }

        if (index < 0 || index >= allMappings.size())
        {
            return false;
        }

        const auto& [origShortcut, targetShortcutUnion, app] = allMappings[index];

        std::wstring origKeysStr = origShortcut.ToHstringVK().c_str();
        mapping->originalKeys = AllocateAndCopyString(origKeysStr);

        mapping->targetApp = AllocateAndCopyString(app);

        if (targetShortcutUnion.index() == 0)
        {
            DWORD targetKey = std::get<DWORD>(targetShortcutUnion);
            mapping->targetKeys = AllocateAndCopyString(std::to_wstring(targetKey));
            mapping->operationType = 0;
            mapping->targetText = AllocateAndCopyString(L"");
            mapping->programPath = AllocateAndCopyString(L"");
            mapping->programArgs = AllocateAndCopyString(L"");
            mapping->uriToOpen = AllocateAndCopyString(L"");
        }
        else if (targetShortcutUnion.index() == 1)
        {
            Shortcut targetShortcut = std::get<Shortcut>(targetShortcutUnion);
            std::wstring targetKeysStr = targetShortcut.ToHstringVK().c_str();

            mapping->operationType = static_cast<int>(targetShortcut.operationType);

            if (targetShortcut.operationType == Shortcut::OperationType::RunProgram)
            {
                mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
                mapping->targetText = AllocateAndCopyString(L"");
                mapping->programPath = AllocateAndCopyString(targetShortcut.runProgramFilePath);
                mapping->programArgs = AllocateAndCopyString(targetShortcut.runProgramArgs);
                mapping->uriToOpen = AllocateAndCopyString(L"");
            }
            else if (targetShortcut.operationType == Shortcut::OperationType::OpenURI)
            {
                mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
                mapping->targetText = AllocateAndCopyString(L"");
                mapping->programPath = AllocateAndCopyString(L"");
                mapping->programArgs = AllocateAndCopyString(L"");
                mapping->uriToOpen = AllocateAndCopyString(targetShortcut.uriToOpen);
            }
            else
            {
                mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
                mapping->targetText = AllocateAndCopyString(L"");
                mapping->programPath = AllocateAndCopyString(L"");
                mapping->programArgs = AllocateAndCopyString(L"");
                mapping->uriToOpen = AllocateAndCopyString(L"");
            }
        }
        else if (targetShortcutUnion.index() == 2)
        {
            std::wstring text = std::get<std::wstring>(targetShortcutUnion);
            mapping->targetKeys = AllocateAndCopyString(L"");
            mapping->operationType = 0;
            mapping->targetText = AllocateAndCopyString(text);
            mapping->programPath = AllocateAndCopyString(L"");
            mapping->programArgs = AllocateAndCopyString(L"");
            mapping->uriToOpen = AllocateAndCopyString(L"");
        }

        return true;
    }

    void FreeString(wchar_t* str)
    {
        delete[] str;
    }

    bool AddSingleKeyRemap(void* config, int originalKey, int targetKey)
    {
        auto mappingConfig = static_cast<MappingConfiguration*>(config);
        return mappingConfig->AddSingleKeyRemap(static_cast<DWORD>(originalKey), static_cast<DWORD>(targetKey));
    }

    bool AddSingleKeyToTextRemap(void* config, int originalKey, const wchar_t* text)
    {
        auto mappingConfig = static_cast<MappingConfiguration*>(config);

        if (text == nullptr)
        {
            return false;
        }

        return mappingConfig->AddSingleKeyToTextRemap(static_cast<DWORD>(originalKey), text);
    }

    bool AddSingleKeyToShortcutRemap(void* config, int originalKey, const wchar_t* targetKeys)
    {
        auto mappingConfig = static_cast<MappingConfiguration*>(config);

        if (!targetKeys)
        {
            return false;
        }

        Shortcut targetShortcut(targetKeys);

        return mappingConfig->AddSingleKeyRemap(static_cast<DWORD>(originalKey), targetShortcut);
    }

    bool AddShortcutRemap(void* config,
                          const wchar_t* originalKeys,
                          const wchar_t* targetKeys,
                          const wchar_t* targetApp,
                          int operationType, 
                          const wchar_t* appPathOrUri,
                          const wchar_t* args,
                          const wchar_t* startDirectory,
                          int elevation,
                          int ifRunningAction,
                          int visibility)
    {
        auto mappingConfig = static_cast<MappingConfiguration*>(config);

        Shortcut originalShortcut(originalKeys);

        KeyShortcutTextUnion targetShortcut;

        switch (operationType)
        {
        case 1:
            targetShortcut = Shortcut(targetKeys);
            std::get<Shortcut>(targetShortcut).runProgramFilePath = std::wstring(appPathOrUri);
            if (args)
            {
                std::get<Shortcut>(targetShortcut).runProgramArgs = std::wstring(args);
            }
            if (startDirectory)
            {
                std::get<Shortcut>(targetShortcut).runProgramStartInDir = std::wstring(startDirectory);
            }
            std::get<Shortcut>(targetShortcut).elevationLevel = static_cast<Shortcut::ElevationLevel>(elevation);
            std::get<Shortcut>(targetShortcut).alreadyRunningAction = static_cast<Shortcut::ProgramAlreadyRunningAction>(ifRunningAction);
            std::get<Shortcut>(targetShortcut).startWindowType = static_cast<Shortcut::StartWindowType>(visibility);
            std::get<Shortcut>(targetShortcut).operationType = static_cast<Shortcut::OperationType>(operationType);
            break;
        case 2:
            targetShortcut = Shortcut(targetKeys);
            std::get<Shortcut>(targetShortcut).uriToOpen = std::wstring(appPathOrUri);
            std::get<Shortcut>(targetShortcut).operationType = static_cast<Shortcut::OperationType>(operationType);
            break;
        case 3:
            targetShortcut = std::wstring(targetKeys);
            break;
        default:
            targetShortcut = Shortcut(targetKeys);
            std::get<Shortcut>(targetShortcut).operationType = static_cast<Shortcut::OperationType>(operationType);
            break;
        }

        std::wstring app(targetApp ? targetApp : L"");

        if (app.empty())
        {
            return mappingConfig->AddOSLevelShortcut(originalShortcut, targetShortcut);
        }
        else
        {
            return mappingConfig->AddAppSpecificShortcut(app, originalShortcut, targetShortcut);
        }
    }

    void GetKeyDisplayName(int keyCode, wchar_t* keyName, int maxCount)
    {
        if (keyName == nullptr || maxCount <= 0)
        {
            return;
        }
        LayoutMap layoutMap;
        std::wstring name = layoutMap.GetKeyName(static_cast<DWORD>(keyCode));
        wcsncpy_s(keyName, maxCount, name.c_str(), _TRUNCATE);
    }

    int GetKeyCodeFromName(const wchar_t* keyName)
    {
        Logger::info(L"Getting key code for key name: {0}", keyName ? keyName : L"null");

        if (keyName == nullptr)
        {
            return 0;
        }

        LayoutMap layoutMap;
        std::wstring name(keyName);
        int keyCode = static_cast<int>(layoutMap.GetKeyFromName(name));
        Logger::info(L"Key code for key name {0}: {1}", keyName, keyCode);
        return keyCode;
    }

    // Function to get the type of a key (Win, Ctrl, Alt, Shift, or Action)
    int GetKeyType(int key)
    {
        return static_cast<int>(Helpers::GetKeyType(static_cast<DWORD>(key)));
    }

    // Function to check if a shortcut is illegal
    bool IsShortcutIllegal(const wchar_t* shortcutKeys)
    {
        Logger::info(L"Checking if shortcut is illegal: {0}", shortcutKeys ? shortcutKeys : L"null");

        if (!shortcutKeys)
        {
            return false;
        }

        Shortcut shortcut(shortcutKeys);

        ShortcutErrorType result = EditorHelpers::IsShortcutIllegal(shortcut);

        // Return true if an error was detected (anything other than NoError)
        return result != ShortcutErrorType::NoError;
    }

    // Function to check if two shortcuts are equal
    bool AreShortcutsEqual(const wchar_t* lShort, const wchar_t* rShort)
    {
        if (!lShort || !rShort)
        {
            return false;
        }

        Shortcut lhs(lShort);
        Shortcut rhs(rShort);

        return lhs == rhs;
    }

    // Function to delete a single key remapping
    bool DeleteSingleKeyRemap(void* config, int originalKey)
    {
        auto mappingConfig = static_cast<MappingConfiguration*>(config);

        // Find and delete the single key remapping
        auto it = mappingConfig->singleKeyReMap.find(static_cast<DWORD>(originalKey));
        if (it != mappingConfig->singleKeyReMap.end())
        {
            mappingConfig->singleKeyReMap.erase(it);
            return true;
        }

        return false;
    }

    bool DeleteSingleKeyToTextRemap(void* config, int originalKey)
    {
        auto mappingConfig = static_cast<MappingConfiguration*>(config);
        auto it = mappingConfig->singleKeyToTextReMap.find(originalKey);
        if (it != mappingConfig->singleKeyToTextReMap.end())
        {
            mappingConfig->singleKeyToTextReMap.erase(it);
            return true;
        }
        return false;
    }

    // Function to delete a shortcut remapping
    bool DeleteShortcutRemap(void* config, const wchar_t* originalKeys, const wchar_t* targetApp)
    {
        auto mappingConfig = static_cast<MappingConfiguration*>(config);

        if (originalKeys == nullptr)
        {
            return false;
        }

        std::wstring appName = targetApp ? targetApp : L"";
        Shortcut shortcut(originalKeys);

        // Determine the type of remapping to delete based on the app name
        if (appName.empty())
        {
            // Delete OS level shortcut mapping
            auto it = mappingConfig->osLevelShortcutReMap.find(shortcut);
            if (it != mappingConfig->osLevelShortcutReMap.end())
            {
                mappingConfig->osLevelShortcutReMap.erase(it);
                return true;
            }
        }
        else
        {
            // Delete app-specific shortcut mapping
            auto appIt = mappingConfig->appSpecificShortcutReMap.find(appName);
            if (appIt != mappingConfig->appSpecificShortcutReMap.end())
            {
                auto shortcutIt = appIt->second.find(shortcut);
                if (shortcutIt != appIt->second.end())
                {
                    appIt->second.erase(shortcutIt);

                    // If the app-specific mapping is empty, remove the app entry
                    if (appIt->second.empty())
                    {
                        mappingConfig->appSpecificShortcutReMap.erase(appIt);
                    }

                    return true;
                }
            }
        }

        return false;
    }
}

// Get the list of keyboard keys in Editor
int GetKeyboardKeysList(bool isShortcut, KeyNamePair* keyList, int maxCount)
{
    if (keyList == nullptr || maxCount <= 0)
    {
        return 0;
    }

    LayoutMap layoutMap;
    auto keyNameList = layoutMap.GetKeyNameList(isShortcut);

    int count = (std::min)(static_cast<int>(keyNameList.size()), maxCount);

    // Transfer the key list to the output struct format
    for (int i = 0; i < count; ++i)
    {
        keyList[i].keyCode = static_cast<int>(keyNameList[i].first);
        wcsncpy_s(keyList[i].keyName, keyNameList[i].second.c_str(), _countof(keyList[i].keyName) - 1);
    }

    return count;
}