/*
    Rosegarden
    A MIDI and audio sequencer and musical notation editor.
 
    This program is Copyright 2000-2008
        Guillaume Laurent   <glaurent@telegraph-road.org>,
        Chris Cannam        <cannam@all-day-breakfast.com>,
        Richard Bown        <richard.bown@ferventsoftware.com>
 
    The moral rights of Guillaume Laurent, Chris Cannam, and Richard
    Bown to claim authorship of this work have been asserted.
 
    Other copyrights also apply to some parts of this work.  Please
    see the AUTHORS file and individual file headers for details.
 
    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.  See the file
    COPYING included with this distribution for more information.
*/


#include "MidiMixerWindow.h"
#include <tqlayout.h>

#include "sound/Midi.h"
#include <tdelocale.h>
#include "misc/Debug.h"
#include "misc/Strings.h"
#include "base/Colour.h"
#include "base/Device.h"
#include "base/Instrument.h"
#include "base/MidiDevice.h"
#include "base/MidiProgram.h"
#include "base/Studio.h"
#include "document/RosegardenGUIDoc.h"
#include "gui/editors/notation/NotePixmapFactory.h"
#include "gui/seqmanager/SequencerMapper.h"
#include "gui/widgets/Fader.h"
#include "gui/widgets/Rotary.h"
#include "gui/widgets/VUMeter.h"
#include "MidiMixerVUMeter.h"
#include "MixerWindow.h"
#include "sound/MappedEvent.h"
#include "StudioControl.h"
#include <tdeaction.h>
#include <tdemainwindow.h>
#include <kstdaction.h>
#include <tqaccel.h>
#include <tqcolor.h>
#include <tqframe.h>
#include <tqiconset.h>
#include <tqlabel.h>
#include <tqobject.h>
#include <tqstring.h>
#include <tqtabwidget.h>
#include <tqwidget.h>


namespace Rosegarden
{

MidiMixerWindow::MidiMixerWindow(TQWidget *parent,
                                 RosegardenGUIDoc *document):
        MixerWindow(parent, document),
        m_tabFrame(0)
{
    // Initial setup
    //
    setupTabs();

    KStdAction::close(this,
                      TQ_SLOT(slotClose()),
                      actionCollection());

    TQIconSet icon = TQIconSet(NotePixmapFactory::toTQPixmap(NotePixmapFactory::makeToolbarPixmap
                             ("transport-play")));
    TDEAction *play = new TDEAction(i18n("&Play"), icon, Key_Enter, this,
                TQ_SIGNAL(play()), actionCollection(), "play");
    // Alternative shortcut for Play
    TDEShortcut playShortcut = play->shortcut();
    playShortcut.append( KKey(Key_Return + CTRL) );
    play->setShortcut(playShortcut);

    icon = TQIconSet(NotePixmapFactory::toTQPixmap(NotePixmapFactory::makeToolbarPixmap
                    ("transport-stop")));
    new TDEAction(i18n("&Stop"), icon, Key_Insert, this,
                TQ_SIGNAL(stop()), actionCollection(), "stop");

    icon = TQIconSet(NotePixmapFactory::toTQPixmap(NotePixmapFactory::makeToolbarPixmap
                    ("transport-rewind")));
    new TDEAction(i18n("Re&wind"), icon, Key_End, this,
                TQ_SIGNAL(rewindPlayback()), actionCollection(),
                "playback_pointer_back_bar");

    icon = TQIconSet(NotePixmapFactory::toTQPixmap(NotePixmapFactory::makeToolbarPixmap
                    ("transport-ffwd")));
    new TDEAction(i18n("&Fast Forward"), icon, Key_PageDown, this,
                TQ_SIGNAL(fastForwardPlayback()), actionCollection(),
                "playback_pointer_forward_bar");

    icon = TQIconSet(NotePixmapFactory::toTQPixmap(NotePixmapFactory::makeToolbarPixmap
                    ("transport-rewind-end")));
    new TDEAction(i18n("Rewind to &Beginning"), icon, 0, this,
                TQ_SIGNAL(rewindPlaybackToBeginning()), actionCollection(),
                "playback_pointer_start");

    icon = TQIconSet(NotePixmapFactory::toTQPixmap(NotePixmapFactory::makeToolbarPixmap
                    ("transport-ffwd-end")));
    new TDEAction(i18n("Fast Forward to &End"), icon, 0, this,
                TQ_SIGNAL(fastForwardPlaybackToEnd()), actionCollection(),
                "playback_pointer_end");

    icon = TQIconSet(NotePixmapFactory::toTQPixmap(NotePixmapFactory::makeToolbarPixmap
                    ("transport-record")));
    new TDEAction(i18n("&Record"), icon, 0, this,
                TQ_SIGNAL(record()), actionCollection(),
                "record");

    icon = TQIconSet(NotePixmapFactory::toTQPixmap(NotePixmapFactory::makeToolbarPixmap
                    ("transport-panic")));
    new TDEAction(i18n("Panic"), icon, Key_P + CTRL + ALT, this,
                TQ_SIGNAL(panic()), actionCollection(),
                "panic");

    createGUI("midimixer.rc");

}

void
MidiMixerWindow::setupTabs()
{
    DeviceListConstIterator it;
    MidiDevice *dev = 0;
    InstrumentList instruments;
    InstrumentList::const_iterator iIt;
    int faderCount = 0, deviceCount = 1;

    if (m_tabFrame)
        delete m_tabFrame;

    // Setup m_tabFrame
    //
    m_tabWidget = new TQTabWidget(this);
    setCentralWidget(m_tabWidget);
    connect(m_tabWidget, TQ_SIGNAL(currentChanged(TQWidget *)),
            this, TQ_SLOT(slotCurrentTabChanged(TQWidget *)));
    m_tabWidget->setTabPosition(TQTabWidget::Bottom);
    setCaption(i18n("MIDI Mixer"));

    for (it = m_studio->begin(); it != m_studio->end(); ++it) {
        dev = dynamic_cast<MidiDevice*>(*it);

        if (dev) {
            // Get the control parameters that are on the IPB (and hence can
            // be shown here too).
            //
            ControlList controls = dev->getIPBControlParameters();

            instruments = dev->getPresentationInstruments();

            // Don't add a frame for empty devices
            //
            if (!instruments.size())
                continue;

            m_tabFrame = new TQFrame(m_tabWidget);
            m_tabFrame->setFrameStyle(TQFrame::TabWidgetPanel);
            m_tabFrame->setMargin(10);

            TQGridLayout *mainLayout = new TQGridLayout
                                      (m_tabFrame, instruments.size() + 4, controls.size() + 4, 5);

            // MIDI Mixer label
            //
            //TQLabel *label = new TQLabel(TQString("%1 %2").
            //arg(strtoqstr(dev->getName()))
            //.arg(i18n("MIDI Mixer")), m_tabFrame);

            TQLabel *label = new TQLabel("", m_tabFrame);
            mainLayout->addMultiCellWidget(label, 0, 0, 0, 16, TQt::AlignCenter);

            // control labels
            for (unsigned int i = 0; i < controls.size(); ++i) {
                label = new TQLabel(strtoqstr(controls[i].getName()), m_tabFrame);
                mainLayout->addWidget(label, i + 1, 0, TQt::AlignCenter);
            }

            // meter label
            //
            //label = new TQLabel(i18n("Meter"), m_tabFrame);
            //mainLayout->addWidget(label,
            //controls.size() + 1, 0, TQt::AlignCenter);

            // volume label
            label = new TQLabel(i18n("Volume"), m_tabFrame);
            mainLayout->addWidget(label, controls.size() + 2, 0,
                                  TQt::AlignCenter);

            // instrument label
            label = new TQLabel(i18n("Instrument"), m_tabFrame);
            mainLayout->addWidget(label, controls.size() + 3, 0,
                                  TQt::AlignCenter);

            int posCount = 1;
            int firstInstrument = -1;

            for (iIt = instruments.begin(); iIt != instruments.end(); ++iIt) {

                // Add new fader struct
                //
                m_faders.push_back(new FaderStruct());

                // Store the first ID
                //
                if (firstInstrument == -1)
                    firstInstrument = (*iIt)->getId();


                // Add the controls
                //
                for (unsigned int i = 0; i < controls.size(); ++i) {
                    TQColor knobColour = TQt::white;

                    if (controls[i].getColourIndex() > 0) {
                        Colour c =
                            m_document->getComposition().getGeneralColourMap().
                            getColourByIndex(controls[i].getColourIndex());

                        knobColour = TQColor(c.getRed(),
                                            c.getGreen(), c.getBlue());
                    }

                    Rotary *controller =
                        new Rotary(m_tabFrame,
                                   controls[i].getMin(),
                                   controls[i].getMax(),
                                   1.0,
                                   5.0,
                                   controls[i].getDefault(),
                                   20,
                                   Rotary::NoTicks,
                                   false,
                                   controls[i].getDefault() == 64); //!!! hacky

                    controller->setKnobColour(knobColour);

                    connect(controller, TQ_SIGNAL(valueChanged(float)),
                            this, TQ_SLOT(slotControllerChanged(float)));

                    mainLayout->addWidget(controller, i + 1, posCount,
                                          TQt::AlignCenter);

                    // Store the rotary
                    //
                    m_faders[faderCount]->m_controllerRotaries.push_back(
                        std::pair<MidiByte, Rotary*>
                        (controls[i].getControllerValue(), controller));
                }

                // Pan rotary
                //
                MidiMixerVUMeter *meter =
                    new MidiMixerVUMeter(m_tabFrame,
                                         VUMeter::FixedHeightVisiblePeakHold, 6, 30);
                mainLayout->addWidget(meter, controls.size() + 1,
                                      posCount, TQt::AlignCenter);
                m_faders[faderCount]->m_vuMeter = meter;

                // Volume fader
                //
                Fader *fader =
                    new Fader(0, 127, 100, 20, 80, m_tabFrame);
                mainLayout->addWidget(fader, controls.size() + 2,
                                      posCount, TQt::AlignCenter);
                m_faders[faderCount]->m_volumeFader = fader;
                //fader->setFader(float((*iIt)->getVolume()));

                // Label
                //
                TQLabel *idLabel = new TQLabel(TQString("%1").
                                             arg((*iIt)->getId() - firstInstrument + 1),
                                             m_tabFrame, "idLabel");

                mainLayout->addWidget(idLabel, controls.size() + 3,
                                      posCount, TQt::AlignCenter);

                // store id in struct
                m_faders[faderCount]->m_id = (*iIt)->getId();

                // Connect them up
                //
                connect(fader, TQ_SIGNAL(faderChanged(float)),
                        this, TQ_SLOT(slotFaderLevelChanged(float)));

                // Update all the faders and controllers
                //
                slotUpdateInstrument((*iIt)->getId());

                // Increment counters
                //
                posCount++;
                faderCount++;
            }

            TQString name = TQString("%1 (%2)").arg(strtoqstr(dev->getName()))
                           .arg(deviceCount++);

            addTab(m_tabFrame, name);
        }
    }
}

void
MidiMixerWindow::addTab(TQWidget *tab, const TQString &title)
{
    m_tabWidget->addTab(tab, title);
}

void
MidiMixerWindow::slotFaderLevelChanged(float value)
{
    const TQObject *s = sender();

    for (FaderVector::const_iterator it = m_faders.begin();
            it != m_faders.end(); ++it) {
        if ((*it)->m_volumeFader == s) {
            Instrument *instr = m_studio->
                                getInstrumentById((*it)->m_id);

            if (instr) {

                instr->setVolume(MidiByte(value));

                MappedEvent mE((*it)->m_id,
                               MappedEvent::MidiController,
                               MIDI_CONTROLLER_VOLUME,
                               MidiByte(value));
                StudioControl::sendMappedEvent(mE);

                // send out to external controllers as well.
                //!!! really want some notification of whether we have any!
                int tabIndex = m_tabWidget->currentPageIndex();
                if (tabIndex < 0)
                    tabIndex = 0;
                int i = 0;
                for (DeviceList::const_iterator dit = m_studio->begin();
                        dit != m_studio->end(); ++dit) {
                    RG_DEBUG << "slotFaderLevelChanged: i = " << i << ", tabIndex " << tabIndex << endl;
                    if (!dynamic_cast<MidiDevice*>(*dit))
                        continue;
                    if (i != tabIndex) {
                        ++i;
                        continue;
                    }
                    RG_DEBUG << "slotFaderLevelChanged: device id = " << instr->getDevice()->getId() << ", visible device id " << (*dit)->getId() << endl;
                    if (instr->getDevice()->getId() == (*dit)->getId()) {
                        RG_DEBUG << "slotFaderLevelChanged: sending control device mapped event for channel " << instr->getMidiChannel() << endl;
                        mE.setRecordedChannel(instr->getMidiChannel());
                        mE.setRecordedDevice(Device::CONTROL_DEVICE);
                        StudioControl::sendMappedEvent(mE);
                    }
                    break;
                }
            }

            emit instrumentParametersChanged((*it)->m_id);
            return ;
        }
    }
}

void
MidiMixerWindow::slotControllerChanged(float value)
{
    const TQObject *s = sender();
    unsigned int i = 0, j = 0;

    for (i = 0; i < m_faders.size(); ++i) {
        for (j = 0; j < m_faders[i]->m_controllerRotaries.size(); ++j) {
            if (m_faders[i]->m_controllerRotaries[j].second == s)
                break;
        }

        // break out on match
        if (j != m_faders[i]->m_controllerRotaries.size())
            break;
    }

    // Don't do anything if we've not matched and got solid values
    // for i and j
    //
    if (i == m_faders.size() || j == m_faders[i]->m_controllerRotaries.size())
        return ;

    //RG_DEBUG << "MidiMixerWindow::slotControllerChanged - found a controller"
    //<< endl;

    Instrument *instr = m_studio->getInstrumentById(
                            m_faders[i]->m_id);

    if (instr) {

        //RG_DEBUG << "MidiMixerWindow::slotControllerChanged - "
        //<< "got instrument to change" << endl;

        if (m_faders[i]->m_controllerRotaries[j].first ==
                MIDI_CONTROLLER_PAN)
            instr->setPan(MidiByte(value));
        else {
            instr->setControllerValue(m_faders[i]->
                                      m_controllerRotaries[j].first,
                                      MidiByte(value));
        }

        MappedEvent mE(m_faders[i]->m_id,
                       MappedEvent::MidiController,
                       m_faders[i]->m_controllerRotaries[j].first,
                       MidiByte(value));
        StudioControl::sendMappedEvent(mE);

        int tabIndex = m_tabWidget->currentPageIndex();
        if (tabIndex < 0)
            tabIndex = 0;
        int i = 0;
        for (DeviceList::const_iterator dit = m_studio->begin();
                dit != m_studio->end(); ++dit) {
            RG_DEBUG << "slotControllerChanged: i = " << i << ", tabIndex " << tabIndex << endl;
            if (!dynamic_cast<MidiDevice*>(*dit))
                continue;
            if (i != tabIndex) {
                ++i;
                continue;
            }
            RG_DEBUG << "slotControllerChanged: device id = " << instr->getDevice()->getId() << ", visible device id " << (*dit)->getId() << endl;
            if (instr->getDevice()->getId() == (*dit)->getId()) {
                RG_DEBUG << "slotControllerChanged: sending control device mapped event for channel " << instr->getMidiChannel() << endl;
                // send out to external controllers as well.
                //!!! really want some notification of whether we have any!
                mE.setRecordedChannel(instr->getMidiChannel());
                mE.setRecordedDevice(Device::CONTROL_DEVICE);
                StudioControl::sendMappedEvent(mE);
            }
        }

        emit instrumentParametersChanged(m_faders[i]->m_id);
    }
}

void
MidiMixerWindow::slotUpdateInstrument(InstrumentId id)
{
    //RG_DEBUG << "MidiMixerWindow::slotUpdateInstrument - id = " << id << endl;

    DeviceListConstIterator it;
    MidiDevice *dev = 0;
    InstrumentList instruments;
    InstrumentList::const_iterator iIt;
    int count = 0;

    blockSignals(true);

    for (it = m_studio->begin(); it != m_studio->end(); ++it) {
        dev = dynamic_cast<MidiDevice*>(*it);

        if (dev) {
            instruments = dev->getPresentationInstruments();
            ControlList controls = dev->getIPBControlParameters();

            for (iIt = instruments.begin(); iIt != instruments.end(); ++iIt) {
                // Match and set
                //
                if ((*iIt)->getId() == id) {
                    // Set Volume fader
                    //
                    m_faders[count]->m_volumeFader->blockSignals(true);
                    m_faders[count]->m_volumeFader->
                    setFader(float((*iIt)->getVolume()));
                    m_faders[count]->m_volumeFader->blockSignals(false);

                    /*
                    StaticControllers &staticControls = 
                        (*iIt)->getStaticControllers();
                    RG_DEBUG << "STATIC CONTROLS SIZE = " 
                             << staticControls.size() << endl;
                    */

                    // Set all controllers for this Instrument
                    //
                    for (unsigned int i = 0; i < controls.size(); ++i) {
                        float value = 0.0;

                        m_faders[count]->m_controllerRotaries[i].second->blockSignals(true);

                        if (controls[i].getControllerValue() ==
                                MIDI_CONTROLLER_PAN) {
                            m_faders[count]->m_controllerRotaries[i].
                            second->setPosition((*iIt)->getPan());
                        } else {
                            // The ControllerValues might not yet be set on
                            // the actual Instrument so don't always expect
                            // to find one.  There might be a hole here for
                            // deleted Controllers to hang around on
                            // Instruments..
                            //
                            try {
                                value = float((*iIt)->getControllerValue
                                              (controls[i].getControllerValue()));
                            } catch (std::string s) {
                                /*
                                RG_DEBUG << 
                                "MidiMixerWindow::slotUpdateInstrument - "
                                         << "can't match controller " 
                                         << int(controls[i].
                                             getControllerValue()) << " - \""
                                         << s << "\"" << endl;
                                         */
                                continue;
                            }

                            /*
                            RG_DEBUG << "MidiMixerWindow::slotUpdateInstrument"
                                     << " - MATCHED "
                                     << int(controls[i].getControllerValue())
                                     << endl;
                                     */

                            m_faders[count]->m_controllerRotaries[i].
                            second->setPosition(value);
                        }

                        m_faders[count]->m_controllerRotaries[i].second->blockSignals(false);
                    }
                }
                count++;
            }
        }
    }

    blockSignals(false);
}

void
MidiMixerWindow::updateMeters(SequencerMapper *mapper)
{
    for (unsigned int i = 0; i != m_faders.size(); ++i) {
        LevelInfo info;
        if (!mapper->
                getInstrumentLevelForMixer(m_faders[i]->m_id, info))
            continue;
        m_faders[i]->m_vuMeter->setLevel(double(info.level / 127.0));
        RG_DEBUG << "MidiMixerWindow::updateMeters - level  " << info.level << endl;
    }
}

void
MidiMixerWindow::updateMonitorMeter(SequencerMapper *)
{
    // none here
}

void
MidiMixerWindow::slotControllerDeviceEventReceived(MappedEvent *e,
        const void *preferredCustomer)
{
    if (preferredCustomer != this)
        return ;
    RG_DEBUG << "MidiMixerWindow::slotControllerDeviceEventReceived: this one's for me" << endl;
    raise();

    // get channel number n from event
    // get nth instrument on current tab

    if (e->getType() != MappedEvent::MidiController)
        return ;
    unsigned int channel = e->getRecordedChannel();
    MidiByte controller = e->getData1();
    MidiByte value = e->getData2();

    int tabIndex = m_tabWidget->currentPageIndex();

    int i = 0;

    for (DeviceList::const_iterator it = m_studio->begin();
            it != m_studio->end(); ++it) {

        MidiDevice *dev =
            dynamic_cast<MidiDevice*>(*it);

        if (!dev)
            continue;
        if (i != tabIndex) {
            ++i;
            continue;
        }

        InstrumentList instruments = dev->getPresentationInstruments();

        for (InstrumentList::const_iterator iIt =
                    instruments.begin(); iIt != instruments.end(); ++iIt) {

            Instrument *instrument = *iIt;

            if (instrument->getMidiChannel() != channel)
                continue;

            switch (controller) {

            case MIDI_CONTROLLER_VOLUME:
                RG_DEBUG << "Setting volume for instrument " << instrument->getId() << " to " << value << endl;
                instrument->setVolume(value);
                break;

            case MIDI_CONTROLLER_PAN:
                RG_DEBUG << "Setting pan for instrument " << instrument->getId() << " to " << value << endl;
                instrument->setPan(value);
                break;

            default: {
                    ControlList cl = dev->getIPBControlParameters();
                    for (ControlList::const_iterator i = cl.begin();
                            i != cl.end(); ++i) {
                        if ((*i).getControllerValue() == controller) {
                            RG_DEBUG << "Setting controller " << controller << " for instrument " << instrument->getId() << " to " << value << endl;
                            instrument->setControllerValue(controller, value);
                            break;
                        }
                    }
                    break;
                }
            }

            MappedEvent mE(instrument->getId(),
                           MappedEvent::MidiController,
                           MidiByte(controller),
                           MidiByte(value));
            StudioControl::sendMappedEvent(mE);

            slotUpdateInstrument(instrument->getId());
            emit instrumentParametersChanged(instrument->getId());
        }

        break;
    }
}

void
MidiMixerWindow::slotCurrentTabChanged(TQWidget *)
{
    sendControllerRefresh();
}

void
MidiMixerWindow::sendControllerRefresh()
{
    //!!! need to know if we have a current external controller device,
    // as this is expensive

    int tabIndex = m_tabWidget->currentPageIndex();
    RG_DEBUG << "MidiMixerWindow::slotCurrentTabChanged: current is " << tabIndex << endl;

    if (tabIndex < 0)
        return ;

    int i = 0;

    for (DeviceList::const_iterator dit = m_studio->begin();
            dit != m_studio->end(); ++dit) {

        MidiDevice *dev = dynamic_cast<MidiDevice*>(*dit);
        RG_DEBUG << "device is " << (*dit)->getId() << ", dev " << dev << endl;

        if (!dev)
            continue;
        if (i != tabIndex) {
            ++i;
            continue;
        }

        InstrumentList instruments = dev->getPresentationInstruments();
        ControlList controls = dev->getIPBControlParameters();

        RG_DEBUG << "device has " << instruments.size() << " presentation instruments, " << dev->getAllInstruments().size() << " instruments " << endl;

        for (InstrumentList::const_iterator iIt =
                    instruments.begin(); iIt != instruments.end(); ++iIt) {

            Instrument *instrument = *iIt;
            int channel = instrument->getMidiChannel();

            RG_DEBUG << "instrument is " << instrument->getId() << endl;

            for (ControlList::const_iterator cIt =
                        controls.begin(); cIt != controls.end(); ++cIt) {

                int controller = (*cIt).getControllerValue();
                int value;
                if (controller == MIDI_CONTROLLER_PAN) {
                    value = instrument->getPan();
                } else {
                    try {
                        value = instrument->getControllerValue(controller);
                    } catch (std::string s) {
                        std::cerr << "Exception in MidiMixerWindow::currentChanged: " << s << " (controller " << controller << ", instrument " << instrument->getId() << ")" << std::endl;
                        value = 0;
                    }
                }

                MappedEvent mE(instrument->getId(),
                               MappedEvent::MidiController,
                               controller, value);
                mE.setRecordedChannel(channel);
                mE.setRecordedDevice(Device::CONTROL_DEVICE);
                StudioControl::sendMappedEvent(mE);
            }

            MappedEvent mE(instrument->getId(),
                           MappedEvent::MidiController,
                           MIDI_CONTROLLER_VOLUME,
                           instrument->getVolume());
            mE.setRecordedChannel(channel);
            mE.setRecordedDevice(Device::CONTROL_DEVICE);
            RG_DEBUG << "sending controller mapped event for channel " << channel << ", volume " << instrument->getVolume() << endl;
            StudioControl::sendMappedEvent(mE);
        }

        break;
    }
}

void
MidiMixerWindow::slotSynchronise()
{
    RG_DEBUG << "MidiMixer::slotSynchronise" << endl;
    //setupTabs();
}

}
#include "MidiMixerWindow.moc"
