#include <JuceHeader.h>
#include "PluginScannerSubprocess.hpp"
#include "WorkerSubprocess.hpp"
#include "CustomPluginScanner.hpp"
#include "ConnectionWorker.hpp"
#include "SafeWebsocketDeferredSend.h"
#include "common.h"
#include "properties.h"
#include <iostream>
#include <thread>
#include <App.h>
#include "json.h"
#include <chrono>
#include <map>
#include "MacSupport.h"
#include "SocketOwnership.hpp"
#include "StateIntegrity.hpp"
#include "Base64.h"
#include "PluginSearchPathEditor.hpp"
#include "DisableCrashReporter.hpp"
#ifdef _MSC_VER
#include "winsparkle.h"
#include "winsparkle-key.h"
#include "wincred.h"
#include <winuser.h>
#include <TlHelp32.h>
#include <psapi.h>
#endif

extern "C" {
#include "random.h"
}

using namespace juce;
using namespace jsoncons;

#ifdef _MSC_VER
LRESULT CALLBACK KeyboardInputCallbackProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam);
#endif

class WorkerSuperprocess final : private ChildProcessCoordinator
{
public:
    WorkerSuperprocess(int bufferSize,
                       int sampleRate,
                       const std::string & bearerToken,
                       const std::string_view & bridgeKeySeed,
                       const std::string & architecture,
                       std::function<void(int)> && portCallback,
                       std::function<void()> && foregroundChangedCallback,
                       std::function<void(WorkerSuperprocess*)> && disconnectCallback,
                       std::function<void(const std::string_view &)> && trustQueryCallback,
                       std::function<void(bool)> && keyboardEnabledCallback)
    : portCallback(std::move(portCallback)),
      foregroundChangedCallback(std::move(foregroundChangedCallback)),
      disconnectCallback(std::move(disconnectCallback)),
      trustQueryCallback(std::move(trustQueryCallback)),
      keyboardEnabledCallback(std::move(keyboardEnabledCallback)),
      isForeground(false)
    {
        launchWorkerProcess (File::getSpecialLocation (File::currentExecutableFile), workerProcessUID, 8000, 0, architecture);
        
        auto message = std::make_unique<XmlElement>("WorkerSubprocess");
        message->setAttribute("bufferSize", bufferSize);
        message->setAttribute("sampleRate", sampleRate);
        message->setAttribute("bearerToken", bearerToken);
        message->setAttribute("bridgeKeySeed", convertToBase64(bridgeKeySeed));
        message->setAttribute("architecture", architecture);
        
        auto xmlString = message->toString();
        sendMessageToWorker({ xmlString.toRawUTF8(), xmlString.getNumBytesAsUTF8() });
    }
    
    bool getIsForeground() const { return isForeground; }
    
    void sendKeyboardEnabled(bool value) {
        auto message = std::make_unique<XmlElement>("KeyboardEnabled");
        message->setAttribute("value", value);
        auto xmlString = message->toString();
        sendMessageToWorker({ xmlString.toRawUTF8(), xmlString.getNumBytesAsUTF8() });
    }

    void sendKeyboardActivity() {
        auto message = std::make_unique<XmlElement>("KeyboardActivity");
        auto xmlString = message->toString();
        sendMessageToWorker({ xmlString.toRawUTF8(), xmlString.getNumBytesAsUTF8() });
    }

    void sendShouldStayOnTop(bool value) {
        auto message = std::make_unique<XmlElement>("ShouldStayOnTop");
        message->setAttribute("value", value);
        auto xmlString = message->toString();
        sendMessageToWorker({ xmlString.toRawUTF8(), xmlString.getNumBytesAsUTF8() });
    }
    
    void sendTrustResolution(const XmlElement & trustStore) {
        auto message = std::make_unique<XmlElement>("TrustResolution");
        auto trustStoreCopy = std::make_unique<XmlElement>(trustStore);
        message->addChildElement(trustStoreCopy.release());
        auto xmlString = message->toString();
        sendMessageToWorker({ xmlString.toRawUTF8(), xmlString.getNumBytesAsUTF8() });
    }

    using ChildProcessCoordinator::sendMessageToWorker;

private:
    void handleMessageFromWorker (const MemoryBlock& mb) override
    {
        auto elem = parseXML(mb.toString());
        if(!elem) {
            return;
        }
        
        if(elem->getTagName() == "WorkerSubprocessStarted") {
            int port = elem->getIntAttribute("port");
            if(port == 0 || !portCallback) {
                return;
            }
            portCallback(port);
            portCallback = {};
        } else if(elem->getTagName() == "WorkerSubprocessForegroundChanged") {
            isForeground = elem->getBoolAttribute("foreground");
            foregroundChangedCallback();
        } else if(elem->getTagName() == "WorkerSubprocessTrustQuery") {
            trustQueryCallback(convertFromBase64(elem->getStringAttribute("publicKey")));
        }
        else if (elem->getTagName() == "WorkerKeyboardEnabled") {
            keyboardEnabledCallback(elem->getBoolAttribute("value"));
        }
    }

    void handleConnectionLost() override
    {
        disconnectCallback(this);
    }

    std::function<void(int)> portCallback;
    std::function<void()> foregroundChangedCallback;
    std::function<void(WorkerSuperprocess*)> disconnectCallback;
    std::function<void(const std::string_view &)> trustQueryCallback;
    std::function<void(bool)> keyboardEnabledCallback;
    bool isForeground;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WorkerSuperprocess)
};

class PluginScannerThread final : private Thread {
public:
    PluginScannerThread(std::map<std::string, std::unique_ptr<XmlElement>> & currentLists,
                        SearchPathSettings & searchPathSettings,
                        std::list<std::string> architectures,
                        uWS::MoveOnlyFunction<void(const std::string_view &)> && progressCallback,
                        uWS::MoveOnlyFunction<void(std::map<std::string, std::unique_ptr<XmlElement>>&)> && doneCallback,
                        uWS::MoveOnlyFunction<void(const std::string_view &)> && errorCallback)
      : Thread("Plugin scanner thread"),
        currentLists(std::make_move_iterator(currentLists.begin()), std::make_move_iterator(currentLists.end())),
        searchPathSettings(searchPathSettings),
        architectures(std::move(architectures)),
        progressCallback(std::move(progressCallback)),
        doneCallback(std::move(doneCallback)),
        errorCallback(std::move(errorCallback))
    {
        this->startThread();
    }
    
    ~PluginScannerThread() {
        signalThreadShouldExit();
        waitForThreadToExit(-1);
    }
    
private:
    void run() override {
        try {
            realRun();
        } catch(const std::exception & e) {
            errorCallback(e.what());
        } catch(...) {
            errorCallback("");
        }
    }
    
    struct ScanJob final : public ThreadPoolJob
    {
        ScanJob (PluginDirectoryScanner& pds, uWS::MoveOnlyFunction<void(const std::string_view &)> & progressCallback)
          : ThreadPoolJob ("pluginscan"), pds (pds), progressCallback(progressCallback)
        { }

        JobStatus runJob()
        {
            String pluginName;
            while(pds.scanNextFile(true, pluginName) && !shouldExit()) {
                if(pluginName.length() > 0) {
                    progressCallback(pluginName.toStdString());
                }
                pluginName.clear();
            }

            return jobHasFinished;
        }

        PluginDirectoryScanner& pds;
        uWS::MoveOnlyFunction<void(const std::string_view &)> & progressCallback;

        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScanJob)
    };
    
    void realRun() {
        constexpr const int NUM_JOBS = 4;
        
        auto manager = std::make_unique<AudioPluginFormatManager>();
        manager->addDefaultFormats();
        
        std::list<std::unique_ptr<AudioPluginFormat>> formats;
        formats.emplace_back(std::make_unique<VST3PluginFormat>());
        formats.emplace_back(std::make_unique<VSTPluginFormat>());
#ifndef _MSC_VER
        formats.emplace_back(std::make_unique<AudioUnitPluginFormat>());
#endif
        
        std::map<std::string, std::unique_ptr<XmlElement>> res;
        std::list<String> alreadyHaveIdentifiers;
        
        for(auto & architecture : architectures) {
            auto pluginList = std::make_unique<KnownPluginList>();
            {
                auto it = currentLists.find(architecture);
                if(it != currentLists.end()) {
                    pluginList->recreateFromXml(*it->second);
                }
            }
            std::cerr << "scan: Scanning for architecture " << architecture << " with blacklist:\n";
            for(const auto & elem : pluginList->getBlacklistedFiles()) {
                std::cerr << "scan:  " << elem << "\n";
            }
            pluginList->setCustomScanner(std::make_unique<CustomPluginScanner>(architecture, alreadyHaveIdentifiers));
            for(auto & format : formats) {
                FileSearchPath searchPath;
                if(format->getName() == "VST") {
                    if(searchPathSettings.useVstDefaultSearchPaths) {
                        searchPath = format->getDefaultLocationsToSearch();
                    }
                    for(auto & p : searchPathSettings.vstSearchPaths) {
                        searchPath.add(File(p));
                    }
                } else if(format->getName() == "VST3") {
                    if(searchPathSettings.useVst3DefaultSearchPaths) {
                        searchPath = format->getDefaultLocationsToSearch();
                    }
                    for(auto & p : searchPathSettings.vst3SearchPaths) {
                        searchPath.add(File(p));
                    }
                } else {
                    searchPath = format->getDefaultLocationsToSearch();
                }
                searchPath.removeNonExistentPaths();
                searchPath.removeRedundantPaths();
                
                std::cout << "scan: Search path for " << format->getName() << ": " << searchPath.toString() << "\n";
                PluginDirectoryScanner pds(*pluginList, *format, searchPath, true, File(), true);
                {
                    ThreadPool pool{ ThreadPoolOptions{}.withNumberOfThreads (NUM_JOBS) };
                    for(int i = 0; i < NUM_JOBS; i++) {
                        pool.addJob(new ScanJob(pds, progressCallback), true);
                    }
                    while(pool.getNumJobs() > 0 && !threadShouldExit()) {
                        using namespace std::chrono_literals;
                        std::this_thread::sleep_for(50ms);
                    }
                    pool.removeAllJobs(true, -1);
                    if(threadShouldExit()) {
                        throw std::runtime_error("Early thread termination requested");
                    }
                }
            }
            for(auto & desc : pluginList->getTypes()) {
                alreadyHaveIdentifiers.push_back(desc.fileOrIdentifier);
            }
            res.insert(std::make_pair(architecture, pluginList->createXml()));
            
            std::cerr << "scan: Scan finished for architecture " << architecture << " with blacklist:\n";
            for(const auto & elem : pluginList->getBlacklistedFiles()) {
                std::cerr << "scan:  " << elem << "\n";
            }
            std::cerr << "---\n";
        }
        
        doneCallback(res);
    }
    
    std::map<std::string, std::unique_ptr<XmlElement>> currentLists;
    SearchPathSettings searchPathSettings;
    std::list<std::string> architectures;
    uWS::MoveOnlyFunction<void(const std::string_view &)> progressCallback;
    uWS::MoveOnlyFunction<void(std::map<std::string, std::unique_ptr<XmlElement>>&)> doneCallback;
    uWS::MoveOnlyFunction<void(const std::string_view &)> errorCallback;
};

#ifndef _MSC_VER
class BridgePluginTrayIconComponent final : public SystemTrayIconComponent {
public:
    BridgePluginTrayIconComponent(MacSupport * macSupport)
    : macSupport(macSupport)
    {
        auto trayIconImageTemplate = juce::ImageCache::getFromMemory(BinaryData::DockTemplate32_png, BinaryData::DockTemplate32_pngSize);
        setIconImage(trayIconImageTemplate, trayIconImageTemplate);
        setIconTooltip("WavTool Bridge");
    }
    
    void mouseDown(const MouseEvent & e) override {
        setHighlighted(true);
        macSupport->showStatusItemDropdownMenu(getNativeHandle());
    }
private:
    MacSupport * macSupport;
};
#else
class BridgePluginTrayIconComponent final : public SystemTrayIconComponent {
public:
    BridgePluginTrayIconComponent(uWS::MoveOnlyFunction<void()> && clearTrustStore, uWS::MoveOnlyFunction<void()> && checkForUpdates,
        uWS::MoveOnlyFunction<void()> && editSearchPaths)
        : clearTrustStore(std::move(clearTrustStore)),
          checkForUpdates(std::move(checkForUpdates)),
          editSearchPaths(std::move(editSearchPaths))
    {
        auto trayIconImage = juce::ImageCache::getFromMemory(BinaryData::AppIcon256_png, BinaryData::AppIcon256_pngSize);
        auto trayIconImageTemplate = juce::ImageCache::getFromMemory(BinaryData::DockTemplate32_png, BinaryData::DockTemplate32_pngSize);
        setIconImage(trayIconImage, trayIconImageTemplate);
        setIconTooltip("WavTool Bridge");
    }

    void mouseDown(const MouseEvent& e) override {
        PopupMenu m;
        m.addItem(1, "Check for updates...");
        m.addItem(2, "Clear trust store");
        m.addItem(3, "Edit plugin search paths");
        m.addItem(4, "Quit");
        m.showMenuAsync(PopupMenu::Options(),
            [this](int result) {
                if (result == 1) {
                    checkForUpdates();
                } else if (result == 2) {
                    clearTrustStore();
                } else if (result == 3) {
                    editSearchPaths();
                } else if (result == 4) {
                    JUCEApplicationBase::quit();
                }
            });
    }
private:
    uWS::MoveOnlyFunction<void()> clearTrustStore;
    uWS::MoveOnlyFunction<void()> checkForUpdates;
    uWS::MoveOnlyFunction<void()> editSearchPaths;
};

int wsc_can_shutdown();
void wsc_shutdown();
#endif

#ifdef _MSC_VER
class WavtoolWindowsKeyListener
{
public:
    WavtoolWindowsKeyListener(std::function<void(const KeyDescription&)>&& keyHandler)
        : keyHandler(std::move(keyHandler))
    { }

    void processEvent(
        uint8_t sc,
        bool    e0,
        bool    e1,
        bool    keyup,
        uint8_t vk)
    {
        int combinedCode = sc;
        if (e0) {
            combinedCode |= 0xe000;
        }
        if (e1) {
            combinedCode |= 0xe100;
        }

        if (keyup) {
            auto it = heldKeys.find(combinedCode);
            if (it != heldKeys.end()) {
                if (it->second.has_value()) {
                    auto desc = KeyDescription{
                        .keyDown = false,
                        .keyDescription = it->second.value()
                    };
                    keyHandler(desc);
                }
                heldKeys.erase(it);
            }
        }
        else {
            if (heldKeys.find(combinedCode) != heldKeys.end()) {
                return;
            }
            auto maybeDescStr = scanCodeToDescription(sc);
            if (maybeDescStr.has_value()) {
                auto desc = KeyDescription{
                    .keyDown = true,
                    .keyDescription = maybeDescStr.value()
                };
                keyHandler(desc);
            }
            heldKeys.insert(std::make_pair(combinedCode, maybeDescStr));
        }
    }

    void reset() {
        for (auto& p : heldKeys) {
            if (!p.second.has_value()) {
                continue;
            }
            keyHandler(KeyDescription{
                .keyDown = false,
                .keyDescription = p.second.value()
            });
        }
        heldKeys.clear();
    }

private:
    bool isControlHeld() { return isAnyLowByteHeld(0x1d); }
    bool isAltHeld() { return isAnyLowByteHeld(0x38); }
    bool isShiftHeld() { return isAnyLowByteHeld(0x2a) || isAnyLowByteHeld(0x36); }

    bool isAnyLowByteHeld(uint8_t byte) {
        for (auto& p : heldKeys) {
            if ((p.first & 0xFF) == byte) {
                return true;
            }
        }
        return false;
    }

    std::optional<std::string> scanCodeToDescriptionUnmodified(uint8_t sc) {
        if (sc >= 0x2 && sc <= 0xa) {
            return std::to_string(sc - 1);
        }
        switch (sc) {
        case 0x01: return "esc";
        case 0x0b: return "0";
        case 0x0c: case 0x4a: return "-";
        case 0x0d: case 0x4e: return "+";
        case 0x0e: return "backspace";
        case 0x0f: return "tab";
        case 0x10: return "q";
        case 0x11: return "w";
        case 0x12: return "e";
        case 0x13: return "r";
        case 0x14: return "t";
        case 0x15: return "y";
        case 0x16: return "u";
        case 0x17: return "i";
        case 0x18: return "o";
        case 0x19: return "p";
        case 0x1a: return "[";
        case 0x1b: return "]";
        case 0x1c: return "enter";
        case 0x1e: return "a";
        case 0x1f: return "s";
        case 0x20: return "d";
        case 0x21: return "f";
        case 0x22: return "g";
        case 0x23: return "h";
        case 0x24: return "j";
        case 0x25: return "k";
        case 0x26: return "l";
        case 0x27: return ";";
        case 0x28: return "'";
        case 0x29: return "`";
        case 0x2b: return "\\";
        case 0x2c: return "z";
        case 0x2d: return "x";
        case 0x2e: return "c";
        case 0x2f: return "v";
        case 0x30: return "b";
        case 0x31: return "n";
        case 0x32: return "m";
        case 0x33: return ",";
        case 0x34: return ".";
        case 0x35: return "/";
        case 0x39: return "space";
        case 0x47: return "home";
        case 0x48: return "up";
        case 0x4b: return "left";
        case 0x4d: return "right";
        case 0x50: return "down";
        case 0x53: return "delete";
        }
        return {};
    }

    std::optional<std::string> scanCodeToDescription(uint8_t sc) {
        auto unmod = scanCodeToDescriptionUnmodified(sc);
        if (!unmod.has_value()) {
            return {};
        }
        std::string modifiers;
        if (isControlHeld()) {
            modifiers += "ctrl+";
        }
        if (isAltHeld()) {
            modifiers += "alt+";
        }
        if (isShiftHeld()) {
            modifiers += "shift+";
        }
        return modifiers + unmod.value();
    }

    std::map<int, std::optional<std::string>> heldKeys;
    std::function<void(const KeyDescription&)> keyHandler;
};
#endif

class BridgePluginHost final : public JUCEApplication, AsyncUpdater, Timer {
public:
    std::unique_ptr<ApplicationProperties> appProperties;
    
    BridgePluginHost()
    : websocketThread(this),
#ifdef _MSC_VER
      rawKeyboardWnd(NULL),
#endif
      alwaysOnTop(false),
      dAlwaysOnTop(false),
      trustQueryInProgress(false),
      isAnyProcessWindowForeground(false),
      noForegroundPollCount(0),
      dAlwaysOnTopPollCount(0),
      keyboardEnabled(true),
      dIsAnyForeground(false),
      dIsAnyForegroundPollCount(0)
    {
        supportedArchitectures.push_back("native");
#ifndef _MSC_VER
        if(File("/Library/Apple/usr/share/rosetta/rosetta").existsAsFile()) {
            supportedArchitectures.push_back(processIsTranslated() ? "arm64" : "x86_64");
        }
#endif
    }

    bool moreThanOneInstanceAllowed() override {
        auto params = getCommandLineParameters();
        for (auto * uid : { scannerProcessUID, workerProcessUID }) {
            if (params.startsWith(String("--") + uid)) {
                return true;
            }
        }
        return false;
    }
    
    void initialise (const juce::String& commandLine) override
    {
        initializeAppProperties();
#ifndef _MSC_VER
        Process::setDockIconVisible(false);
#endif
        
        auto scannerSubprocess = std::make_unique<PluginScannerSubprocess>();

        if (scannerSubprocess->initialiseFromCommandLine (commandLine, scannerProcessUID))
        {
            subprocess = std::move (scannerSubprocess);
            return;
        }
        
        auto workerSubprocess = std::make_unique<WorkerSubprocess>();

        if (workerSubprocess->initialiseFromCommandLine (commandLine, workerProcessUID, 8000))
        {
            subprocess = std::move (workerSubprocess);
            return;
        }
        
        enqueueThunk([this]{
#ifndef _MSC_VER
            macSupport = std::make_unique<MacSupport>([this]{
                clearTrustStore();
            }, [this]{
                openPluginSearchPathEditor();
            });
            auto bridgeKeySeedb64 = macSupport->generateOrLoadBridgeKeySeed([]{
                return convertToBase64(StateIntegrity::generateSeed());
            });
            bridgeKeySeed = convertFromBase64(bridgeKeySeedb64);
#else
            {
                PCREDENTIALW pcred;
                auto ok = ::CredReadW(L"WavTool/BridgeKeySeed", CRED_TYPE_GENERIC, 0, &pcred);
                if (ok) {
                    bridgeKeySeed = convertFromBase64(std::string((char*)pcred->CredentialBlob, (size_t)pcred->CredentialBlobSize));
                }
                else {
                    bridgeKeySeed = StateIntegrity::generateSeed();
                    auto newSeedb64 = convertToBase64(bridgeKeySeed);

                    CREDENTIALW cred = { 0 };
                    cred.Type = CRED_TYPE_GENERIC;
                    cred.TargetName = (LPWSTR)L"WavTool/BridgeKeySeed";
                    cred.CredentialBlobSize = newSeedb64.size();
                    cred.CredentialBlob = (LPBYTE)newSeedb64.data();
                    cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
                    ok = ::CredWriteW(&cred, 0);
                    if (!ok) {
                        throw std::runtime_error("could not read or write credential");
                    }
                }
            }
#endif
            stateIntegrity = std::make_unique<StateIntegrity>(bridgeKeySeed);
            auto trustStore = appProperties->getUserSettings()->getXmlValue("trustStore");
            if(trustStore) {
                try {
                    stateIntegrity->loadTrustStore(*trustStore);
                } catch(const std::exception & e) {
                    std::cerr << "Failed to load trust store from app settings: " << e.what() << "\n";
                }
            }
            
            auto searchPathSettingsXml = appProperties->getUserSettings()->getXmlValue("searchPaths");
            if(searchPathSettingsXml) {
                searchPathSettings.fromXml(searchPathSettingsXml.get());
            }
            
#ifndef _MSC_VER
            trayIconComponent = std::make_unique<BridgePluginTrayIconComponent>(macSupport.get());
#else
            trayIconComponent = std::make_unique<BridgePluginTrayIconComponent>([this] {                     clearTrustStore();
                }, [this] {
                    win_sparkle_check_update_with_ui();
                }, [this] {
                    openPluginSearchPathEditor();
                });
           
            win_sparkle_init();
            win_sparkle_set_appcast_url("https://wavtool.com/bridge/win64/appcast.xml");
            win_sparkle_set_dsa_pub_pem(WIN_SPARKLE_KEY);
            win_sparkle_set_can_shutdown_callback(wsc_can_shutdown);
            win_sparkle_set_shutdown_request_callback(wsc_shutdown);
#endif

            loadPluginLists(pluginListByArchitecture);
            websocketThread.startThread();
#ifdef _MSC_VER
            attachKeyboardListener();
            keyListener = std::make_unique<WavtoolWindowsKeyListener>([this](const KeyDescription& k) {
                {
                    std::unique_lock lock(closeThunksMutex);
                    for (auto& p : keyListenerSockets) {
                        p.second->send(json_to_string(k), uWS::OpCode::TEXT);
                    }
                }
                for (auto& sp : workerSuperprocs) {
                    sp->sendKeyboardActivity();
                }
            });
#endif
            startTimer(50);
        });
    }
    
    void initializeAppProperties()
    {
        PropertiesFile::Options options;
        options.applicationName     = "WavTool Bridge";
        options.filenameSuffix      = "settings";
        options.osxLibrarySubFolder = "Preferences";

        appProperties = std::make_unique<ApplicationProperties>();
        appProperties->setStorageParameters (options);
    }

    void clearTrustStore() {
        if (!stateIntegrity) {
            return;
        }
        stateIntegrity->clearTrustStore();
        auto trustStore = stateIntegrity->saveTrustStore();
        appProperties->getUserSettings()->setValue("trustStore", trustStore.get());
        appProperties->getUserSettings()->saveIfNeeded();
        for (auto& superproc : workerSuperprocs) {
            superproc->sendTrustResolution(*trustStore);
        }
    }
    
    ~BridgePluginHost() {
        for(int i = 0; i < bridgeKeySeed.size(); i++) {
            bridgeKeySeed[i] = 0;
        }
        trayIconComponent.reset();
#ifdef _MSC_VER
        win_sparkle_cleanup();
#endif
    }
    
    void handleAsyncUpdate() override
    {
        for(;;) {
            uWS::MoveOnlyFunction<void()> thunk;
            {
                std::unique_lock lock(pendingThunksMutex);
                if(pendingThunks.empty()) {
                    break;
                }
                thunk = std::move(pendingThunks.front());
                pendingThunks.pop_front();
            }
            thunk();
        }
    }

    bool pollProcessWindowsForeground()
    {
#ifndef _MSC_VER
        return false;
#else
        HWND foregroundWindow = GetForegroundWindow();

        if (foregroundWindow == NULL || !IsWindowVisible(foregroundWindow)) {
            return false;
        }

        TCHAR currentProcessPath[MAX_PATH];
        GetModuleFileName(NULL, currentProcessPath, sizeof(currentProcessPath));

        DWORD foregroundProcessId;
        if (GetWindowThreadProcessId(foregroundWindow, &foregroundProcessId) == 0) {
            return false;
        }

        HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, foregroundProcessId);
        if (hProcess != NULL) {
            TCHAR foregroundProcessPath[MAX_PATH];
            if (GetModuleFileNameEx(hProcess, NULL, foregroundProcessPath, sizeof(foregroundProcessPath))) {
                if (strcmp(foregroundProcessPath, currentProcessPath) == 0) {
                    CloseHandle(hProcess);
                    return true;
                }
            }
            CloseHandle(hProcess);
        }
        return false;
#endif
    }

    void appDidEnterBackground() {
#ifdef _MSC_VER
        if (keyListener) {
            keyListener->reset();
        }
#endif
    }

    template<typename T>
    bool fallingEdgeDelay(T & current, bool next, int & poll, int threshold)
    {
        if (current && !next && poll++ >= threshold) {
            current = false;
            return true;
        }
        else if (next) {
            poll = 0;
            if (!current) {
                current = true;
                return true;
            }
        }
        return false;
    }
    
    void timerCallback() override
    {
        bool shouldUpdate = false;
        
        if(fallingEdgeDelay(isAnyProcessWindowForeground, pollProcessWindowsForeground(), noForegroundPollCount, 4)) {
            shouldUpdate = true;
            appDidEnterBackground();
        }
        
        if(fallingEdgeDelay(dAlwaysOnTop, alwaysOnTop, dAlwaysOnTopPollCount, 6)) {
            shouldUpdate = true;
        }
        
        bool isAnyForeground = false;
        for(auto & superproc : workerSuperprocs) {
            if(superproc->getIsForeground()) {
                isAnyForeground = true;
                break;
            }
        }
        
        if(fallingEdgeDelay(dIsAnyForeground, isAnyForeground, dIsAnyForegroundPollCount, 4)) {
            shouldUpdate = true;
        }
        
        if (shouldUpdate) {
            updateProcessGroupForeground();
        }
    }
    
    void updateProcessGroupForeground() {
        std::cout << "updateProcessGroupForeground: dAlwaysOnTop = " << dAlwaysOnTop << ", dIsAnyForeground = " << dIsAnyForeground << ", IsAnyProcessWindowForeground = " << isAnyProcessWindowForeground << ", numSuperprocs = " << workerSuperprocs.size() << "\n";
        
        for(auto & superproc : workerSuperprocs) {
            superproc->sendShouldStayOnTop(dIsAnyForeground || isAnyProcessWindowForeground || dAlwaysOnTop);
        }
    }
    
    std::string generateBearerToken() {
        constexpr const char HEX_TABLE[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        constexpr const size_t TOKEN_BYTES = 32;
        unsigned char buf[TOKEN_BYTES];
        if(random_bytes(buf, TOKEN_BYTES) != 0) {
            throw std::runtime_error("Failed to generate bearer token");
        }
        std::string hexbuf(2 * TOKEN_BYTES, ' ');
        for(size_t i = 0; i < TOKEN_BYTES; i++) {
            hexbuf[2*i] = HEX_TABLE[buf[i] / 16];
            hexbuf[2*i+1] = HEX_TABLE[buf[i] % 16];
        }
        return hexbuf;
    }
    
    struct trustQueryInProgressRaii {
        trustQueryInProgressRaii(bool & valRef) : valRef(valRef) { valRef = true; }
        ~trustQueryInProgressRaii() { valRef = false; }
    private:
        bool & valRef;
    };
    
    void trustQuery(const std::string_view & publicKey) {
        enqueueThunk([this,publicKey=std::string(publicKey)]() mutable {
            if (trustQueryInProgress) {
                pendingTrustQueries.emplace_back(std::move(publicKey));
                return;
            }
            trustQueryOnce(std::move(publicKey));
        });
    }
    
    void trustQueryOnce(std::string && publicKey) {
        trustQueryInProgress = true;
        auto processNext = [this]() {
            enqueueThunk([this] {
                auto trustStore = stateIntegrity->saveTrustStore();

                for (auto& superproc : workerSuperprocs) {
                    superproc->sendTrustResolution(*trustStore);
                }

                if (pendingTrustQueries.empty()) {
                    trustQueryInProgress = false;
                }
                else {
                    auto nextPublicKey = std::move(pendingTrustQueries.front());
                    pendingTrustQueries.pop_front();
                    trustQueryOnce(std::move(nextPublicKey));
                }
            });
        };
        if (stateIntegrity->getKeyTrust(publicKey).has_value()) {
            processNext();
        } else {
            // first time this key has been seen - ask user
            auto continuation = [this, publicKey = std::move(publicKey), processNext=std::move(processNext)](int shouldTrust) mutable {
                enqueueThunk([this, publicKey = std::move(publicKey), processNext=std::move(processNext), shouldTrust] {
                    std::cerr << "User set trust value of public key " << convertToBase64(publicKey) << " to " << shouldTrust << "\n";
                    stateIntegrity->updateKeyTrust(publicKey, shouldTrust);
                    auto trustStore = stateIntegrity->saveTrustStore();
                    appProperties->getUserSettings()->setValue("trustStore", trustStore.get());
                    appProperties->getUserSettings()->saveIfNeeded();
                    processNext();
                });
            };
#ifndef _MSC_VER
            continuation(macSupport->showTrustQuery());
#else
            NativeMessageBox::showYesNoBox(
                MessageBoxIconType::WarningIcon,
                "WavTool Bridge",
                "WavTool is attempting to load VST settings from an unrecognized source.\n\n"
                "Loading untrusted VST settings may lead to compromise of your device. "
                "Be sure you trust the author of the project before continuing.\n\n"
                "Load untrusted settings?",
                nullptr,
                ModalCallbackFunction::create(std::move(continuation)));
#endif
            
        }
        
        
    }
    
    struct ConnectionUserData {
        std::unique_ptr<SafeWebsocketDeferredSend> safeSend;
        std::unique_ptr<PluginScannerThread> pluginScannerThread;
        std::string peerAddress;
        int peerPort;
    };
    
    template<typename WebSocket>
    void handleControlMessage(const std::string_view & message, WebSocket * ws) {
        auto j = decode_json<ClientControlMessage>(message);
        auto userData = ws->getUserData();
        auto safeSend = userData->safeSend->get();
        
        switch(j.op) {
            case ClientOp::listPlugins:
            case ClientOp::scanPlugins:
            {
                enqueueThunk([=,this]{
                    if(j.op == ClientOp::scanPlugins) {
                        clearSavedPluginLists();
                    }
                    loadPluginLists(pluginListByArchitecture);
                    std::map<std::string, std::unique_ptr<XmlElement>> spl;
                    for(auto & p : pluginListByArchitecture) {
                        spl.insert(std::make_pair(p.first, p.second->createXml()));
                    }
                    userData->pluginScannerThread = std::make_unique<PluginScannerThread>(spl,
                                                                                          searchPathSettings,
                                                                                          supportedArchitectures,
                    [=,this](const std::string_view & currentlyScanning) {
                        safeSend->send(json_to_string(ProgressResponse{.progress = std::string(currentlyScanning)}), uWS::OpCode::TEXT);
                    }, [=,this](std::map<std::string, std::unique_ptr<XmlElement>> & newLists) mutable {
                        enqueueThunk([=,this,newLists=std::remove_reference<decltype(newLists)>::type(std::make_move_iterator(newLists.begin()), std::make_move_iterator(newLists.end()))] {
                            pluginListByArchitecture.clear();
                            for(auto & elem : newLists) {
                                auto newList = std::make_unique<KnownPluginList>();
                                newList->recreateFromXml(*elem.second);
                                pluginListByArchitecture.insert(std::make_pair(elem.first, std::move(newList)));
                            }
                            savePluginLists(pluginListByArchitecture);
                            safeSend->send(json_to_string(flattenPluginLists(pluginListByArchitecture)), uWS::OpCode::TEXT);
                        });
                    }, [=,this](const std::string_view & errorView) {
                        std::string error(errorView);
                        enqueueThunk([=,this,error=std::move(error)]{
                            safeSend->send(json_to_string(ErrorResponse{.error = error}), uWS::OpCode::TEXT);
                        });
                    });
                });
                
                break;
            }
            case ClientOp::resolvePlugin:
            {
                auto j2 = decode_json<ClientResolvePluginMessage>(message);
                ClientResolvePluginResponse exactMatch;
                exactMatch.found = false;
                ClientResolvePluginResponse nameManufacturerMatch;
                nameManufacturerMatch.found = false;
                
                for(auto & p : pluginListByArchitecture) {
                    const auto & [architecture, pluginList] = p;
                    for(auto & t : pluginList->getTypes()) {
                        if(t.fileOrIdentifier.toStdString() == j2.identifier) {
                            if(!exactMatch.found || architecture == "native") { // prefer native
                                exactMatch.identifier = t.fileOrIdentifier.toStdString();
                                exactMatch.architecture = architecture;
                                exactMatch.found = true;
                            }
                        }
                        if(t.name.toStdString() == j2.name && t.manufacturerName.toStdString() == j2.manufacturer) {
                            if(!nameManufacturerMatch.found || architecture == "native") {
                                nameManufacturerMatch.identifier = t.fileOrIdentifier.toStdString();
                                nameManufacturerMatch.architecture = architecture;
                                nameManufacturerMatch.found = true;
                            }
                        }
                    }
                }
                if(exactMatch.found) {
                    ws->send(json_to_string(exactMatch), uWS::OpCode::TEXT);
                } else if(nameManufacturerMatch.found) {
                    ws->send(json_to_string(nameManufacturerMatch), uWS::OpCode::TEXT);
                } else {
                    ws->send(json_to_string(ErrorResponse{.error = "Plugin could not be resolved"}), uWS::OpCode::TEXT);
                }
                break;
            }
            case ClientOp::startWorker:
            {
                auto j2 = decode_json<ClientStartWorkerMessage>(message);
                auto bearerToken = generateBearerToken();
                auto superproc = std::make_unique<WorkerSuperprocess>(j2.bufferSize, j2.sampleRate, bearerToken, bridgeKeySeed, j2.architecture, [=](int port) {
                    safeSend->send(json_to_string(WorkerStartedResponse{.port=port, .bearerToken=bearerToken}), uWS::OpCode::TEXT);
                }, [this]() {
                    enqueueThunk([=]{
                        updateProcessGroupForeground();
                    });
                }, [this](WorkerSuperprocess * which) {
                    enqueueThunk([=]{
                        for(auto it = workerSuperprocs.begin(); it != workerSuperprocs.end(); it++) {
                            if(it->get() == which) {
                                workerSuperprocs.erase(it);
                                break;
                            }
                        }
                        updateProcessGroupForeground();
                    });
                }, [this](const std::string_view & publicKey) {
                    trustQuery(publicKey);
                }, [this](bool newKeyboardEnabled) {
                    enqueueThunk([this,newKeyboardEnabled] {
                        setKeyboardEnabled(newKeyboardEnabled);
                    });
                });
                enqueueThunk([this,superproc=std::move(superproc)]() mutable {
                    workerSuperprocs.emplace_back(std::move(superproc));
                    updateProcessGroupForeground();
                    sendKeyboardEnabled();
                });
                break;
            }
            case ClientOp::setAlwaysOnTop:
            {
                auto j2 = decode_json<AlwaysOnTopMessage>(message);
                alwaysOnTop = j2.alwaysOnTop;
                updateProcessGroupForeground();
                break;
            }
            default:
                ws->send(json_to_string(ErrorResponse{.error = "Operation must be handled by a worker"}), uWS::OpCode::TEXT);
                break;
        }
    }
    
    class WebsocketThread : private Thread {
    public:
        WebsocketThread(BridgePluginHost * host) : Thread("Websocket thread"), host(host) { }
        BridgePluginHost * host;
        
        std::function<void()> closeAll;
        
        void run() override {
            auto app = uWS::App().ws<ConnectionUserData>("/*", {
                /* Settings */
                .maxPayloadLength = 100 * 1024 * 1024,
                .idleTimeout = 16,
                .maxBackpressure = 100 * 1024 * 1024,
                .closeOnBackpressureLimit = false,
                .resetIdleTimeoutOnSend = false,
                .sendPingsAutomatically = true,
                /* Handlers */
                .upgrade = [this](auto *res, auto *req, auto *context) {
                    if(!validateOriginHeader(req)) {
                        res->close();
                        return;
                    }
                    
                    res->template upgrade<ConnectionUserData>({},
                        req->getHeader("sec-websocket-key"),
                        req->getHeader("sec-websocket-protocol"),
                        req->getHeader("sec-websocket-extensions"),
                        context);
                },
                .open = [this](auto * ws) {
                    auto ctx = ws->getUserData();

                    auto [peerAddress, peerPort] = getPeerAddressAndPort(ws);
                    ctx->peerAddress = peerAddress;
                    ctx->peerPort = peerPort;
                    
                    if(peerAddress != "127.0.0.1") {
                        ws->end();
                        return;
                    }

#ifdef _MSC_VER
                    auto maybeMySsid = getStringSidForProcess(GetCurrentProcess());
                    auto maybeSsid = findLocalhostRemoteSocketOwner(peerPort);
                    if(!maybeMySsid.has_value() || !maybeSsid.has_value() || maybeMySsid.value() != maybeSsid.value()) {
                        ws->end();
                        return;
                    }
#else
                    auto uid = findLocalhostRemoteSocketOwner(peerPort);
                    if(!uid.has_value() || uid.value() != getuid()) {
                        ws->end();
                        return;
                    }
#endif
                    
                    ctx->safeSend.reset(new SafeWebsocketDeferredSendT(ws, uWS::Loop::get()));
                    
                    std::unique_lock lock(host->closeThunksMutex);
                    host->closeThunks.emplace(std::make_pair((uintptr_t)ws, [=]{
                        ws->end();
                    }));
                    host->keyListenerSockets.emplace(std::make_pair((uintptr_t)ws, ctx->safeSend->get()));
                    
                    std::cerr << "New connection from " << peerAddress << ":" << peerPort << "\n";
                    
                    ws->send("🫡 hello fellow nerds (protocol 2)", uWS::OpCode::TEXT);
                },
                .message = [this](auto *ws, std::string_view message, uWS::OpCode opCode) {
                    if(opCode == uWS::OpCode::TEXT) {
                        // control message
                        try {
                            host->handleControlMessage(message, ws);
                        } catch(std::exception & e) {
                            ws->send(json_to_string(ErrorResponse{.error = e.what()}), uWS::OpCode::TEXT);
                        }
                    } else if(opCode == uWS::OpCode::BINARY) {
                        ws->send(json_to_string(ErrorResponse{.error = "Binary frames can only be handled by a worker"}), uWS::OpCode::TEXT);
                    }
                },
                .dropped = [](auto */*ws*/, std::string_view /*message*/, uWS::OpCode /*opCode*/) {
                    /* A message was dropped due to set maxBackpressure and closeOnBackpressureLimit limit */
                },
                .drain = [](auto */*ws*/) {
                    /* Check ws->getBufferedAmount() here */
                },
                .ping = [](auto */*ws*/, std::string_view) {
                    /* Not implemented yet */
                },
                .pong = [](auto */*ws*/, std::string_view) {
                    /* Not implemented yet */
                },
                .close = [this](auto * ws, int /*code*/, std::string_view /*message*/) {
                    std::unique_lock lock(host->closeThunksMutex);
                    auto it = host->closeThunks.find((uintptr_t)ws);
                    if(it != host->closeThunks.end()) {
                        host->closeThunks.erase(it);
                    }
                    auto it2 = host->keyListenerSockets.find((uintptr_t)ws);
                    if (it2 != host->keyListenerSockets.end()) {
                        host->keyListenerSockets.erase(it2);
                    }
                    auto ctx = ws->getUserData();
                    std::cerr << "Connection closed: " << ctx->peerAddress << ":" << ctx->peerPort << "\n";
                }
            });
            
            app.any("/*", [](auto *res, auto *req) {
                res->close();
            });
            
            bool listening = false;
            for(int p = 9002; p < 9007 && !listening; p++) {
                app.listen("127.0.0.1", p, LIBUS_LISTEN_EXCLUSIVE_PORT, [&,this](auto *listen_socket) {
                    if (listen_socket) {
                        std::cerr << "listening on " << p << "\n";
                        listening = true;
                        auto eventLoop = uWS::Loop::get();
                        closeAll = [=,this]{
                            eventLoop->defer([=,this]{
                                us_listen_socket_close(0, listen_socket);
                                std::list<std::function<void()>> closeThunks;
                                {
                                    std::unique_lock lock(host->closeThunksMutex);
                                    for(auto & pair : host->closeThunks) {
                                        closeThunks.emplace_back(std::move(pair.second));
                                    }
                                }
                                for(auto & func : closeThunks) {
                                    func();
                                }
                            });
                        };
                    }
                });
            }
            
            if(!listening) {
                throw std::runtime_error("Failed to listen on any port in the range");
            }
            
            app.run();
        }
        friend class BridgePluginHost;
    };
    friend class WebsocketThread;
    
    // we borrow this from uWS because JUCE doesn't support c++23 yet
    void enqueueThunk(uWS::MoveOnlyFunction<void()> && thunk) {
        {
            std::unique_lock lock(pendingThunksMutex);
            pendingThunks.emplace_back(std::move(thunk));
        }
        triggerAsyncUpdate();
    }
    
    void shutdown() override
    {
#ifdef _MSC_VER
        detachKeyboardListener();
#endif
        if(websocketThread.isThreadRunning()) {
            websocketThread.closeAll();
            websocketThread.waitForThreadToExit(5000);
        }
        if(websocketThread.isThreadRunning()) {
            // ugh...
            exitImmediately();
        }
    }
    
    const juce::String getApplicationName() override
    {
        return "WavTool Bridge";
    }
    
    const juce::String getApplicationVersion() override
    {
#define WAVTOOL_STRINGIFY(x) #x
#define WAVTOOL_TOSTRING(x) WAVTOOL_STRINGIFY(x)
        return WAVTOOL_TOSTRING(JUCE_APP_VERSION);
#undef WAVTOOL_TOSTRING
#undef WAVTOOL_STRINGIFY
    }
    
    void unhandledException(const std::exception * e, const String & file, int line) override
    {
        if(e) {
            std::cerr << "unhandled exception: " << e->what() << " raised at " << file << ":" << line << "\n";
        } else {
            std::cerr << "unhandled exception raised at " << file << ":" << line << "\n";
        }
        std::terminate();
    }

    void setKeyboardEnabled(bool isEnabled) {
        std::cerr << "setKeyboardEnabled: " << isEnabled << "\n";
        if (keyboardEnabled != isEnabled) {
            keyboardEnabled = isEnabled;
            sendKeyboardEnabled();
        }
#ifdef _MSC_VER
        if (!keyboardEnabled && keyListener) {
            keyListener->reset();
        }
#endif
    }

    void sendKeyboardEnabled() {
        for (auto& superproc : workerSuperprocs) {
            superproc->sendKeyboardEnabled(keyboardEnabled);
        }
    }

#ifdef _MSC_VER
    void processKeyboardEvent(
        uint8_t sc,
        bool    e0,
        bool    e1,
        bool    keyup,
        uint8_t vk
    ) {
        if (!isAnyProcessWindowForeground || !keyboardEnabled) {
            return;
        }
        if (keyListener) {
            keyListener->processEvent(sc, e0, e1, keyup, vk);
        }
    }

    void attachKeyboardListener()
    {
        //define a window class which is required to receive RAWINPUT events
        WNDCLASSEX wc;
        ZeroMemory(&wc, sizeof(WNDCLASSEX));
        wc.cbSize = sizeof(WNDCLASSEX);
        wc.lpfnWndProc = KeyboardInputCallbackProc;
        wc.hInstance = GetModuleHandle(NULL);
        wc.lpszClassName = "wavtool_wndclass";

        // register class
        if (!RegisterClassExA(&wc))
            return;

        // create window
        rawKeyboardWnd = CreateWindowExA(0, wc.lpszClassName, NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, GetModuleHandle(NULL), NULL);
        if (!rawKeyboardWnd)
            return;

        // setup raw input device sink
        RAWINPUTDEVICE devs = { 0x01 /* generic */, 0x06 /* keyboard */, RIDEV_INPUTSINK, rawKeyboardWnd };
        if (RegisterRawInputDevices(&devs, 1, sizeof(RAWINPUTDEVICE)) == FALSE)
            return;
    }

    void detachKeyboardListener()
    {
        if (rawKeyboardWnd) {
            DestroyWindow(rawKeyboardWnd);
        }
        UnregisterClass("wavtool_wndclass", GetModuleHandle(NULL));
    }
#endif
    
    void openPluginSearchPathEditor()
    {
        if(!pluginSearchPathEditor) {
            pluginSearchPathEditor = std::make_unique<PluginSearchPathEditor>(searchPathSettings,
            [this]{
                pluginSearchPathEditor.reset();
            }, [this] {
                auto searchPathSettingsXml = searchPathSettings.toXml();
                appProperties->getUserSettings()->setValue("searchPaths", searchPathSettingsXml.get());
            });
        }
        pluginSearchPathEditor->toFront(true);
    }
private:
#ifndef _MSC_VER
    std::unique_ptr<MacSupport> macSupport;
#endif
    std::unique_ptr<ChildProcessWorker> subprocess;
    std::map<std::string, std::unique_ptr<KnownPluginList>> pluginListByArchitecture;
    WebsocketThread websocketThread;
    
#ifdef _MSC_VER
    HWND rawKeyboardWnd;
    std::unique_ptr<WavtoolWindowsKeyListener> keyListener;
#endif
    
    std::list<uWS::MoveOnlyFunction<void()>> pendingThunks;
    std::mutex pendingThunksMutex;
    
    std::list<std::unique_ptr<WorkerSuperprocess>> workerSuperprocs;
    
    std::mutex closeThunksMutex;
    std::map<uintptr_t, std::function<void()>> closeThunks;
    std::map < uintptr_t, std::shared_ptr<SafeWebsocketDeferredSend::impl>> keyListenerSockets;
    
    std::unique_ptr<BridgePluginTrayIconComponent> trayIconComponent;
    std::atomic<bool> alwaysOnTop;
    std::atomic<bool> dAlwaysOnTop;
    
    std::string bridgeKeySeed;
    std::unique_ptr<StateIntegrity> stateIntegrity;
    
    bool trustQueryInProgress;
    std::deque<std::string> pendingTrustQueries;
    
    std::list<std::string> supportedArchitectures;

    bool isAnyProcessWindowForeground;
    int noForegroundPollCount;
    int dAlwaysOnTopPollCount;
    bool keyboardEnabled;
    
    std::atomic<bool> dIsAnyForeground;
    int dIsAnyForegroundPollCount;
    
    SearchPathSettings searchPathSettings;
    std::unique_ptr<PluginSearchPathEditor> pluginSearchPathEditor;
};

ApplicationProperties& getAppProperties() {
    return *(dynamic_cast<BridgePluginHost*>(JUCEApplication::getInstance())->appProperties);
}

#ifdef _MSC_VER
int wsc_can_shutdown() {
    return 1;
}
void wsc_shutdown() {
    JUCEApplicationBase::quit();
}

LRESULT CALLBACK KeyboardInputCallbackProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam)
{
    if (message != WM_INPUT)
        return DefWindowProc(window, message, wparam, lparam);

    char rid_buf[64];
    UINT rid_size = sizeof(rid_buf);

    if (GetRawInputData((HRAWINPUT)lparam, RID_INPUT, rid_buf, &rid_size, sizeof(RAWINPUTHEADER)))
    {
        RAWINPUT* raw = (RAWINPUT*)rid_buf;
        if (raw->header.dwType == RIM_TYPEKEYBOARD)
        {
            RAWKEYBOARD* rk = &raw->data.keyboard;
            dynamic_cast<BridgePluginHost*>(JUCEApplication::getInstance())->processKeyboardEvent(
                rk->MakeCode,
                rk->Flags & RI_KEY_E0,
                rk->Flags & RI_KEY_E1,
                rk->Flags & RI_KEY_BREAK,
                rk->VKey
            );
        }
    }
    return DefWindowProc(window, message, wparam, lparam);
}
#endif

START_JUCE_APPLICATION (BridgePluginHost)

#if defined (_MSC_VER) && ! defined (NDEBUG)
int main() {
    return WinMain(0, 0, 0, 0);
}
#endif
