/*
 * Carla Plugin Host
 * Copyright (C) 2011-2014 Filipe Coelho <falktx@falktx.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
 */

#ifndef BUILD_BRIDGE
# error This file should not be compiled if not building bridge
#endif

#include "CarlaEngineInternal.hpp"
#include "CarlaPlugin.hpp"

#include "CarlaBackendUtils.hpp"
#include "CarlaBridgeUtils.hpp"
#include "CarlaMIDI.h"

#include "jackbridge/JackBridge.hpp"

#include <cerrno>
#include <ctime>

CARLA_BACKEND_START_NAMESPACE

#if 0
} // Fix editor indentation
#endif

// -------------------------------------------------------------------

template<typename T>
bool jackbridge_shm_map2(char* shm, T*& value)
{
    value = (T*)jackbridge_shm_map(shm, sizeof(T));
    return (value != nullptr);
}

struct BridgeAudioPool {
    CarlaString filename;
    float* data;
    char shm[32];

    BridgeAudioPool()
        : data(nullptr)
    {
        carla_zeroChar(shm, 32);
        jackbridge_shm_init(shm);
    }

    ~BridgeAudioPool()
    {
        // should be cleared by now
        CARLA_ASSERT(data == nullptr);

        clear();
    }

    bool attach()
    {
        jackbridge_shm_attach(shm, filename.getBuffer());

        return jackbridge_shm_is_valid(shm);
    }

    void clear()
    {
        filename.clear();

        data = nullptr;

        if (jackbridge_shm_is_valid(shm))
            jackbridge_shm_close(shm);
    }
};

struct BridgeControl : public RingBufferControl<StackRingBuffer> {
    CarlaString filename;
    BridgeShmControl* data;
    char shm[32];

    BridgeControl()
        : RingBufferControl(nullptr),
          data(nullptr)
    {
        carla_zeroChar(shm, 32);
        jackbridge_shm_init(shm);
    }

    ~BridgeControl()
    {
        // should be cleared by now
        CARLA_ASSERT(data == nullptr);

        clear();
    }

    bool attach()
    {
        jackbridge_shm_attach(shm, filename.getBuffer());

        return jackbridge_shm_is_valid(shm);
    }

    void clear()
    {
        filename.clear();

        data = nullptr;

        if (jackbridge_shm_is_valid(shm))
            jackbridge_shm_close(shm);
    }

    bool mapData()
    {
        CARLA_ASSERT(data == nullptr);

        if (jackbridge_shm_map2<BridgeShmControl>(shm, data))
        {
            setRingBuffer(&data->ringBuffer, false);
            return true;
        }

        return false;
    }

    PluginBridgeOpcode readOpcode()
    {
        return static_cast<PluginBridgeOpcode>(readInt());
    }
};

// -------------------------------------------------------------------

class CarlaEngineBridge : public CarlaEngine,
                          public CarlaThread
{
public:
    CarlaEngineBridge(const char* const audioBaseName, const char* const controlBaseName)
        : CarlaEngine(),
          CarlaThread("CarlaEngineBridge"),
          fIsRunning(false)
    {
        carla_debug("CarlaEngineBridge::CarlaEngineBridge()");

        fShmAudioPool.filename  = "/carla-bridge_shm_";
        fShmAudioPool.filename += audioBaseName;

        fShmControl.filename    = "/carla-bridge_shc_";
        fShmControl.filename   += controlBaseName;
    }

    ~CarlaEngineBridge() override
    {
        carla_debug("CarlaEngineBridge::~CarlaEngineBridge()");
    }

    // -------------------------------------
    // CarlaEngine virtual calls

    bool init(const char* const clientName) override
    {
        carla_debug("CarlaEngineBridge::init(\"%s\")", clientName);

        // SHM Audio Pool
        {
            if (! fShmAudioPool.attach())
            {
                carla_stdout("Failed to open or create shared memory file #1");
                return false;
            }
        }

        // SHM Control
        {
            if (! fShmControl.attach())
            {
                carla_stdout("Failed to open or create shared memory file #2");
                // clear
                fShmAudioPool.clear();
                return false;
            }

            if (! fShmControl.mapData())
            {
                carla_stdout("Failed to mmap shared memory file");
                // clear
                fShmControl.clear();
                fShmAudioPool.clear();
                return false;
            }
        }

        // Read values from memory
        PluginBridgeOpcode opcode;

        opcode = fShmControl.readOpcode();
        CARLA_SAFE_ASSERT_INT(opcode == kPluginBridgeOpcodeNull, opcode);
        const uint32_t structSize = fShmControl.readUInt();
        CARLA_SAFE_ASSERT_INT2(structSize == sizeof(BridgeShmControl), structSize, sizeof(BridgeShmControl));
        carla_stderr("Struct Size: %i", structSize);

        opcode = fShmControl.readOpcode();
        CARLA_SAFE_ASSERT_INT(opcode == kPluginBridgeOpcodeSetBufferSize, opcode);
        pData->bufferSize = fShmControl.readUInt();
        carla_stderr("BufferSize: %i", pData->bufferSize);

        opcode = fShmControl.readOpcode();
        CARLA_SAFE_ASSERT_INT(opcode == kPluginBridgeOpcodeSetSampleRate, opcode);
        pData->sampleRate = fShmControl.readFloat();
        carla_stderr("SampleRate: %f", pData->sampleRate);

        CarlaThread::start();
        CarlaEngine::init(clientName);
        return true;
    }

    bool close() override
    {
        carla_debug("CarlaEnginePlugin::close()");
        CarlaEngine::close();

        CarlaThread::stop(6000);

        fShmControl.clear();
        fShmAudioPool.clear();

        return true;
    }

    bool isRunning() const noexcept override
    {
        return fIsRunning;
    }

    bool isOffline() const noexcept override
    {
        return false;
    }

    EngineType getType() const noexcept override
    {
        return kEngineTypeBridge;
    }

    const char* getCurrentDriverName() const noexcept
    {
        return "Bridge";
    }

    // -------------------------------------
    // CarlaThread virtual calls

    void run() override
    {
        fIsRunning = true;

        // TODO - set RT permissions
        carla_debug("CarlaEngineBridge::run()");

        for (; ! shouldExit();)
        {
            if (! jackbridge_sem_timedwait(&fShmControl.data->runServer, 5))
            {
                if (errno == ETIMEDOUT)
                {
                    fIsRunning = false;
                    signalShouldExit();
                    return;
                }
            }

            for (; fShmControl.isDataAvailable();)
            {
                const PluginBridgeOpcode opcode(fShmControl.readOpcode());

                if (opcode != kPluginBridgeOpcodeProcess) {
                    carla_debug("CarlaEngineBridge::run() - got opcode: %s", PluginBridgeOpcode2str(opcode));
                }

                switch (opcode)
                {
                case kPluginBridgeOpcodeNull:
                    break;

                case kPluginBridgeOpcodeSetAudioPool: {
                    const int64_t poolSize(fShmControl.readLong());
                    CARLA_SAFE_ASSERT_BREAK(poolSize > 0);
                    fShmAudioPool.data = (float*)jackbridge_shm_map(fShmAudioPool.shm, size_t(poolSize));
                    break;
                }

                case kPluginBridgeOpcodeSetBufferSize: {
                    const uint32_t bufferSize(fShmControl.readUInt());
                    bufferSizeChanged(bufferSize);
                    break;
                }

                case kPluginBridgeOpcodeSetSampleRate: {
                    const float sampleRate(fShmControl.readFloat());
                    sampleRateChanged(sampleRate);
                    break;
                }

                case kPluginBridgeOpcodeSetParameter: {
                    const int32_t index(fShmControl.readInt());
                    const float   value(fShmControl.readFloat());

                    CarlaPlugin* const plugin(getPluginUnchecked(0));

                    if (plugin != nullptr && plugin->isEnabled())
                    {
                        plugin->setParameterValueByRealIndex(index, value, false, false, false);

                        //if (index >= 0)
                        //    plugin->postponeRtEvent(kPluginPostRtEventParameterChange, index, 0, value);
                    }
                    break;
                }

                case kPluginBridgeOpcodeSetProgram: {
                    const int32_t index(fShmControl.readInt());
                    CARLA_SAFE_ASSERT_BREAK(index >= 0);

                    CarlaPlugin* const plugin(getPluginUnchecked(0));

                    if (plugin != nullptr && plugin->isEnabled())
                    {
                        plugin->setProgram(index, false, false, false);
                        //plugin->postponeRtEvent(kPluginPostRtEventProgramChange, index, 0, 0.0f);
                    }
                    break;
                }

                case kPluginBridgeOpcodeSetMidiProgram: {
                    const int32_t index(fShmControl.readInt());
                    CARLA_SAFE_ASSERT_BREAK(index >= 0);

                    CarlaPlugin* const plugin(getPluginUnchecked(0));

                    if (plugin != nullptr && plugin->isEnabled())
                    {
                        plugin->setMidiProgram(index, false, false, false);
                        //plugin->postponeRtEvent(kPluginPostRtEventMidiProgramChange, index, 0, 0.0f);
                    }

                    break;
                }

                case kPluginBridgeOpcodeMidiEvent: {
                    const int64_t time(fShmControl.readLong());
                    const int32_t size(fShmControl.readInt());
                    CARLA_SAFE_ASSERT_BREAK(time >= 0);
                    CARLA_SAFE_ASSERT_BREAK(size > 0 && size <= 4);

                    uint8_t data[size];

                    for (int32_t i=0; i < size; ++i)
                        data[i] = static_cast<uint8_t>(fShmControl.readChar());

                    CARLA_SAFE_ASSERT_BREAK(pData->bufEvents.in != nullptr);

                    for (ushort i=0; i < kMaxEngineEventInternalCount; ++i)
                    {
                        EngineEvent& event(pData->bufEvents.in[i]);

                        if (event.type != kEngineEventTypeNull)
                            continue;

                        event.fillFromMidiData(static_cast<uint8_t>(size), data);
                        break;
                    }
                    break;
                }

                case kPluginBridgeOpcodeProcess: {
                    CARLA_SAFE_ASSERT_BREAK(fShmAudioPool.data != nullptr);
                    CarlaPlugin* const plugin(getPluginUnchecked(0));

                    if (plugin != nullptr && plugin->isEnabled() && plugin->tryLock(true)) // FIXME - always lock?
                    {
                        const uint32_t inCount(plugin->getAudioInCount());
                        const uint32_t outCount(plugin->getAudioOutCount());

                        float* inBuffer[inCount];
                        float* outBuffer[outCount];

                        for (uint32_t i=0; i < inCount; ++i)
                            inBuffer[i] = fShmAudioPool.data + i*pData->bufferSize;
                        for (uint32_t i=0; i < outCount; ++i)
                            outBuffer[i] = fShmAudioPool.data + (i+inCount)*pData->bufferSize;

                        plugin->initBuffers();
                        plugin->process(inBuffer, outBuffer, pData->bufferSize);
                        plugin->unlock();
                    }

                    // clear buffer
                    CARLA_SAFE_ASSERT_BREAK(pData->bufEvents.in != nullptr);

                    if (pData->bufEvents.in[0].type != kEngineEventTypeNull)
                        carla_zeroStruct<EngineEvent>(pData->bufEvents.in, kMaxEngineEventInternalCount);
                    break;
                }

                case kPluginBridgeOpcodeQuit:
                    signalShouldExit();
                    fIsRunning = false;
                    break;
                }
            }

            if (! jackbridge_sem_post(&fShmControl.data->runClient))
                carla_stderr2("Could not post to semaphore");
        }

        fIsRunning = false;
    }

private:
    BridgeAudioPool fShmAudioPool;
    BridgeControl   fShmControl;

    volatile bool fIsRunning;

    CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaEngineBridge)
};

// -----------------------------------------

CarlaEngine* CarlaEngine::newBridge(const char* const audioBaseName, const char* const controlBaseName)
{
    return new CarlaEngineBridge(audioBaseName, controlBaseName);
}

CARLA_BACKEND_END_NAMESPACE

// -----------------------------------------------------------------------
// Extra stuff for linking purposes

CARLA_BACKEND_START_NAMESPACE

CarlaEngine*       CarlaEngine::newRtAudio(const AudioApi) { return nullptr; }
unsigned int       CarlaEngine::getRtAudioApiCount() { return 0; }
const char*        CarlaEngine::getRtAudioApiName(const unsigned int) { return nullptr; }
const char* const* CarlaEngine::getRtAudioApiDeviceNames(const unsigned int) { return nullptr; }
const EngineDriverDeviceInfo* CarlaEngine::getRtAudioDeviceInfo(const unsigned int, const char* const) { return nullptr; }

#ifdef HAVE_JUCE
CarlaEngine*       CarlaEngine::newJuce(const AudioApi) { return nullptr; }
unsigned int       CarlaEngine::getJuceApiCount() { return 0; }
const char*        CarlaEngine::getJuceApiName(const unsigned int) { return nullptr; }
const char* const* CarlaEngine::getJuceApiDeviceNames(const unsigned int) { return nullptr; }
const EngineDriverDeviceInfo* CarlaEngine::getJuceDeviceInfo(const unsigned int, const char* const) { return nullptr; }
#endif

CARLA_BACKEND_END_NAMESPACE

// -----------------------------------------------------------------------

#if defined(CARLA_OS_WIN) && ! defined(__WINE__)
extern "C" __declspec (dllexport)
#else
extern "C" __attribute__ ((visibility("default")))
#endif
void carla_register_native_plugin_carla();
void carla_register_native_plugin_carla(){}

// -----------------------------------------------------------------------
