//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: midictrl.cpp,v 1.1.1.1 2003/10/29 10:05:09 wschweer Exp $
//
//  (C) Copyright 2001 Werner Schweer (ws@seh.de)
//=========================================================

#include <stdio.h>

#include "app.h"
#include "midictrl.h"
#include "xml.h"
#include "filedialog.h"
#include "globals.h"

#include <qlistview.h>
#include <qlineedit.h>
#include <qcombobox.h>
#include <qspinbox.h>
#include <qpushbutton.h>
#include <qtoolbutton.h>

MidiControllerList midiControllerList;
MidiControllerEditDialog* midiControllerEditDialog;

static const char* ctrlName[] = {
     "BankSelMSB", "Modulation",  "BreathCtrl",
     "Control 3",  "Foot Ctrl",   "Porta Time",  "DataEntMSB",
     "MainVolume", "Balance",     "Control 9",   "Pan",
     "Expression", "Control 12",  "Control 13",  "Control 14",
     "Control 15", "Gen.Purp.1",  "Gen.Purp.2",  "Gen.Purp.3",
     "Gen.Purp.4", "Control 20",  "Control 21",  "Control 22",
     "Control 23", "Control 24",  "Control 25",  "Control 26",
     "Control 27", "Control 28",  "Control 29",  "Control 30",
     "Control 31", "BankSelLSB",  "Modul. LSB",  "BrthCt.LSB",
     "Control 35", "FootCt.LSB",  "Port.T LSB",  "DataEntLSB",
     "MainVolLSB", "BalanceLSB",  "Control 41",  "Pan LSB",
     "Expr. LSB",  "Control 44",  "Control 45",  "Control 46",
     "Control 47", "Gen.P.1LSB",  "Gen.P.2LSB",  "Gen.P.3LSB",
     "Gen.P.4LSB", "Control 52",  "Control 53",  "Control 54",
     "Control 55", "Control 56",  "Control 57",  "Control 58",
     "Control 59", "Control 60",  "Control 61",  "Control 62",
     "Control 63", "Sustain",     "Porta Ped",   "Sostenuto",
     "Soft Pedal", "Control 68",  "Hold 2",      "Control 70",
     "HarmonicCo", "ReleaseTime", "Attack Time", "Brightness",
     "Control 75", "Control 76",  "Control 77",  "Control 78",
     "Control 79", "Gen.Purp.5",  "Gen.Purp.6",  "Gen.Purp.7",
     "Gen.Purp.8", "Porta Ctrl",  "Control 85",  "Control 86",
     "Control 87", "Control 88",  "Control 89",  "Control 90",
     "Effect1Dep", "Effect2Dep",  "Effect3Dep",  "Effect4Dep",
     "Phaser Dep", "Data Incr",   "Data Decr",   "NRPN LSB",
     "NRPN MSB",   "RPN LSB",     "RPN MSB",     "Control102",
     "Control103", "Control104",  "Control105",  "Control106",
     "Control107", "Control108",  "Control109",  "Control110",
     "Control111", "Control112",  "Control113",  "Control114",
     "Control115", "Control116",  "Control117",  "Control118",
     "Control119", "AllSndOff",   "Reset Ctrl",  "Local Ctrl",
     "AllNoteOff", "OmniModOff",  "OmniModeOn",  "MonoModeOn",
     "PolyModeOn"
      };

static const char* ctrl14Name[] = {
     "BankSel",    "Modulation",  "BreathCtrl",
     "Control 3",  "Foot Ctrl",   "Porta Time",  "DataEntry",
     "MainVolume", "Balance",     "Control 9",   "Pan",
     "Expression", "Control 12",  "Control 13",  "Control 14",
     "Control 15", "Gen.Purp.1",  "Gen.Purp.2",  "Gen.Purp.3",
     "Gen.Purp.4", "Control 20",  "Control 21",  "Control 22",
     "Control 23", "Control 24",  "Control 25",  "Control 26",
     "Control 27", "Control 28",  "Control 29",  "Control 30",
     "Control 31",
     };

enum {
      COL_NAME = 0, COL_TYPE,
      COL_HNUM, COL_LNUM, COL_MIN, COL_MAX
      };

//---------------------------------------------------------
//   midiCtrl7Name
//---------------------------------------------------------

QString midiCtrl7Name(int ctrl)
      {
      return QString(ctrlName[ctrl]);
      }

//---------------------------------------------------------
//   midiCtrl14Name
//---------------------------------------------------------

QString midiCtrl14Name(int h, int l)
      {
      for (iMidiController i = midiControllerList.begin();
         i != midiControllerList.end(); ++i) {
            if ((*i)->type() == MidiController::Controller14
              && (*i)->hnum() == h
              && (*i)->lnum() == l)
                  return (*i)->name();
            }
      return QString("??");
      }

//---------------------------------------------------------
//   writeMidiController
//---------------------------------------------------------

void writeMidiController(int level, Xml& xml)
      {
      for (iMidiController i = midiControllerList.begin();
         i != midiControllerList.end(); ++i) {
            (*i)->write(level, xml);
            }
      }

//---------------------------------------------------------
//   readMidiController
//---------------------------------------------------------

MidiController* readMidiController(Xml& xml)
      {
      MidiController* ctrl = new MidiController();
      ctrl->read(xml);
      return ctrl;
      }

//---------------------------------------------------------
//   MidiController::write
//---------------------------------------------------------

void MidiController::write(int level, Xml& xml) const
      {
      xml.tag(level++, "mctrl");
      xml.strTag(level, "name", _name);
      xml.intTag(level, "type", int(_type));
      xml.intTag(level, "hnum", _hnum);
      xml.intTag(level, "lnum", _lnum);
      xml.intTag(level, "min", _minVal);
      xml.intTag(level, "max", _maxVal);
      xml.etag(level, "mctrl");
      }

//---------------------------------------------------------
//   MidiController::read
//---------------------------------------------------------

void MidiController::read(Xml& xml)
      {
      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        if (tag == "name")
                              _name = xml.parse1();
                        else if (tag == "shortname")
                              // for compatibility
                              xml.parse1();
                        else if (tag == "type")
                              _type = MidiController::ControllerType(xml.parseInt());
                        else if (tag == "hnum")
                              _hnum = xml.parseInt();
                        else if (tag == "lnum")
                              _lnum = xml.parseInt();
                        else if (tag == "min")
                              _minVal = xml.parseInt();
                        else if (tag == "max")
                              _maxVal = xml.parseInt();
                        else
                              xml.unknown("MidiController");
                        break;
                  case Xml::TagEnd:
                        if (tag == "mctrl")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   ctrlType2Int
//   int2ctrlType
//---------------------------------------------------------

static QString ctrlTypes[] = {
      QString("Control7"), QString("Control14"), QString("RPN"),
      QString("NRPN"), QString("XG-SysEx"), QString("SysEx")
      };

//---------------------------------------------------------
//   ctrlType2Int
//---------------------------------------------------------

static MidiController::ControllerType ctrlType2Int(const QString& s)
      {
      int n = sizeof(ctrlTypes)/sizeof(*ctrlTypes);
      for (int i = 0; i < n; ++i) {
            if (ctrlTypes[i] == s)
                  return MidiController::ControllerType(i);
            }
      return MidiController::ControllerType(0);
      }

//---------------------------------------------------------
//   int2ctrlType
//---------------------------------------------------------

static const QString& int2ctrlType(int i)
      {
      return ctrlTypes[i];
      }

//---------------------------------------------------------
//   addControllerToView
//---------------------------------------------------------

void MidiControllerEditDialog::addControllerToView(MidiController* mctrl)
      {
      QString hnum;
      hnum.setNum(mctrl->hnum());
      QString lnum("---");
      QString min;
      min.setNum(mctrl->minVal());
      QString max;
      max.setNum(mctrl->maxVal());
      new QListViewItem(viewController,
               mctrl->name(),
               int2ctrlType(mctrl->type()),
               hnum, lnum, min, max
               );
      }

//---------------------------------------------------------
//   MidiControllerEditDialog
//---------------------------------------------------------

MidiControllerEditDialog::MidiControllerEditDialog(QWidget* parent, const char* name, bool modal, WFlags fl)
   : MidiControllerEditDialogBase(parent, name, modal, fl)
      {
      viewController->setAllColumnsShowFocus(true);
      viewController->setColumnAlignment(COL_HNUM, AlignCenter);
      viewController->setColumnAlignment(COL_LNUM, AlignCenter);
      viewController->setColumnAlignment(COL_MIN,  AlignCenter);
      viewController->setColumnAlignment(COL_MAX,  AlignCenter);
      viewController->setColumnWidthMode(COL_NAME, QListView::Maximum);

      reject();   // populate list
      viewController->setCurrentItem(viewController->firstChild());

      connect(buttonNew,    SIGNAL(clicked()), SLOT(ctrlNew()));
      connect(buttonDelete, SIGNAL(clicked()), SLOT(ctrlDelete()));
      connect(entryName,    SIGNAL(textChanged(const QString&)), SLOT(nameChanged(const QString&)));
      connect(comboType,    SIGNAL(activated(const QString&)), SLOT(typeChanged(const QString&)));
      connect(spinboxHCtrlNo, SIGNAL(valueChanged(int)), SLOT(valueHChanged(int)));
      connect(spinboxLCtrlNo, SIGNAL(valueChanged(int)), SLOT(valueLChanged(int)));
      connect(spinboxMin, SIGNAL(valueChanged(int)), SLOT(minChanged(int)));
      connect(spinboxMax, SIGNAL(valueChanged(int)), SLOT(maxChanged(int)));
      connect(viewController, SIGNAL(selectionChanged(QListViewItem*)),
         SLOT(controllerChanged(QListViewItem*)));

      connect(saveAsButton, SIGNAL(clicked()), SLOT(saveAs()));
      connect(replaceButton, SIGNAL(clicked()), SLOT(replace()));
      connect(mergeButton, SIGNAL(clicked()), SLOT(merge()));
      controllerChanged(viewController->currentItem());
      }

//---------------------------------------------------------
//   reject
//---------------------------------------------------------

void MidiControllerEditDialog::reject()
      {
      viewController->clear();
      for (iMidiController i = midiControllerList.begin();
         i != midiControllerList.end(); ++i) {
            addControllerToView(*i);
            }
      MidiControllerEditDialogBase::reject();
      }

//---------------------------------------------------------
//   ctrlNew
//---------------------------------------------------------

void MidiControllerEditDialog::ctrlNew()
      {
      QListViewItem* item = new QListViewItem(viewController,
         QString(ctrlName[1]), int2ctrlType(0), QString("1"), QString("---"), QString("0"), QString("127"));
      viewController->setCurrentItem(item);
      controllerChanged(item);
      }

//---------------------------------------------------------
//   ctrlDelete
//---------------------------------------------------------

void MidiControllerEditDialog::ctrlDelete()
      {
      QListViewItem* item = viewController->currentItem();
      if (item == 0)
            return;
      delete item;
      }

//---------------------------------------------------------
//   accept
//---------------------------------------------------------

void MidiControllerEditDialog::accept()
      {
      for (iMidiController i = midiControllerList.begin();
         i != midiControllerList.end(); ++i) {
            delete *i;
            }
      midiControllerList.clear();
      QListViewItem* item = viewController->firstChild();
      while (item) {
            MidiController* c = new MidiController(
                  ctrlType2Int(item->text(COL_TYPE)),
                  item->text(COL_NAME)
                  );
            c->setHNum(item->text(COL_HNUM).toInt());
            c->setLNum(item->text(COL_LNUM).toInt());
            c->setMinVal(item->text(COL_MIN).toInt());
            c->setMaxVal(item->text(COL_MAX).toInt());
            midiControllerList.push_back(c);
            item = item->nextSibling();
            }
      MidiControllerEditDialogBase::accept();
      }

//---------------------------------------------------------
//   nameChanged
//---------------------------------------------------------

void MidiControllerEditDialog::nameChanged(const QString& s)
      {
      QListViewItem* item = viewController->currentItem();
      if (item == 0)
            return;
      item->setText(COL_NAME, s);
      }

//---------------------------------------------------------
//   typeChanged
//---------------------------------------------------------

void MidiControllerEditDialog::typeChanged(const QString& s)
      {
      QListViewItem* item = viewController->currentItem();
      if (item == 0)
            return;
      item->setText(COL_TYPE, s);
      switch(ctrlType2Int(s)) {
            case 0:     // Control7
            case 1:     // Control14
                  item->setText(COL_MIN, QString("0"));
                  item->setText(COL_MAX, QString("127"));
                  break;
            case 2:     // RPN
            case 3:     // NRPN
                  break;
            case 4:     // XG-SysEx
            case 5:     // SysEx
            default:
                  break;
            }
      controllerChanged(item);
      }

//---------------------------------------------------------
//   valueHChanged
//---------------------------------------------------------

void MidiControllerEditDialog::valueHChanged(int val)
      {
      QListViewItem* item = viewController->currentItem();
      if (item == 0)
            return;
      QString s;
      s.setNum(val);
      item->setText(COL_HNUM, s);
      if (item->text(COL_TYPE) == "Control7") {
            item->setText(COL_NAME, QString(ctrlName[val]));
            entryName->setText(QString(ctrlName[val]));
            }
      if (item->text(COL_TYPE) == "Control14") {
            if (val < 32) {
                  item->setText(COL_NAME, QString(ctrl14Name[val]));
                  entryName->setText(QString(ctrl14Name[val]));
                  spinboxLCtrlNo->setValue(val + 32);
                  QString lnum;
                  lnum.setNum(val + 32);
                  item->setText(COL_LNUM, lnum);
                  }
            }
      }

//---------------------------------------------------------
//   valueLChanged
//---------------------------------------------------------

void MidiControllerEditDialog::valueLChanged(int val)
      {
      QListViewItem* item = viewController->currentItem();
      if (item == 0)
            return;
      QString s;
      s.setNum(val);
      item->setText(COL_LNUM, s);
      }

//---------------------------------------------------------
//   controllerChanged
//---------------------------------------------------------

void MidiControllerEditDialog::controllerChanged(QListViewItem* item)
      {
      if (item == 0) {
            entryName->setText(QString(""));
            comboType->setCurrentItem(0);
            spinboxHCtrlNo->setValue(1);
            spinboxLCtrlNo->setValue(33);
            spinboxMin->setValue(0);
            spinboxMax->setValue(127);
            spinboxLCtrlNo->setEnabled(false);
            return;
            }
      entryName->setText(item->text(COL_NAME));
      comboType->setCurrentItem(int(ctrlType2Int(item->text(COL_TYPE))));

      switch (ctrlType2Int(item->text(COL_TYPE))) {
            case 0:     // Control7
                  spinboxHCtrlNo->setValue(item->text(COL_HNUM).toInt());
                  spinboxLCtrlNo->setEnabled(false);
                  spinboxMin->setValue(item->text(COL_MIN).toInt());
                  spinboxMax->setValue(item->text(COL_MAX).toInt());
                  item->setText(COL_LNUM, QString("---"));
                  break;
            case 1:     // Control14
            case 2:     // RPN
            case 3:     // NRPN
                  spinboxHCtrlNo->setValue(item->text(COL_HNUM).toInt());
                  spinboxLCtrlNo->setValue(item->text(COL_LNUM).toInt());
                  spinboxLCtrlNo->setEnabled(true);
                  spinboxMin->setValue(item->text(COL_MIN).toInt());
                  spinboxMax->setValue(item->text(COL_MAX).toInt());
                  break;
            case 4:     // XG-SysEx
            case 5:     // SysEx
            default:
                  break;
            }
      }

//---------------------------------------------------------
//   minChanged
//---------------------------------------------------------

void MidiControllerEditDialog::minChanged(int val)
      {
      QListViewItem* item = viewController->currentItem();
      if (item == 0)
            return;
      QString s;
      s.setNum(val);
      item->setText(COL_MIN, s);
      }

//---------------------------------------------------------
//   maxChanged
//---------------------------------------------------------

void MidiControllerEditDialog::maxChanged(int val)
      {
      QListViewItem* item = viewController->currentItem();
      if (item == 0)
            return;
      QString s;
      s.setNum(val);
      item->setText(COL_MAX, s);
      }

//---------------------------------------------------------
//   configMidiController
//---------------------------------------------------------

void configMidiController()
      {
      if (midiControllerEditDialog == 0)
            midiControllerEditDialog = new MidiControllerEditDialog();
      midiControllerEditDialog->show();
      }

//---------------------------------------------------------
//   saveAs
//---------------------------------------------------------

void MidiControllerEditDialog::saveAs()
      {
      MFile file(QString("controller/"), QString(".ctrl"));
      FILE* f = file.open("w", ctrl_file_pattern, this, false, true,
         tr("MusE: save midi controller list"));
      if (f == 0) {
            return;
            }
      Xml xml(f);
      xml.header();
      xml.tag(0, "muse version=\"1.0\"");

      QListViewItem* item = viewController->firstChild();
      while (item) {
            MidiController* c = new MidiController(
                  ctrlType2Int(item->text(COL_TYPE)),
                  item->text(COL_NAME)
                  );
            c->setHNum(item->text(COL_HNUM).toInt());
            c->setLNum(item->text(COL_LNUM).toInt());
            c->setMinVal(item->text(COL_MIN).toInt());
            c->setMaxVal(item->text(COL_MAX).toInt());
            c->write(1, xml);
            delete c;
            item = item->nextSibling();
            }
      xml.tag(1, "/muse");
      }

void MidiControllerEditDialog::merge()
      {
      mergeReplace(false);
      }

void MidiControllerEditDialog::replace()
      {
      mergeReplace(true);
      }

//---------------------------------------------------------
//   mergeReplace
//---------------------------------------------------------

void MidiControllerEditDialog::mergeReplace(bool replace)
      {
      MFile file(QString("controller/"), QString(".ctrl"));
      FILE* f = file.open("r", ctrl_file_pattern, this, false, true,
         tr("MusE: load midi controller list"));
      if (f == 0)
            return;
      if (replace)
            viewController->clear();
      Xml xml(f);
      int mode = 0;
      for (;;) {
            Xml::Token token = xml.parse();
            QString tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        if (mode == 0 && tag == "muse")
                              mode = 1;
                        else if (mode == 1 && tag == "mctrl") {
                              MidiController* mctrl = readMidiController(xml);
                              addControllerToView(mctrl);
                              }
                        else
                              xml.unknown("PluginGui");
                        break;
                  case Xml::Attribut:
                        break;
                  case Xml::TagEnd:
                        if (mode && tag == "muse")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   addMidiController
//---------------------------------------------------------

void addMidiController(MidiController::ControllerType type, int hval, int lval)
      {
      hval &= 0x7f;
      lval &= 0x7f;

//      printf("addMidiController %d %d\n", hval, lval);

      // check if controller is already defined:
      for (iMidiController i = midiControllerList.begin();
         i != midiControllerList.end(); ++i) {
            MidiController* ctrl = *i;
            if ((ctrl->type() == type) && (ctrl->hnum() == hval)) {
                  if (type == MidiController::Controller14 && ctrl->lnum() != lval)
                        break;
                  return;
                  }
            }

      QString name;
      switch(type) {
            case MidiController::Controller7:
                  name = ctrlName[hval];
                  break;
            case MidiController::Controller14:
                  name = ctrl14Name[hval];
                  break;
            default:
                  name = "??";
                  break;
            }

      MidiController* c = new MidiController(type, name);

      c->setHNum(hval);
      c->setLNum(lval);

      // Todo: set values according to type:
      c->setMinVal(0);
      c->setMaxVal(127);

      midiControllerList.push_back(c);
      }

