#include "CustomPluginScanner.hpp"
#include "common.h"

using namespace juce;

class ScannerSuperprocess final : private ChildProcessCoordinator
{
public:
    ScannerSuperprocess(const String& architecture)
    {
        launchWorkerProcess (File::getSpecialLocation (File::currentExecutableFile), scannerProcessUID, 0, 0, architecture);
    }

    enum class State
    {
        timeout,
        gotResult,
        connectionLost,
    };

    struct Response
    {
        State state;
        std::unique_ptr<XmlElement> xml;
    };

    Response getResponse()
    {
        std::unique_lock<std::mutex> lock { mutex };

        if (! condvar.wait_for (lock, std::chrono::milliseconds { 50 }, [&] { return gotResult || connectionLost; })) {
            return { State::timeout, nullptr };
        }

        const auto state = connectionLost ? State::connectionLost : State::gotResult;
        connectionLost = false;
        gotResult = false;

        return { state, std::move (pluginDescription) };
    }

    using ChildProcessCoordinator::sendMessageToWorker;

private:
    void handleMessageFromWorker (const MemoryBlock& mb) override
    {
        const std::lock_guard<std::mutex> lock { mutex };
        pluginDescription = parseXML (mb.toString());
        gotResult = true;
        condvar.notify_one();
    }

    void handleConnectionLost() override
    {
        const std::lock_guard<std::mutex> lock { mutex };
        connectionLost = true;
        condvar.notify_one();
    }

    std::mutex mutex;
    std::condition_variable condvar;

    std::unique_ptr<XmlElement> pluginDescription;
    bool connectionLost = false;
    bool gotResult = false;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScannerSuperprocess)
};

CustomPluginScanner::CustomPluginScanner(const String& architecture, const std::list<String> & skipIdentifiers)
  : architecture(architecture),
    skipIdentifiers(skipIdentifiers.cbegin(), skipIdentifiers.cend())
{}
CustomPluginScanner::~CustomPluginScanner() {}

bool CustomPluginScanner::findPluginTypesFor
 (AudioPluginFormat& format,
  OwnedArray<PluginDescription>& result,
  const String& fileOrIdentifier)
{
    if (addPluginDescriptions (format.getName(), fileOrIdentifier, result)) {
        return true;
    }

    std::unique_lock lock(threadSuperprocessMutex);
    threadSuperprocess[platformGetCurrentThread()] = nullptr;
    return false;
}

void CustomPluginScanner::scanFinished()
{
    std::unique_lock lock(threadSuperprocessMutex);
    threadSuperprocess[platformGetCurrentThread()] = nullptr;
}

bool CustomPluginScanner::addPluginDescriptions
 (const String& formatName,
  const String& fileOrIdentifier,
  OwnedArray<PluginDescription>& result)
{
    if (std::find(skipIdentifiers.cbegin(), skipIdentifiers.cend(), fileOrIdentifier) != skipIdentifiers.end()) {
        std::cerr << "Skipping " << fileOrIdentifier << "\n";
        return true;
    }
    ScannerSuperprocess * superprocess = nullptr;
    {
        std::unique_lock lock(threadSuperprocessMutex);
        auto it = threadSuperprocess.find(platformGetCurrentThread());
        
        if(it == threadSuperprocess.end() || !threadSuperprocess[platformGetCurrentThread()]) {
            threadSuperprocess[platformGetCurrentThread()] = std::make_unique<ScannerSuperprocess>(architecture);
            superprocess = threadSuperprocess[platformGetCurrentThread()].get();
        }
        superprocess = threadSuperprocess[platformGetCurrentThread()].get();
    }

    MemoryBlock block;
    MemoryOutputStream stream { block, true };
    stream.writeString (formatName);
    stream.writeString (fileOrIdentifier);

    if (! superprocess->sendMessageToWorker (block))
        return false;
    
    using clock_t = std::chrono::high_resolution_clock;
    auto startTime = clock_t::now();

    for (;;)
    {
        if (shouldExit())
            return true;

        const auto response = superprocess->getResponse();

        auto elapsedTime = clock_t::now() - startTime;
        if(elapsedTime > std::chrono::seconds(10)) {
            return false;
        }
        
        if (response.state == ScannerSuperprocess::State::timeout)
            continue;

        if (response.xml != nullptr)
        {
            for (const auto* item : response.xml->getChildIterator())
            {
                auto desc = std::make_unique<PluginDescription>();

                if (desc->loadFromXml (*item))
                    result.add (std::move (desc));
            }
        }

        return (response.state == ScannerSuperprocess::State::gotResult);
    }
}
