/*
    Virtual Piano test using the MIDI Sequencer C++ library
    Copyright (C) 2006-2019, Pedro Lopez-Cabanillas <plcl@users.sf.net>

    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
    (at your option) 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.

    You should have received a copy of the GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <QDebug>
#include <drumstick/backendmanager.h>
#include "vpiano.h"

VPiano::VPiano( QWidget * parent, Qt::WindowFlags flags )
    : QMainWindow(parent, flags),
    m_midiIn(nullptr),
    m_midiOut(nullptr)
{
    m_nativeInput = QLatin1Literal("Network");
    m_defaultInput = QLatin1Literal("21928");
#if defined(Q_OS_LINUX)
    m_nativeOutput = QLatin1Literal("SonivoxEAS");
    m_defaultOutput = QLatin1Literal("SonivoxEAS");
#elif defined(Q_OS_OSX)
    m_nativeOutput = QLatin1Literal("DLS Synth");
    m_defaultOutput = QLatin1Literal("DLS Synth");
#elif defined(Q_OS_WIN)
    m_nativeOutput = QLatin1Literal("Windows MM");
    m_defaultOutput = QLatin1Literal("Microsoft GS Wavetable Synth");
#else
    m_nativeOutput = m_nativeInput;
    m_defaultOutput = m_defaultInput;
#endif

    ui.setupUi(this);
    ui.statusBar->hide();

    readSettings();

    QSettings settings;
    BackendManager man;
    man.refresh(&settings);
    QList<MIDIInput*> inputs = man.availableInputs();
    QList<MIDIOutput*> outputs = man.availableOutputs();

    findInput(m_lastInputBackend, inputs);
    if (m_midiIn == nullptr) {
        findInput(m_nativeInput, inputs);
        if (m_midiIn == nullptr) {
            qFatal("Unable to find a suitable input backend.");
        }
    }

    findOutput(m_lastOutputBackend, outputs);
    if (m_midiOut == nullptr) {
        findOutput(m_nativeOutput, outputs);
        if (m_midiOut == nullptr) {
            qFatal("Unable to find a suitable output backend");
        }
    }

    connect(ui.actionExit, SIGNAL(triggered()), SLOT(close()));
    connect(ui.actionAbout, SIGNAL(triggered()), SLOT(slotAbout()));
    connect(ui.actionAbout_Qt, SIGNAL(triggered()), SLOT(slotAboutQt()));
    connect(ui.actionConnections, SIGNAL(triggered()), SLOT(slotConnections()));
    connect(ui.actionPreferences, SIGNAL(triggered()), SLOT(slotPreferences()));
    connect(ui.pianokeybd, SIGNAL(noteOn(int,int)), SLOT(slotNoteOn(int,int)));
    connect(ui.pianokeybd, SIGNAL(noteOff(int,int)), SLOT(slotNoteOff(int,int)));

    dlgConnections.setInputs(inputs);
    dlgConnections.setOutputs(outputs);
    dlgConnections.setInput(m_midiIn);
    dlgConnections.setOutput(m_midiOut);
    dlgConnections.setMidiThru(m_midiThru);
    dlgConnections.setAdvanced(m_advanced);

    if (m_midiIn != nullptr) {
#if QT_VERSION < 0x050700
        connect(m_midiIn, SIGNAL(midiNoteOn(int,int,int)),
                          SLOT(slotNoteOn(int,int,int)),
                          Qt::QueuedConnection);
        connect(m_midiIn, SIGNAL(midiNoteOff(int,int,int)),
                          SLOT(slotNoteOff(int,int,int)),
                          Qt::QueuedConnection);
#else
        connect(m_midiIn, &MIDIInput::midiNoteOn,
                this, QOverload<int,int,int>::of(&VPiano::slotNoteOn),
                Qt::QueuedConnection);
        connect(m_midiIn, &MIDIInput::midiNoteOff,
                this, QOverload<int,int,int>::of(&VPiano::slotNoteOff),
                Qt::QueuedConnection);
#endif
        if (!m_lastInputConnection.isEmpty()) {
            m_midiIn->initialize(&settings);
            m_midiIn->open(m_lastInputConnection);
        }
    }

    if (m_midiOut != nullptr && !m_lastOutputConnection.isEmpty()) {
        m_midiOut->initialize(&settings);
        m_midiOut->open(m_lastOutputConnection);
        if (m_midiIn != nullptr) {
            m_midiIn->setMIDIThruDevice(m_midiOut);
            m_midiIn->enableMIDIThru(m_midiThru);
        }
    }
}

VPiano::~VPiano()
{
    //qDebug() << Q_FUNC_INFO;
    m_midiIn->close();
    m_midiOut->close();
    //qDebug() << "Cheers!";
}

void VPiano::slotNoteOn(const int midiNote, const int vel)
{
    int chan = dlgPreferences.getOutChannel();
    m_midiOut->sendNoteOn(chan, midiNote, vel);
}

void VPiano::slotNoteOff(const int midiNote, const int vel)
{
    int chan = dlgPreferences.getOutChannel();
    m_midiOut->sendNoteOff(chan, midiNote, vel);
}

void VPiano::slotNoteOn(const int chan, const int note, const int vel)
{
    if (dlgPreferences.getInChannel() == chan) {
        if (vel > 0)
            ui.pianokeybd->getPianoScene()->showNoteOn(note);
        else
            ui.pianokeybd->getPianoScene()->showNoteOff(note);
    }
}

void VPiano::slotNoteOff(const int chan, const int note, const int vel)
{
    Q_UNUSED(vel)
    if (dlgPreferences.getInChannel() == chan) {
        ui.pianokeybd->getPianoScene()->showNoteOff(note);
    }
}

void VPiano::slotAbout()
{
    dlgAbout.exec();
}

void VPiano::slotAboutQt()
{
    qApp->aboutQt();
}

void VPiano::slotConnections()
{
    dlgConnections.refresh();
    if (dlgConnections.exec() == QDialog::Accepted) {
        if (m_midiIn != nullptr) {
            m_midiIn->disconnect();
        }
        if (m_midiOut != nullptr) {
            m_midiOut->disconnect();
        }
        m_midiIn = dlgConnections.getInput();
        m_midiOut = dlgConnections.getOutput();
        if (m_midiIn != nullptr) {
            connect(m_midiIn, SIGNAL(midiNoteOn(int,int,int)), SLOT(slotNoteOn(int,int,int)));
            connect(m_midiIn, SIGNAL(midiNoteOff(int,int,int)), SLOT(slotNoteOff(int,int,int)));
        }
        m_midiThru = dlgConnections.midiThru();
        m_advanced = dlgConnections.advanced();
    }
}

void VPiano::slotPreferences()
{
    if (dlgPreferences.exec() == QDialog::Accepted) {
        if (ui.pianokeybd->baseOctave() != dlgPreferences.getBaseOctave()) {
            ui.pianokeybd->setBaseOctave(dlgPreferences.getBaseOctave());
        }
        if (ui.pianokeybd->numKeys() != dlgPreferences.getNumKeys()) {
            ui.pianokeybd->setNumKeys(dlgPreferences.getNumKeys());
        }
        if (ui.pianokeybd->startKey() != dlgPreferences.getStartingKey()) {
            ui.pianokeybd->setNumKeys(dlgPreferences.getNumKeys(), dlgPreferences.getStartingKey());
        }
    }
}

void VPiano::closeEvent(QCloseEvent *event)
{
    //qDebug() << Q_FUNC_INFO;
    writeSettings();
    event->accept();
}

void VPiano::writeSettings()
{
    QSettings settings;

    settings.beginGroup("Window");
    settings.setValue("Geometry", saveGeometry());
    settings.setValue("State", saveState());
    settings.endGroup();

    settings.beginGroup("Connections");
    settings.setValue("inputBackend", m_midiIn->backendName());
    settings.setValue("outputBackend", m_midiOut->backendName());
    settings.setValue("inputConnection", m_midiIn->currentConnection());
    settings.setValue("outputConnection", m_midiOut->currentConnection());
    settings.setValue("midiThru", m_midiThru);
    settings.setValue("advanced", m_advanced);
    settings.endGroup();

    settings.beginGroup("Preferences");
    settings.setValue("inputChannel", dlgPreferences.getInChannel());
    settings.setValue("outputChannel",dlgPreferences.getOutChannel());
    settings.setValue("velocity", dlgPreferences. getVelocity());
    settings.setValue("baseOctave", dlgPreferences.getBaseOctave());
    settings.setValue("numKeys", dlgPreferences.getNumKeys());
    settings.setValue("startingKey", dlgPreferences.getStartingKey());
    settings.endGroup();

    settings.sync();
}

void VPiano::readSettings()
{
    QSettings settings;

    settings.beginGroup("Window");
    restoreGeometry(settings.value("Geometry").toByteArray());
    restoreState(settings.value("State").toByteArray());
    settings.endGroup();

    settings.beginGroup("Connections");
    m_lastInputBackend = settings.value("inputBackend", m_nativeInput).toString();
    m_lastOutputBackend = settings.value("outputBackend", m_nativeOutput).toString();
    m_lastInputConnection = settings.value("inputConnection", m_defaultInput).toString();
    m_lastOutputConnection = settings.value("outputConnection", m_defaultOutput).toString();
    m_midiThru = settings.value("midiThru", false).toBool();
    m_advanced = settings.value("advanced", false).toBool();
    settings.endGroup();

    settings.beginGroup("Preferences");
    dlgPreferences.setInChannel(settings.value("inputChannel", 0).toInt());
    dlgPreferences.setOutChannel(settings.value("outputChannel", 0).toInt());
    dlgPreferences.setVelocity(settings.value("velocity", 100).toInt());
    dlgPreferences.setBaseOctave(settings.value("baseOctave", 1).toInt());
    dlgPreferences.setNumKeys(settings.value("numKeys", 88).toInt());
    dlgPreferences.setStartingKey(settings.value("startingKey", 9).toInt());
    ui.pianokeybd->setBaseOctave(settings.value("baseOctave", 1).toInt());
    ui.pianokeybd->setNumKeys(settings.value("numKeys", 88).toInt(), settings.value("startingKey", 9).toInt());
    settings.endGroup();
}

void VPiano::findInput(QString name, QList<MIDIInput *> &inputs)
{
    if (name.isEmpty()) {
        return;
    }
    foreach(MIDIInput* input, inputs) {
        if (m_midiIn == nullptr && (input->backendName() == name))  {
            m_midiIn = input;
            break;
        }
    }
    if (m_midiIn == nullptr) {
        qWarning() << "Input backend not found: " << name;
    }
}

void VPiano::findOutput(QString name, QList<MIDIOutput *> &outputs)
{
    if (name.isEmpty()) {
        return;
    }
    foreach(MIDIOutput* output, outputs) {
        if (m_midiOut == nullptr && (output->backendName() == name))  {
            m_midiOut = output;
            break;
        }
    }
    if (m_midiOut == nullptr) {
        qWarning() << "Output backend not found: " << name;
    }
}
