#ifndef wavtool_json_h
#define wavtool_json_h

#include <JuceHeader.h>
#include <jsoncons/json.hpp>
#include "common.h"

namespace jsoncons {

template <class Json>
struct json_type_traits<Json, juce::String>
{
    static bool is(const Json& val) noexcept
    {
        return val.is_string();
    }
    
    static juce::String as(const Json& val)
    {
        return juce::String(val.template as<std::string>());
    }
    
    static Json to_json(const juce::String & val,
                        typename Json::allocator_type alloc = typename Json::allocator_type())
    {
        return Json(val.toStdString(), alloc);
    }
};

};

enum class ClientOp { scanPlugins, listPlugins, startWorker, instantiatePlugin, listInstanceParameters, setTransportState, loadState, saveState, openEditor, closeEditor, setAlwaysOnTop, resolvePlugin, resetPlugin };
JSONCONS_ENUM_TRAITS(ClientOp, scanPlugins, listPlugins, startWorker, instantiatePlugin, listInstanceParameters, setTransportState, loadState, saveState, openEditor, closeEditor, setAlwaysOnTop, resolvePlugin, resetPlugin );

struct ClientControlMessage {
    ClientOp op;
};
JSONCONS_ALL_MEMBER_TRAITS(ClientControlMessage, op);

struct ClientInstantiatePluginMessage {
    juce::String pluginIdentifier;
};
JSONCONS_ALL_MEMBER_TRAITS(ClientInstantiatePluginMessage, pluginIdentifier);

struct ClientResolvePluginMessage {
    std::string identifier;
    std::string name;
    std::string manufacturer;
};
JSONCONS_ALL_MEMBER_TRAITS(ClientResolvePluginMessage, identifier, name, manufacturer);

struct ClientResolvePluginResponse {
    bool found;
    std::optional<std::string> identifier;
    std::optional<std::string> architecture;
};
JSONCONS_ALL_MEMBER_TRAITS(ClientResolvePluginResponse, found, identifier, architecture);

struct ClientStartWorkerMessage {
    int bufferSize;
    int sampleRate;
    std::string architecture;
};
JSONCONS_ALL_MEMBER_TRAITS(ClientStartWorkerMessage, bufferSize, sampleRate, architecture);

struct ClientOpenEditorMessage {
    std::optional<int> x;
    std::optional<int> y;
};
JSONCONS_ALL_MEMBER_TRAITS(ClientOpenEditorMessage, x, y);

struct EncodedStateMessage {
    std::string encodedState;
    bool periodic;
};
JSONCONS_N_MEMBER_TRAITS(EncodedStateMessage, 1, encodedState, periodic);

struct AlwaysOnTopMessage {
    bool alwaysOnTop;
};
JSONCONS_ALL_MEMBER_TRAITS(AlwaysOnTopMessage, alwaysOnTop);

struct ErrorResponse {
    std::string error;
};
JSONCONS_ALL_MEMBER_TRAITS(ErrorResponse, error);

struct EventResponse {
    std::string event;
};
JSONCONS_ALL_MEMBER_TRAITS(EventResponse, event);

struct WorkerStartedResponse {
    int port;
    std::string bearerToken;
};
JSONCONS_ALL_MEMBER_TRAITS(WorkerStartedResponse, port, bearerToken);

struct EditorClosedResponse {
    EditorClosedResponse() : closed(true) { };
    bool closed;
};
JSONCONS_ALL_MEMBER_TRAITS(EditorClosedResponse, closed);

struct EditorMovedResponse {
    EditorMovedResponse() { };
    EditorMovedResponse(int x, int y) : moved(true), x(x), y(y) { };
    bool moved;
    int x;
    int y;
};
JSONCONS_ALL_MEMBER_TRAITS(EditorMovedResponse, moved, x, y);

struct ProgressResponse {
    std::string progress;
};
JSONCONS_ALL_MEMBER_TRAITS(ProgressResponse, progress);

struct PluginLatencyResponse {
    int samplesLatency;
};
JSONCONS_ALL_MEMBER_TRAITS(PluginLatencyResponse, samplesLatency);

struct PluginDescriptionWithArchitecture {
    juce::String name;
    juce::String descriptiveName;
    juce::String pluginFormatName;
    juce::String manufacturerName;
    juce::String version;
    juce::String fileOrIdentifier;
    bool isInstrument;
    int numInputChannels;
    int numOutputChannels;
    juce::String architecture;
};

JSONCONS_ALL_MEMBER_TRAITS(PluginDescriptionWithArchitecture,
                           name,
                           descriptiveName,
                           pluginFormatName,
                           manufacturerName,
                           version,
                           fileOrIdentifier,
                           isInstrument,
                           numInputChannels,
                           numOutputChannels,
                           architecture);

struct PluginListResponse {
    std::list<PluginDescriptionWithArchitecture> plugins;
};
JSONCONS_ALL_MEMBER_TRAITS(PluginListResponse, plugins);

inline PluginListResponse flattenPluginLists(const std::map<std::string, std::unique_ptr<juce::KnownPluginList>> & lists) {
    std::map<std::string, PluginDescriptionWithArchitecture> resolved;
    for(auto & list : lists) {
        for(auto & desc : list.second->getTypes()) {
            auto pd = PluginDescriptionWithArchitecture{
                .name = desc.name,
                .descriptiveName = desc.descriptiveName,
                .pluginFormatName = desc.pluginFormatName,
                .manufacturerName = desc.manufacturerName,
                .version = desc.version,
                .fileOrIdentifier = desc.fileOrIdentifier,
                .isInstrument = desc.isInstrument,
                .numInputChannels = desc.numInputChannels,
                .numOutputChannels = desc.numOutputChannels,
                .architecture = list.first
            };
            
            auto it = resolved.find(desc.fileOrIdentifier.toStdString());
            if(it != resolved.end() && it->second.architecture == "native") {
                continue; // native takes precedence
            }
            resolved[desc.fileOrIdentifier.toStdString()] = pd;
        }
    }
    PluginListResponse res;
    for(auto & p : resolved) {
        res.plugins.push_back(p.second);
    }
    return res;
}

JSONCONS_N_MEMBER_TRAITS(ShmSetTransportData, 9,
                         bpm,
                         beatNumerator,
                         beatDenominator,
                         samplePosition,
                         sampleLoopStart,
                         sampleLoopEnd,
                         playing,
                         recording,
                         looping);

JSONCONS_ALL_MEMBER_TRAITS(KeyDescription,
                           keyDown,
                           keyDescription);

struct ParameterListResponse {
    // ...
};

JSONCONS_ALL_MEMBER_TRAITS(ParameterDescription, index, name);

template<typename T>
inline std::string json_to_string(const T & v) {
    std::string res;
    jsoncons::encode_json(v, res);
    return res;
}

#endif /* wavtool_json_h */
