/*
 * Carla Native Plugins
 * Copyright (C) 2012-2019 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.
 */

#include "CarlaNativePrograms.hpp"
#include "midi-base.hpp"

#include "water/files/FileInputStream.h"
#include "water/midi/MidiFile.h"

#ifndef HAVE_PYQT
# define process2 process
#endif

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

#ifdef HAVE_PYQT
class MidiFilePlugin : public NativePluginWithMidiPrograms<FileMIDI>,
#else
class MidiFilePlugin : public NativePluginClass,
#endif
                       public AbstractMidiPlayer
{
public:
    MidiFilePlugin(const NativeHostDescriptor* const host)
#ifdef HAVE_PYQT
        : NativePluginWithMidiPrograms<FileMIDI>(host, fPrograms, 0),
#else
        : NativePluginClass(host),
#endif
          fMidiOut(this),
          fNeedsAllNotesOff(false),
          fWasPlayingBefore(false)
#ifdef HAVE_PYQT
        , fPrograms(hostGetFilePath("midi"), "*.mid;*.midi")
#endif
    {
    }

protected:
    // -------------------------------------------------------------------
    // Plugin state calls

    void setCustomData(const char* const key, const char* const value) override
    {
        CARLA_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0',);
        CARLA_SAFE_ASSERT_RETURN(value != nullptr && value[0] != '\0',);

        if (std::strcmp(key, "file") != 0)
            return;

        _loadMidiFile(value);
    }

    // -------------------------------------------------------------------
    // Plugin process calls

    void process2(const float* const*, float**, const uint32_t frames, const NativeMidiEvent* const, const uint32_t) override
    {
        const NativeTimeInfo* const timePos(getTimeInfo());

        if (timePos == nullptr)
            return;

        if (fWasPlayingBefore != timePos->playing)
        {
            fNeedsAllNotesOff = true;
            fWasPlayingBefore = timePos->playing;
        }

        if (fNeedsAllNotesOff)
        {
            NativeMidiEvent midiEvent;

            midiEvent.port    = 0;
            midiEvent.time    = 0;
            midiEvent.data[0] = 0;
            midiEvent.data[1] = MIDI_CONTROL_ALL_NOTES_OFF;
            midiEvent.data[2] = 0;
            midiEvent.data[3] = 0;
            midiEvent.size    = 3;

            for (int channel=MAX_MIDI_CHANNELS; --channel >= 0;)
            {
                midiEvent.data[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (channel & MIDI_CHANNEL_BIT));
                NativePluginClass::writeMidiEvent(&midiEvent);
            }

            fNeedsAllNotesOff = false;
        }

        if (fWasPlayingBefore)
            fMidiOut.play(timePos->frame, frames);
    }

    // -------------------------------------------------------------------
    // Plugin UI calls

    void uiShow(const bool show) override
    {
        if (! show)
            return;

        if (const char* const filename = uiOpenFile(false, "Open MIDI File", "MIDI Files (*.mid *.midi);;"))
            uiCustomDataChanged("file", filename);

        uiClosed();
    }

    // -------------------------------------------------------------------
    // Plugin state calls

    char* getState() const override
    {
        return fMidiOut.getState();
    }

    void setState(const char* const data) override
    {
        fMidiOut.setState(data);
    }

#ifdef HAVE_PYQT
    void setStateFromFile(const char* const filename) override
    {
        _loadMidiFile(filename);
    }
#endif

    // -------------------------------------------------------------------
    // AbstractMidiPlayer calls

    void writeMidiEvent(const uint8_t port, const long double timePosFrame, const RawMidiEvent* const event) override
    {
        NativeMidiEvent midiEvent;

        midiEvent.port    = port;
        midiEvent.time    = uint32_t(timePosFrame);
        midiEvent.size    = event->size;
        midiEvent.data[0] = event->data[0];
        midiEvent.data[1] = event->data[1];
        midiEvent.data[2] = event->data[2];
        midiEvent.data[3] = event->data[3];

        NativePluginClass::writeMidiEvent(&midiEvent);
    }

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

private:
    MidiPattern fMidiOut;
    bool fNeedsAllNotesOff;
    bool fWasPlayingBefore;
#ifdef HAVE_PYQT
    NativeMidiPrograms fPrograms;
#endif

    void _loadMidiFile(const char* const filename)
    {
        fMidiOut.clear();

        using namespace water;

        const String jfilename = String(CharPointer_UTF8(filename));
        File file(jfilename);

        if (! file.existsAsFile())
           return;

        FileInputStream fileStream(file);
        MidiFile        midiFile;

        if (! midiFile.readFrom(fileStream))
            return;

        midiFile.convertTimestampTicksToSeconds();

        const double sampleRate(getSampleRate());

        for (size_t i=0, numTracks = midiFile.getNumTracks(); i<numTracks; ++i)
        {
            const MidiMessageSequence* const track(midiFile.getTrack(i));
            CARLA_SAFE_ASSERT_CONTINUE(track != nullptr);

            for (int j=0, numEvents = track->getNumEvents(); j<numEvents; ++j)
            {
                const MidiMessageSequence::MidiEventHolder* const midiEventHolder(track->getEventPointer(j));
                CARLA_SAFE_ASSERT_CONTINUE(midiEventHolder != nullptr);

                const MidiMessage& midiMessage(midiEventHolder->message);
                //const double time(track->getEventTime(i)*sampleRate);
                const int dataSize(midiMessage.getRawDataSize());

                if (dataSize <= 0 || dataSize > MAX_EVENT_DATA_SIZE)
                    continue;
                if (midiMessage.isActiveSense())
                    continue;
                if (midiMessage.isMetaEvent())
                    continue;
                if (midiMessage.isMidiStart())
                    continue;
                if (midiMessage.isMidiContinue())
                    continue;
                if (midiMessage.isMidiStop())
                    continue;
                if (midiMessage.isMidiClock())
                    continue;
                if (midiMessage.isSongPositionPointer())
                    continue;
                if (midiMessage.isQuarterFrame())
                    continue;
                if (midiMessage.isFullFrame())
                    continue;
                if (midiMessage.isMidiMachineControlMessage())
                    continue;
                if (midiMessage.isSysEx())
                    continue;

                const double time(midiMessage.getTimeStamp()*sampleRate);
                CARLA_SAFE_ASSERT_CONTINUE(time >= 0.0);

                fMidiOut.addRaw(static_cast<uint64_t>(time), midiMessage.getRawData(), static_cast<uint8_t>(dataSize));
            }
        }

        fNeedsAllNotesOff = true;
    }

    PluginClassEND(MidiFilePlugin)
    CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MidiFilePlugin)
};

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

static const NativePluginDescriptor midifileDesc = {
    /* category  */ NATIVE_PLUGIN_CATEGORY_UTILITY,
    /* hints     */ static_cast<NativePluginHints>(NATIVE_PLUGIN_IS_RTSAFE
                                                  |NATIVE_PLUGIN_HAS_UI
                                                  |NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE
#ifdef HAVE_PYQT
                                                  |NATIVE_PLUGIN_REQUESTS_IDLE
#endif
                                                  |NATIVE_PLUGIN_USES_STATE
                                                  |NATIVE_PLUGIN_USES_TIME),
    /* supports  */ NATIVE_PLUGIN_SUPPORTS_NOTHING,
    /* audioIns  */ 0,
    /* audioOuts */ 0,
    /* midiIns   */ 0,
    /* midiOuts  */ 1,
    /* paramIns  */ 0,
    /* paramOuts */ 0,
    /* name      */ "MIDI File",
    /* label     */ "midifile",
    /* maker     */ "falkTX",
    /* copyright */ "GNU GPL v2+",
    PluginDescriptorFILL(MidiFilePlugin)
};

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

CARLA_EXPORT
void carla_register_native_plugin_midifile();

CARLA_EXPORT
void carla_register_native_plugin_midifile()
{
    carla_register_native_plugin(&midifileDesc);
}

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

#ifndef HAVE_PYQT
# undef process2
#endif
