#ifndef ConnectionWorker_hpp
#define ConnectionWorker_hpp

#include <JuceHeader.h>
#include "common.h"
#include <MPMCQueue.h>
#include <App.h>
#include <thread>
#include <semaphore>
#include <deque>
#include "StateIntegrity.hpp"

class PluginWindow;
class WavtoolPlayhead;
class WavtoolKeyListener;

class ConnectionWorker final : private juce::AsyncUpdater, public juce::AudioProcessorListener, private juce::Timer
{
public:
    ConnectionWorker(const juce::PluginDescription& desc,
                     int bufferSize,
                     int sampleRate,
                     std::string&& bridgeKeySeed,
                     std::function<void()>&& loadedHandler,
                     std::function<void(const std::string&)>&& errorHandler,
                     std::function<void(const std::string_view&, bool)>&& saveHandler,
                     std::function<void(const ParameterDescription&)>&& parameterGestureHandler,
                     std::function<void(const KeyDescription&)>&& keyHandler,
                     std::function<void()>&& editorClosedHandler,
                     std::function<void(std::pair<int, int>)>&& editorMovedHandler,
                     std::function<void(const std::string_view&)>&& trustHandler,
                     std::function<void(int)>&& latencyChangedHandler,
                     std::function<void(bool)>&& launchingChangedHandler,
                     std::function<void(bool)>&& keyboardEnabledHandler);
    ~ConnectionWorker();
    
    void handleAsyncUpdate() override;
    void handleBlock(ShmData & block);
    void loadState(const std::string_view & data);
    void requestSaveState(bool periodic);
    
    void openPluginWindow(std::optional<std::pair<int,int>> pos);
    void closePluginWindow();
    void setPluginWindowAlwaysOnTop(bool onTop);

    void setKeyboardEnabled(bool isEnabled);
    void keyboardActivity();
    
    void trustResolution(const juce::XmlElement & elem);
    
    virtual void audioProcessorParameterChanged(juce::AudioProcessor *processor, int parameterIndex, float newValue) override;
    virtual void audioProcessorChanged(juce::AudioProcessor *processor, const ChangeDetails &details) override;
    virtual void audioProcessorParameterChangeGestureBegin(juce::AudioProcessor *processor, int parameterIndex) override;
    virtual void audioProcessorParameterChangeGestureEnd(juce::AudioProcessor *processor, int parameterIndex) override;
    virtual void timerCallback() override;

private:
    friend class PluginWindow;
    using clock_t = std::chrono::high_resolution_clock;
    
    void loadPlugin();
    void addPluginCallback(std::unique_ptr<juce::AudioPluginInstance> instance, const juce::String& error);
    void setTransport(ShmSetTransportData & data);
    void processBlock(ShmProcessBlockData & data, bool shouldReset);
    void enqueueThunk(uWS::MoveOnlyFunction<void()> && thunk);
    void stateUpdateCallbackFired();
    void alwaysOnTopEdgeTrigger();
    bool loadStateOnMessageThread(const std::string_view & data);
    bool parametersDidChange();
    void doBusLayoutUpdate();
    
    juce::MidiBuffer getClearMidiBuffer();
    
    const int bufferSize;
    const int sampleRate;
    
    std::function<void()> loadedHandler;
    std::function<void(const std::string &)> errorHandler;
    std::function<void(const std::string_view &, bool)> saveHandler;
    std::function<void(const ParameterDescription &)> parameterGestureHandler;
    std::function<void()> editorClosedHandler;
    std::function<void(std::pair<int,int>)> editorMovedHandler;
    std::function<void(const std::string_view &)> trustHandler;
    std::function<void(int)> latencyChangedHandler;
    std::function<void(bool)> launchingChangedHandler;
    std::function<void(bool)> keyboardEnabledHandler;
    
    std::unique_ptr<juce::AudioPluginInstance> plugin;
    std::unique_ptr<juce::AudioPluginFormatManager> formatManager;
    std::unique_ptr<WavtoolKeyListener> keyListener;
    std::unique_ptr<PluginWindow> pluginWindow;
    std::unique_ptr<juce::PluginDescription> pluginToLoad;
    std::unique_ptr<WavtoolPlayhead> playhead;
    bool isLoadingState;
    
    rigtorp::MPMCQueue<uWS::MoveOnlyFunction<void()>> thunkQ;
    
    std::atomic<clock_t::time_point> lastStateUpdateCallbackTime;
    std::atomic<bool> hasUncommittedStateUpdate;
    std::atomic<int> gesturesActive;
    
    std::string lastSavedState;
    
    std::vector<juce::AudioProcessorParameter*> pluginParameters;
    std::vector<float> pluginParameterValues;
    bool alwaysOnTop;
    
    juce::AudioBuffer<float> audioBuffer;
    
    std::counting_semaphore<100> triggerAsyncUpdateSema;
    std::atomic<bool> triggerAsyncUpdateShouldExit;
    std::unique_ptr<std::thread> triggerAsyncUpdateThread;
    
    StateIntegrity stateIntegrity;
    bool waitingForTrustResolution;
    std::deque<std::string> pendingStateChanges;
    
    std::atomic<bool> sendLatencyChangedNextBlock;
    std::atomic<clock_t::time_point> lastPeriodicSaveTime;
    
    std::atomic<bool> hasWindowPositionChange;
    std::atomic<clock_t::time_point> lastWindowPositionChangeTime;
    std::pair<int, int> currentWindowPosition;

    std::atomic<clock_t::time_point> pluginLoadTime;
    std::atomic<bool> unsetLaunchingArmed;

    bool keyboardEnabled;
    std::atomic<int> previousInputBuses;
    std::atomic<int> previousOutputBuses;
    std::atomic<int> desiredInputBuses;
    std::atomic<int> desiredOutputBuses;
    std::atomic<bool> wantsBusLayoutUpdate;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectionWorker)
};

#endif /* ConnectionWorker_hpp */
