/*
 * MusE FLUID Synth softsynth plugin
 *
 * Copyright (C) 2004 Mathias Lundgren (lunar_shuttle@users.sourcforge.net)
 *
 * $Id: fluidsynthgui.cpp,v 1.19 2006/01/06 22:48:09 wschweer Exp $
 *
 */

#include "fluidsynthgui.h"
#include "fluidsynti.h"
#include <iostream>
#include "muse/midi.h"
#include "xpm/buttondown.xpm"


//#define MUSE_FLUID_DEBUG      false

FluidSynthGui::FluidSynthGui()
      : MessGui()
      {
      setupUi(this);
      //Connect socketnotifier to fifo
      QSocketNotifier* s = new QSocketNotifier(readFd, QSocketNotifier::Read);
      connect(s, SIGNAL(activated(int)), SLOT(readMessage(int)));
      connect (Push, SIGNAL (clicked()), SLOT(loadClicked()));

      pendingFont = "";

      //channelListView->setColumnWidthMode(FS_CHANNEL_COL, Q3ListView::Maximum);
      //channelListView->setColumnWidthMode(FS_SF_ID_COL,Q3ListView::Maximum);
      ReverbFrame->setEnabled(true);
      ChorusFrame->setEnabled(true);

      if (!FS_DEBUG)
            dumpInfoButton->hide();

      connect(Gain, SIGNAL(valueChanged(int)), SLOT(changeGain(int)));
      connect(dumpInfoButton	, SIGNAL(clicked()), SLOT(dumpInfo()));

      connect(channelTreeWidget, SIGNAL(itemPressed(QTreeWidgetItem*,int)),
         this, SLOT(channelItemClicked(QTreeWidgetItem*,int)));
      connect(sfTreeWidget, SIGNAL(itemPressed(QTreeWidgetItem*,int)),
         this, SLOT(sfItemClicked(QTreeWidgetItem*,int)));

      connect(Reverb, SIGNAL (toggled(bool)), SLOT(toggleReverb(bool)));
      connect(ReverbLevel, SIGNAL (valueChanged (int)), SLOT(changeReverbLevel(int)));
      connect(ReverbRoomSize, SIGNAL (valueChanged (int)), SLOT(changeReverbRoomSize(int)));
      connect(ReverbDamping, SIGNAL (valueChanged (int)), SLOT(changeReverbDamping(int)));
      connect(ReverbWidth, SIGNAL (valueChanged (int)), SLOT(changeReverbWidth(int)));

      connect (Pop, SIGNAL (clicked()), SLOT(popClicked()));
      connect(Chorus, SIGNAL (toggled (bool)), SLOT(toggleChorus (bool)));
      connect(ChorusNumber, SIGNAL (valueChanged (int)), SLOT(changeChorusNumber (int)));
      connect(ChorusType, SIGNAL (activated (int)), SLOT(changeChorusType (int)));
      connect(ChorusSpeed, SIGNAL (valueChanged (int)), SLOT(changeChorusSpeed (int)));
      connect(ChorusDepth, SIGNAL (valueChanged (int)), SLOT(changeChorusDepth (int)));
      connect(ChorusLevel, SIGNAL (valueChanged (int)), SLOT(changeChorusLevel (int)));

      //Clear channels
      for (int i=0; i<FS_MAX_NR_OF_CHANNELS; i++)
            channels[i] = FS_UNSPECIFIED_ID;
      }

FluidSynthGui::~FluidSynthGui()
      {
      /*
      delete _notifier;
      */
      }

void FluidSynthGui::toggleReverb(bool on)         { sendController(0, FS_REVERB_ON, on); }
void FluidSynthGui::changeReverbLevel(int val)    { sendController(0, FS_REVERB_LEVEL, val); }
void FluidSynthGui::changeReverbRoomSize(int val) { sendController(0, FS_REVERB_ROOMSIZE, val); }
void FluidSynthGui::changeReverbWidth(int val)    { sendController(0, FS_REVERB_WIDTH, val); }
void FluidSynthGui::changeReverbDamping(int val)  { sendController(0, FS_REVERB_DAMPING, val); }

void FluidSynthGui::toggleChorus(bool val)        { sendController(0, FS_CHORUS_ON, val); }
void FluidSynthGui::changeChorusNumber(int val)   { sendController(0, FS_CHORUS_NUM, val); }
void FluidSynthGui::changeChorusType(int val)     { sendController(0, FS_CHORUS_TYPE, val); }
void FluidSynthGui::changeChorusSpeed(int val)    { sendController(0, FS_CHORUS_SPEED, val); }
void FluidSynthGui::changeChorusDepth(int val)    { sendController(0, FS_CHORUS_DEPTH, val); }
void FluidSynthGui::changeChorusLevel(int val)    { sendController(0, FS_CHORUS_LEVEL, val); }


void FluidSynthGui::loadClicked()
      {
      QString filename = QFileDialog::getOpenFileName(
         this,
         tr("Choose soundfont"),
         lastdir,
         QString("*.[Ss][Ff]2")
         );
      if (!filename.isEmpty()) {
            QFileInfo fi(filename);
            lastdir = fi.absolutePath();
            sendLastdir(lastdir);
            sendLoadFont(filename);
            pendingFont = filename;
            }
      }

//---------------------------------------------------------
//   sendLastdir
//   Send the last dir-value to the client
//---------------------------------------------------------

void FluidSynthGui::sendLastdir(QString dir)
      {
      int l = dir.size() + 2;
      byte data[l];
      data[0] = FS_LASTDIR_CHANGE;
      memcpy(data+1, dir.toLatin1().data(), dir.size()+1);
      sendSysex(data,l);
      }

//---------------------------------------------------------
//   sendLoadFont
//   Tell the client to load a font with first available id
//---------------------------------------------------------

void FluidSynthGui::sendLoadFont(QString filename)
      {
      int l = filename.length()+3;
      byte data[l];
      data[0] = FS_PUSH_FONT;
      data[1] = FS_UNSPECIFIED_ID;
      memcpy(data+2, filename.toLatin1().data(), filename.length()+1);
      sendSysex(data,l);
      }

//---------------------------------------------------------
//   processEvent
//---------------------------------------------------------

void FluidSynthGui::processEvent(const MidiEvent& ev)
      {
      //Sysexes sent from the client
      if (ev.type() == ME_SYSEX) {
            byte* data = ev.data();
            switch (*data) {
                  case FS_LASTDIR_CHANGE:
                        lastdir = QString((const char*)data+1);
                        break;
                  case FS_ERROR: {
                        char* msg = (char*) (data+1);
                        QMessageBox::critical(this, "Fluidsynth",QString(msg));
                        break;
                        }
                  case FS_SEND_SOUNDFONTDATA: {
                        int chunk_len;
                        int filename_len;

                        int count = (int)*(data+1); //Number of elements
                        byte* cp = data+2; //Point to beginning of first chunk
                        sfTreeWidget->clear(); //Clear the listview
                        stack.clear(); //Clear the stack since we're starting over again

                        while (count) {
                              FluidGuiSoundFont font;
                              filename_len = strlen((const char*)cp) + 1;
                              font.name = (const char*)cp;
                              font.id = *(cp + filename_len);
                              chunk_len = filename_len + FS_SFDATALEN;
                              stack.push_front(font);
                              cp += chunk_len; //Move to next chunk
                              count--;
                              }
                        updateSoundfontTreeWidget();
                        updateChannelTreeWidget();
                        break;
                        }
                  case FS_SEND_CHANNELINFO: {
                        byte* chptr = (data+1);
                        for (int i=0; i< FS_MAX_NR_OF_CHANNELS; i++) {
                              byte id = *chptr;
                              byte channel = *(chptr+1);
                              channels[channel] = id;
                              chptr+=2;
                              }
                        updateChannelTreeWidget();

                        break;
                        }
                  case FS_SEND_DRUMCHANNELINFO: {
                        byte* drumchptr = (data+1);
                        for (int i=0; i<FS_MAX_NR_OF_CHANNELS; i++) {
                              drumchannels[i] = *drumchptr;
                              drumchptr++;
                              }
                        updateChannelTreeWidget();
                        break;
                        }
                  case FS_FONT_SUCCESSFULLY_LOADED: {
                        byte extid = *(data+1);
                        QString fn = (const char*) (data+2);
                        if (FS_DEBUG) {
                              printf("Font successfully loaded: %s, extid: %d\n", fn.toLatin1().data(), extid);
                              }
                        // Try to add last loaded font (if any) to first available channel:
                        if (pendingFont == fn) {
                              if (FS_DEBUG)
                                    printf("Pending font successfully loaded. Add it to first available channel.\n");

                              for (int i=0; i < FS_MAX_NR_OF_CHANNELS; i++) {
                                    if (channels[i] == FS_UNSPECIFIED_ID) {
                                          if (FS_DEBUG)
                                                printf ("sendChannelChange: %d %d\n", extid, i);
                                          sendChannelChange(extid, i);
                                          channels[i] = extid;
                                          updateChannelTreeWidget();
                                          break;
                                          }
                                    }
                              }
                        pendingFont = "";
                        break;
                        }
                  default:
                        if (FS_DEBUG)
                              printf("FluidSynthGui::processEvent() : Unknown Sysex received: %d\n", ev.type());
                        break;
                  }
            }
            //Controllers sent from the client:
      else
            if(ev.type() == ME_CONTROLLER) {
                  int id = ev.dataA();
                  int val = ev.dataB();
                  switch (id) {
                        case FS_GAIN: {
                              bool sb = Gain->signalsBlocked();
                              Gain->blockSignals(true);
                              // Update Gain-slider without causing it to respond to it's own signal (and send another msg to the synth)
                              Gain->setValue(val);
                              Gain->blockSignals(sb);
                              break;
                              }
                        case FS_REVERB_ON: {
                              bool sb = Reverb->signalsBlocked();
                              Reverb->blockSignals(true);
                              Reverb->setChecked(val);
                              Reverb->blockSignals(sb);
                              break;
                              }
                        case FS_REVERB_LEVEL: {
                              bool sb = ReverbLevel->signalsBlocked();
                              ReverbLevel->blockSignals(true);
                              ReverbLevel->setValue(val);
                              ReverbLevel->blockSignals(sb);
                              break;
                              }
                        case FS_REVERB_DAMPING: {
                              bool sb = ReverbDamping->signalsBlocked();
                              ReverbDamping->blockSignals(true);
                              ReverbDamping->setValue(val);
                              ReverbDamping->blockSignals(sb);
                              break;
                              }
                        case FS_REVERB_ROOMSIZE: {
                              bool sb = ReverbRoomSize->signalsBlocked();
                              ReverbRoomSize->blockSignals(true);
                              ReverbRoomSize->setValue(val);
                              ReverbRoomSize->blockSignals(sb);
                              break;
                              }
                        case FS_REVERB_WIDTH: {
                              bool sb = ReverbWidth->signalsBlocked();
                              ReverbWidth->blockSignals(true);
                              ReverbWidth->setValue(val);
                              ReverbWidth->blockSignals(sb);
                              break;
                              }
                        case FS_CHORUS_ON: {
                              Chorus->blockSignals(true);
                              Chorus->setChecked(val);
                              Chorus->blockSignals(false);
                              break;
                              }
                        case FS_CHORUS_SPEED: {
                              ChorusSpeed->blockSignals(true);
                              ChorusSpeed->setValue(val);
                              ChorusSpeed->blockSignals(false);
                              break;
                              }
                        case FS_CHORUS_NUM: {
                              ChorusNumber->blockSignals(true);
                              ChorusNumber->setValue(val);
                              ChorusNumber->blockSignals(false);
                              break;
                              }
                        case FS_CHORUS_TYPE: {
                              ChorusType->blockSignals(true);
                              ChorusType->setCurrentIndex(val);
                              ChorusType->blockSignals(false);
                              break;
                              }
                        case FS_CHORUS_DEPTH: {
                              ChorusDepth->blockSignals(true);
                              ChorusDepth->setValue(val);
                              ChorusDepth->blockSignals(false);
                              break;
                              }
                        case FS_CHORUS_LEVEL: {
                              ChorusLevel->blockSignals(true);
                              ChorusLevel->setValue(val);
                              ChorusLevel->blockSignals(false);
                              break;
                              }
                        default:
                              if (FS_DEBUG)
                                    printf("FluidSynthGui::processEvent() : Unknown controller sent to gui: %x\n",id);
                              break;
                        }
                  }
      else
            if (FS_DEBUG)
                  printf("FluidSynthGui::processEvent - unknown event of type %dreceived from synth.\n", ev.type());
      }

//---------------------------------------------------------
//   readMessage
//---------------------------------------------------------
void FluidSynthGui::readMessage(int)
      {
      MessGui::readMessage();
      }

//---------------------------------------------------------
//   updateChannels
//---------------------------------------------------------
void FluidSynthGui::updateChannelTreeWidget()
      {
      if (FS_DEBUG)
            printf("FluidSynthGui::updateChannelListView\n");
      channelTreeWidget->clear();
      QIcon btndown = QIcon(buttondown_xpm);

      QTreeWidgetItem* header = new QTreeWidgetItem();
      header->setText(FS_CHANNEL_COL, "Channel");
      header->setText(FS_SF_ID_COL, "Soundfont");
      header->setText(FS_DRUM_CHANNEL_COL, "Drum Chnl");
      channelTreeWidget->setHeaderItem(header);

      for (int i=0; i<FS_MAX_NR_OF_CHANNELS; i++) {
            QString chanstr, sfidstr, drumchanstr;

            //Soundfont id string:
            if (channels[i] == FS_UNSPECIFIED_ID)
                  sfidstr = "unspecified";
            else
                  sfidstr = getSoundFontName(channels[i]);
            //Channel string:
            chanstr = QString::number(i+1);
            if (chanstr.length()==1)
                  chanstr = "0" + chanstr;

            //Drumchan string:
            if (drumchannels[i])
                  drumchanstr = "Yes";
            else
                  drumchanstr = "No";
            QTreeWidgetItem* qlvNewItem = new QTreeWidgetItem(channelTreeWidget);
            qlvNewItem->setText(FS_CHANNEL_COL, chanstr);
            qlvNewItem->setIcon(FS_SF_ID_COL, btndown);
            qlvNewItem->setText(FS_SF_ID_COL, sfidstr);
            qlvNewItem->setIcon(FS_DRUM_CHANNEL_COL, btndown);
            qlvNewItem->setText(FS_DRUM_CHANNEL_COL, drumchanstr);
            }
      }

//---------------------------------------------------------
//   updateSoundfontTreeWidget
//---------------------------------------------------------
void FluidSynthGui::updateSoundfontTreeWidget()
      {
      sfTreeWidget->clear(); //Clear the listview

      QTreeWidgetItem* header = new QTreeWidgetItem();
      header->setText(FS_ID_COL, "ID");
      header->setText(FS_SFNAME_COL, "Fontname");
      sfTreeWidget->setHeaderItem(header);

      for (std::list<FluidGuiSoundFont>::iterator it = stack.begin(); it != stack.end(); it++) {
            QTreeWidgetItem* qlvNewItem;

            qlvNewItem = new QTreeWidgetItem(sfTreeWidget);
            QString qsid = QString("%1").arg(it->id);
            qlvNewItem->setText(FS_ID_COL, qsid);
            qlvNewItem->setText(FS_SFNAME_COL, QString(it->name));
            }
      //sfTreeWidget->sort();
      }

//---------------------------------------------------------
//   changeGain
//---------------------------------------------------------
void FluidSynthGui::changeGain(int value)
      {
      sendController(0, FS_GAIN, value);
      }


//---------------------------------------------------------
//   dumpInfoButton
//---------------------------------------------------------
void FluidSynthGui::dumpInfo()
      {
      byte data[1];
      data[0] = FS_DUMP_INFO;
      sendSysex(data, 1);
      }

//---------------------------------------------------------
//   getSoundFontName
//---------------------------------------------------------

QString FluidSynthGui::getSoundFontName(int id)
      {
      QString name = NULL;
      for (std::list<FluidGuiSoundFont>::iterator it = stack.begin(); it != stack.end(); it++) {
            if (id == it->id) {
                  name = it->name;
                  continue;
                  }
            }
      return name;
      }

//---------------------------------------------------------
//   channelItemClicked
// change channel parameters like soundfont / drumchannel on/off
//---------------------------------------------------------

void FluidSynthGui::channelItemClicked(QTreeWidgetItem* item, int col)
      {
      //
      // Soundfont ID column
      //
      if (col == FS_SF_ID_COL) {
            QMenu* popup = new QMenu(this);
            QPoint ppt = channelTreeWidget->visualItemRect(item).bottomLeft();
            QTreeWidget* treeWidget = item->treeWidget();
            ppt += QPoint(treeWidget->header()->sectionPosition(col), treeWidget->header()->height());
            ppt  = treeWidget->mapToGlobal(ppt);

            int i = 0;
            for (std::list<FluidGuiSoundFont>::reverse_iterator it = stack.rbegin(); it != stack.rend(); it++) {
                  i++;
                  QAction* a = popup->addAction(it->name);
                  a->setData(i);
                  }

            int lastindex = i+1;
            QAction* a = popup->addAction("unspecified");
            a->setData(lastindex);
            a = popup->exec(ppt, 0);
            if (a) {
                  int index = a->data().toInt();
                  byte sfid;
                  QString fontname;
                  if (index == lastindex) {
                        sfid = FS_UNSPECIFIED_ID;
                        fontname = "unspecified";	//Actually, it's not possible to reset fluid-channels as for now,
                        } //so this is just a dummy that makes the synth block any events for the channel
                  else {
                        sfid = getSoundFontId(a->text());
                        fontname = getSoundFontName(sfid);
                        }
                  byte channel = atoi(item->text(FS_CHANNEL_COL).toLatin1().data()) - 1;
                  sendChannelChange(sfid, channel);
                  item->setText(FS_SF_ID_COL, fontname);
                  }
            delete popup;
         }
      //
      // Drumchannel column:
      //
      else if (col == FS_DRUM_CHANNEL_COL) {
            QMenu* popup = new QMenu(this);
            QPoint ppt = channelTreeWidget->visualItemRect(item).bottomLeft();
            QTreeWidget* treeWidget = item->treeWidget();
            ppt += QPoint(treeWidget->header()->sectionPosition(col), treeWidget->header()->height());
            ppt  = treeWidget->mapToGlobal(ppt);
            QAction* a = popup->addAction("Yes");
            a->setData(1);
            a = popup->addAction("No");
            a->setData(0);
            byte channel = atoi(item->text(FS_CHANNEL_COL).toLatin1().data()) - 1;

            a = popup->exec(ppt, 0);
            if (a) {
                  int index = a->data().toInt();
                  if (index != drumchannels[channel]) {
                        sendDrumChannelChange(index, channel);
                        drumchannels[channel] = index;
                        item->setText(FS_DRUM_CHANNEL_COL, index == 0 ? "No" : "Yes" );
                        }
                  }
            }
#if 0
      else if (col == FS_DRUM_CHANNEL_COL) {
            Q3PopupMenu* popup = new Q3PopupMenu(this);
            QPoint ppt = channelListView->itemRect(item).bottomLeft();
            Q3ListView* listView = item->listView();
            ppt += QPoint(listView->header()->sectionPos(col), listView->header()->height());
            ppt  = listView->mapToGlobal(ppt);
            popup->insertItem("Yes", 1);
            popup->insertItem("No", 0);
            byte channel = atoi(item->text(FS_CHANNEL_COL).toLatin1().data()) - 1;

            int index = popup->exec(ppt, 0);
            if (index != drumchannels[channel] && index !=-1) {
                  sendDrumChannelChange(index, channel);
                  drumchannels[channel] = index;
                  item->setText(FS_DRUM_CHANNEL_COL, index == 0 ? "No" : "Yes" );
                  }
            }
#endif
      }

//---------------------------------------------------------
//   getSoundFontId
//---------------------------------------------------------

int FluidSynthGui::getSoundFontId(QString q)
      {
      int id = -1;
      for (std::list<FluidGuiSoundFont>::iterator it = stack.begin(); it != stack.end(); it++) {
            if (q == it->name)
                  id = it->id;
            }
      return id;
      }

//---------------------------------------------------------
//   sendChannelChange
// Tell the client to set a soundfont to a specific fluid channel
//---------------------------------------------------------

void FluidSynthGui::sendChannelChange(byte font_id, byte channel)
      {
      byte data[3];
      data[0] = FS_SOUNDFONT_CHANNEL_SET;
      data[1] = font_id;
      data[2] = channel;
      sendSysex(data, 3);
      }

//---------------------------------------------------------
//   sendDrumChannelChange
// Tell the client to set a specific channel to drum channel (equiv to midichan 10)
//---------------------------------------------------------
void FluidSynthGui::sendDrumChannelChange(byte onoff, byte channel)
      {
      byte data[3];
      data[0] = FS_DRUMCHANNEL_SET;
      data[1] = onoff;
      data[2] = channel;
      sendSysex(data, 3);
      if (FS_DEBUG)
            printf("Sent FS_DRUMCHANNEL_SET for channel %d, status: %d\n", channel, onoff);
      }

void FluidSynthGui::popClicked()
      {
      byte data[2];
      data[0] = FS_SOUNDFONT_POP;
      data[1] = currentlySelectedFont;
      sendSysex(data,2);
      }

void FluidSynthGui::sfItemClicked(QTreeWidgetItem* item, int /*column*/)
      {
      if (item != 0) {
            currentlySelectedFont = atoi(item->text(FS_ID_COL).toLatin1().data());
            Pop->setEnabled(true);
            }
      else {
            currentlySelectedFont = -1;
            Pop->setEnabled(false);
            }
      }

#if 0



void FluidSynthGui::readData (int fd)
      {
      unsigned char buffer[512];
      int n = ::read(fd, buffer, 512);
//      dataInput(buffer, n);
      }

#endif