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

#include <qpopupmenu.h>
#include <qdir.h>
#include <qfileinfo.h>

#include "minstrument.h"
#include "midiport.h"
#include "globals.h"
#include "xml.h"
#include "event.h"
#include "mpevent.h"

#include <stdio.h>

MidiInstrumentList midiInstruments;
MidiInstrument* genericMidiInstrument;

static char* gmdrumname = "GM-drums";

//---------------------------------------------------------
//   readEventList
//---------------------------------------------------------

static void readEventList(Xml& xml, EventList* el, const char* name)
      {
      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 == "event") {
                              Event* e = new MidiEvent();
                              e->read(xml);
                              el->add(e);
                              }
                        else
                              xml.unknown("readEventList");
                        break;
                  case Xml::TagEnd:
                        if (tag == name)
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   read
//---------------------------------------------------------

static void readIDF(Xml& xml)
      {
      bool skipmode = true;
      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        if (skipmode && tag == "muse")
                              skipmode = false;
                        else if (skipmode)
                              break;
                        else if (tag == "MidiInstrument") {
                              MidiIDFInstrument* i = new MidiIDFInstrument();
                              i->read(xml);
                              midiInstruments.push_back(i);
                              }
                        else
                              xml.unknown("muse");
                        break;
                  case Xml::Attribut:
                        break;
                  case Xml::TagEnd:
                        if (!skipmode && tag == "muse") {
                              return;
                              }
                  default:
                        break;
                  }
            }
      }


//---------------------------------------------------------
//   loadIDF
//---------------------------------------------------------

static void loadIDF(QFileInfo* fi)
      {
      FILE* f = fopen(fi->filePath().ascii(), "r");
      if (f == 0)
            return;
      if (debugMsg)
            printf("READ IDF %s\n", fi->filePath().latin1());
      Xml xml(f);
      readIDF(xml);
      fclose(f);
      }

//---------------------------------------------------------
//   initMidiInstruments
//---------------------------------------------------------

void initMidiInstruments()
      {
      genericMidiInstrument = new MidiInstrument(QWidget::tr("generic midi"));
      midiInstruments.push_back(genericMidiInstrument);
      QString museGlobalInstruments(museGlobalShare
         + QString("/instruments"));
      if (debugMsg)
            printf("load instrument definitions from <%s>\n", museGlobalInstruments.latin1());
      QDir instrumentsDir(museGlobalInstruments, QString("*.idf"), QDir::Files);
      if (instrumentsDir.exists()) {
            const QFileInfoList* list = instrumentsDir.entryInfoList();
            QFileInfoListIterator it(*list);
            QFileInfo* fi;
            while((fi = it.current())) {
                  loadIDF(fi);
                  ++it;
                  }
            }
      }

//---------------------------------------------------------
//   registerMidiInstrument
//---------------------------------------------------------

MidiInstrument* registerMidiInstrument(const QString& name)
      {
      for (iMidiInstrument i = midiInstruments.begin();
         i != midiInstruments.end(); ++i) {
            if ((*i)->iname() == name)
                  return *i;
            }
      return genericMidiInstrument;
      }

//---------------------------------------------------------
//   removeMidiInstrument
//---------------------------------------------------------

void removeMidiInstrument(const QString& name)
      {
      for (iMidiInstrument i = midiInstruments.begin();
         i != midiInstruments.end(); ++i) {
            printf("remove Instrument %s\n", name.latin1());
            midiInstruments.erase(i);
            return;
            }
      }

//---------------------------------------------------------
//   MidiInstrument
//---------------------------------------------------------

MidiInstrument::MidiInstrument()
      {
      _midiInit  = new EventList();
      _midiReset = new EventList();
      _midiState = new EventList();
      }

//---------------------------------------------------------
//   MidiInstrument
//---------------------------------------------------------

MidiInstrument::MidiInstrument(const QString& txt)
      {
      _name      = txt;
      _midiInit  = new EventList();
      _midiReset = new EventList();
      _midiState = new EventList();
      }

//---------------------------------------------------------
//   MidiInstrument
//---------------------------------------------------------

MidiInstrument::~MidiInstrument()
      {
      delete _midiInit;
      delete _midiReset;
      delete _midiState;
      }

//---------------------------------------------------------
//   reset
//    send note off to all channels
//---------------------------------------------------------

void MidiInstrument::reset(int portNo, MType)
      {
      MidiPlayEvent ev;
      ev.setType(0x90);
      MidiPort* port = &midiPorts[portNo];
      if (port == 0)
            return;
      ev.setPort(portNo);
      for (int chan = 0; chan < MIDI_CHANNELS; ++chan) {
            ev.setChannel(chan);
            for (int pitch = 0; pitch < 128; ++pitch) {
                  ev.setA(pitch);
                  ev.setB(0);
                  port->putEvent(&ev);
                  }
            }
      }

//---------------------------------------------------------
//   getPatchName
//---------------------------------------------------------

const char* MidiInstrument::getPatchName(int, int, int, int, MType)
      {
      return "???";
      }

//---------------------------------------------------------
//   readPatchGroup
//---------------------------------------------------------

void PatchGroup::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 == "Patch") {
                              Patch patch;
                              patch.read(xml);
                              patches.push_back(patch);
                              }
                        else
                              xml.unknown("PatchGroup");
                        break;
                  case Xml::Attribut:
                        if (tag == "name")
                              name = xml.s2();
                        break;
                  case Xml::TagEnd:
                        if (tag == "PatchGroup")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   read
//---------------------------------------------------------

void Patch::read(Xml& xml)
      {
      typ   = -1;
      hbank = -1;
      lbank = -1;
      prog  = 0;
      drum  = false;
      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        xml.unknown("Patch");
                        break;
                  case Xml::Attribut:
                        if (tag == "name")
                              name = xml.s2();
                        else if (tag == "mode")
                              typ = xml.s2().toInt();
                        else if (tag == "hbank")
                              hbank = xml.s2().toInt();
                        else if (tag == "lbank")
                              lbank = xml.s2().toInt();
                        else if (tag == "prog")
                              prog = xml.s2().toInt();
                        else if (tag == "drum")
                              drum = xml.s2().toInt();
                        break;
                  case Xml::TagEnd:
                        if (tag == "Patch")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readMidiState
//---------------------------------------------------------

void MidiInstrument::readMidiState(Xml& xml)
      {
      _midiState->read(xml, "midistate");
      }

//---------------------------------------------------------
//   read
//---------------------------------------------------------

void MidiIDFInstrument::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 == "Patch") {
                              Patch patch;
                              patch.read(xml);
                              if (pg.empty()) {
                                    PatchGroup p;
                                    p.patches.push_back(patch);
                                    pg.push_back(p);
                                    }
                              else
                                    pg[0].patches.push_back(patch);
                              }
                        else if (tag == "PatchGroup") {
                              PatchGroup p;
                              p.read(xml);
                              pg.push_back(p);
                              }
                        else if (tag == "Init")
                              readEventList(xml, _midiInit, "Init");
                        else if (tag == "Reset")
                              readEventList(xml, _midiReset, "Reset");
                        else if (tag == "State")
                              readEventList(xml, _midiState, "State");
                        else
                              xml.unknown("MidiInstrument");
                        break;
                  case Xml::Attribut:
                        if (tag == "name")
                              setIName(xml.s2());
                        break;
                  case Xml::TagEnd:
                        if (tag == "MidiInstrument")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   reset
//---------------------------------------------------------

void MidiIDFInstrument::reset(int port, MType type)
      {
      }

//---------------------------------------------------------
//   getPatchName
//---------------------------------------------------------

const char* MidiIDFInstrument::getPatchName(int channel, int hbank, int lbank, int prog, MType mode)
      {
      if (prog < 0)
            return "---";
      if (lbank < 0)
            lbank = 0;
      if (hbank < 0)
            hbank = 0;

      int tmask;
      int drum = channel == 9;
      bool hb = false;
      bool lb = false;
      switch (mode) {
            case MT_GS:
                  tmask = 2;
                  hb    = true;
                  break;
            case MT_XG:
                  hb    = true;
                  lb    = true;
                  tmask = 4;
                  break;
            case MT_GM:
                  if (channel == 9)
                        return gmdrumname;
                  tmask = 1;
                  break;
            default:
                  hb    = true;     // MSB bank matters
                  lb    = true;     // LSB bank matters
                  break;
            }
      for (std::vector<PatchGroup>::iterator i = pg.begin(); i != pg.end(); ++i) {
            PatchList& pl = i->patches;
            for (PatchList::const_iterator ipl = pl.begin(); ipl != pl.end(); ++ipl) {
                  const Patch& mp = *ipl;
                  if ((mp.typ & tmask)
                    && (prog == mp.prog)
                    && (mp.drum == drum)
                    && (hbank == mp.hbank || !hb || mp.hbank == -1)
                    && (lbank == mp.lbank || !lb || mp.lbank == -1))
                        return mp.name.latin1();
                  }
            }
      return "---";
      }

//---------------------------------------------------------
//   populatePatchPopup
//---------------------------------------------------------

void MidiIDFInstrument::populatePatchPopup(QPopupMenu* menu, int, MType songType)
      {
      menu->clear();
      int mask = 0;
      switch (songType) {
            case MT_XG: mask = 4; break;
            case MT_GS: mask = 2; break;
            case MT_GM: mask = 1; break;
            case MT_UNKNOWN:  mask = 7; break;
            }
      if (pg.size() > 1) {
            for (std::vector<PatchGroup>::iterator i = pg.begin(); i != pg.end(); ++i) {
                  QPopupMenu* pm = new QPopupMenu(menu);
                  pm->setCheckable(false);
                  pm->setFont(font0);
                  PatchList& pl = i->patches;
                  for (PatchList::const_iterator ipl = pl.begin(); ipl != pl.end(); ++ipl) {
                        const Patch& mp = *ipl;
                        if (mp.typ & mask) {
                              int id = ((mp.hbank & 0xff) << 16)
                                         + ((mp.lbank & 0xff) << 8) + (mp.prog & 0xff);
                              pm->insertItem(mp.name, id);
                              }
                        }
                  menu->insertItem(i->name, pm);
                  }
            }
      else {
            // no groups
            PatchList& pl = pg.front().patches;
            for (PatchList::const_iterator ipl = pl.begin(); ipl != pl.end(); ++ipl) {
                  const Patch& mp = *ipl;
                  if (mp.typ & mask) {
                        int id = ((mp.hbank & 0xff) << 16)
                                 + ((mp.lbank & 0xff) << 8) + (mp.prog & 0xff);
                        menu->insertItem(mp.name, id);
                        }
                  }
            }
      }

