//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: importmidi.cpp,v 1.26.2.2 2005/01/10 19:03:15 spamatica Exp $
//
//  (C) Copyright 1999-2003 Werner Schweer (ws@seh.de)
//=========================================================

#include <assert.h>
#include <errno.h>
#include <values.h>
#include <qmessagebox.h>

#include "app.h"
#include "song.h"
#include "globals.h"
#include "filedialog.h"
#include "midi.h"
#include "midifile.h"
#include "midiport.h"
#include "transport.h"
#include "arranger.h"
#include "mpevent.h"
#include "event.h"
#include "midictrl.h"
#include "instruments/minstrument.h"
#include "drummap.h"

//---------------------------------------------------------
//   importMidi
//---------------------------------------------------------

void MusE::importMidi()
      {
      QString empty("");
      importMidi(empty);
      }

void MusE::importMidi(const QString &file)
      {
      QString fn;
      if (file.isEmpty()) {
            fn = getOpenFileName(lastMidiPath, midi_file_pattern, this,
               tr("MusE: Import Midi"), 0);
            if (fn.isEmpty())
                  return;
            lastMidiPath = fn;
            }
      else
            fn = file;

      int n = QMessageBox::question(this, appName,
         tr("Add midi file to current project?\n"),
         tr("&Add to Project"),
         tr("&Replace"),
         tr("&Abort"), 0, 2);

      switch (n) {
            case 0:
                  importMidi(fn, true);
                  song->update();
                  break;
            case 1:
                  loadProjectFile(fn, false, false);    // replace
                  break;
            default:
                  return;
            }
      }

//---------------------------------------------------------
//   importMidi
//    return true on error
//---------------------------------------------------------

bool MusE::importMidi(const QString name, bool merge)
      {
      bool popenFlag;
      FILE* fp = fileOpen(this, name, QString(".mid"), "r", popenFlag);
      if (fp == 0)
            return true;
      MidiFile mf(fp);
      bool rv = mf.read();
      popenFlag ? pclose(fp) : fclose(fp);
      if (rv) {
            QString s(tr("reading midifile\n  "));
            s += name;
            s += tr("\nfailed: ");
            s += mf.error();
            QMessageBox::critical(this, QString("MusE"), s);
            return rv;
            }
      //
      //  evaluate song Type (GM, XG, GS, unknown)
      //
      MType t = song->mtype();
      if (!merge) {
            t = mf.mtype();
            song->setMType(t);
            }
      MidiInstrument* instr = 0;
      for (iMidiInstrument i = midiInstruments.begin(); i != midiInstruments.end(); ++i) {
            MidiInstrument* mi = *i;
            if ((mi->iname() == "GM" && ((t == MT_UNKNOWN) || (t == MT_GM)))
               || ((mi->iname() == "GS") && (t == MT_GS))
               || ((mi->iname() == "XG") && (t == MT_XG))) {
                  instr = mi;
                  break;
                  }
            }
      if (instr == 0) {
            // the standard instrument files (GM, GS, XG) must be present
            printf("no instrument, type %d\n", t);
            abort();
            }

      MidiFileTrackList* etl = mf.trackList();
      int division     = mf.division();

      //
      // create MidiTrack and copy events to ->events()
      //    - combine note on/off events
      //    - calculate tick value for internal resolution
      //
      for (iMidiFileTrack t = etl->begin(); t != etl->end(); ++t) {
            MPEventList* el  = &((*t)->events);
            if (el->empty())
                  continue;
            //
            // if we split the track, SYSEX and META events go into
            // the first target track

            bool first = true;
            // somewhat silly and slooow:
            for (int port = 0; port < MIDI_PORTS; ++port) {
                  for (int channel = 0; channel < MIDI_CHANNELS; ++channel) {
                        //
                        // check if there are any events for port/channel in track:
                        //
                        iMPEvent i;
                        for (i = el->begin(); i != el->end(); ++i) {
                              MidiPlayEvent ev = *i;
                              if (ev.type() != ME_SYSEX && ev.type() != ME_META
                                 && ev.channel() == channel && ev.port() == port)
                                    break;
                              }
                        if (i == el->end())
                              continue;
                        MidiTrack* track = new MidiTrack();
                        if ((*t)->isDrumTrack)
                              track->setType(Track::DRUM);
                        track->setOutChannel(channel);
                        track->setOutPort(port);

                        MidiPort* mport = &midiPorts[track->outPort()];
                        // this overwrites any instrument set for this port:
                        mport->setInstrument(instr);

                        EventList* mel = track->events();
                        buildMidiEventList(mel, el, track, division, first);
                        first = false;

                        for (iEvent i = mel->begin(); i != mel->end(); ++i) {
                              Event event = i->second;
                              if (event.type() == Controller) {
                                    importController(channel, mport, event.dataA());
                                    midiPorts[track->outPort()].setCtrl(channel, event.tick(), event.dataA(), event.dataB());
                                    }
                              }

                        if (channel == 9 && song->mtype() != MT_UNKNOWN) {
                              track->setType(Track::DRUM);
                              //
                              // remap drum pitch with drumInmap
                              //
                              EventList* tevents = track->events();
                              for (iEvent i = tevents->begin(); i != tevents->end(); ++i) {
                                    Event ev  = i->second;
                                    if (ev.isNote()) {
                                          int pitch = drumInmap[ev.pitch()];
                                          ev.setPitch(pitch);
                                          }
                                    }
                              }
                        processTrack(track);
                        song->insertTrack0(track, -1);
                        }
                  }
            if (first) {
                  //
                  // track does only contain non-channel messages
                  // (SYSEX or META)
                  //
                  MidiTrack* track = new MidiTrack();
                  track->setOutChannel(0);
                  track->setOutPort(0);
                  EventList* mel = track->events();
                  buildMidiEventList(mel, el, track, division, true);
                  processTrack(track);
                  song->insertTrack0(track, -1);
                  }
            }

      if (!merge) {
            TrackList* tl = song->tracks();
            if (!tl->empty()) {
                  Track* track = tl->front();
                  track->setSelected(true);
                  }
            song->initLen();

            int z, n;
            sigmap.timesig(0, z, n);

            int tempo = tempomap.tempo(0);
            transport->setTimesig(z, n);
            transport->setTempo(tempo);

            bool masterF = !tempomap.empty();
            song->setMasterFlag(masterF);
            transport->setMasterFlag(masterF);

            song->updatePos();

            arranger->reset();
            arranger->setMode(int(song->mtype()));
            }
      else {
            song->initLen();
           }

      return false;
      }

//---------------------------------------------------------
//   processTrack
//    divide events into parts
//---------------------------------------------------------

void MusE::processTrack(MidiTrack* track)
      {
      EventList* tevents = track->events();
      if (tevents->empty())
            return;

      //---------------------------------------------------
      //    Parts ermitteln
      //    die Midi-Spuren werden in Parts aufgebrochen;
      //    ein neuer Part wird bei einer Lücke von einem
      //    Takt gebildet; die Länge wird jeweils auf
      //    Takte aufgerundet und aligned
      //---------------------------------------------------

      PartList* pl = track->parts();

      int lastTick = 0;
      for (iEvent i = tevents->begin(); i != tevents->end(); ++i) {
            Event event = i->second;
            int epos = event.tick() + event.lenTick();
            if (epos > lastTick)
                  lastTick = epos;
            }

      int len = song->roundUpBar(lastTick+1);
      int bar2, beat;
      unsigned tick;
      sigmap.tickValues(len, &bar2, &beat, &tick);

      QString partname = track->name();

      int lastOff = 0;
      int st = -1;      // start tick current part
      int x1 = 0;       // start tick current measure
      int x2 = 0;       // end tick current measure

      for (int bar = 0; bar < bar2; ++bar, x1 = x2) {
            x2 = sigmap.bar2tick(bar+1, 0, 0);
            if (lastOff > x2) {
                  // this measure is busy!
                  continue;
                  }
            iEvent i1 = tevents->lower_bound(x1);
            iEvent i2 = tevents->lower_bound(x2);

            if (i1 == i2) {   // empty?
                  if (st != -1) {
                        MidiPart* part = new MidiPart(track);
                        part->setTick(st);
                        part->setLenTick(x1-st);
// printf("new part %d len: %d\n", st, x1-st);
                        part->setName(partname);
                        pl->add(part);
                        st = -1;
                        }
                  }
            else {
                  if (st == -1)
                        st = x1;    // begin new  part
                  //HACK:
                  //lastOff:
                  for (iEvent i = i1; i != i2; ++i) {
                        Event event = i->second;
                        if (event.type() == Note) {
                              int off = event.tick() + event.lenTick();
                              if (off > lastOff)
                                    lastOff = off;
                              }
                        }
                  }
            }
      if (st != -1) {
            MidiPart* part = new MidiPart(track);
            part->setTick(st);
// printf("new part %d len: %d\n", st, x2-st);
            part->setLenTick(x2-st);
            part->setName(partname);
            pl->add(part);
            }

      //-------------------------------------------------------------
      //    assign events to parts
      //-------------------------------------------------------------

      for (iPart p = pl->begin(); p != pl->end(); ++p) {
            MidiPart* part = (MidiPart*)(p->second);
            int stick = part->tick();
            int etick = part->tick() + part->lenTick();
            iEvent r1 = tevents->lower_bound(stick);
            iEvent r2 = tevents->lower_bound(etick);
            int startTick = part->tick();

            EventList* el = part->events();
            for (iEvent i = r1; i != r2; ++i) {
                  Event ev = i->second;
                  int ntick = ev.tick() - startTick;
                  ev.setTick(ntick);
                  el->add(ev, ntick);
                  }
            tevents->erase(r1, r2);
            }

      if (tevents->size())
            printf("-----------events left: %d\n", tevents->size());
      for (iEvent i = tevents->begin(); i != tevents->end(); ++i) {
            printf("%d===\n", i->first);
            i->second.dump();
            }
      // all events should be processed:
      assert(tevents->empty());
      }

//---------------------------------------------------------
//   importController
//---------------------------------------------------------

void MusE::importController(int channel, MidiPort* mport, int n)
      {
      MidiInstrument* instr = mport->instrument();
      MidiCtrlValListList* vll = mport->controller();
      iMidiCtrlValList i = vll->find(channel, n);
      if (i != vll->end())
            return;           // controller does already exist
      MidiController* ctrl = 0;
      MidiControllerList* mcl = instr->controller();
// printf("import Ctrl\n");
      for (iMidiController i = mcl->begin(); i != mcl->end(); ++i) {
            int cn = (*i)->num();
// printf("   %x %x\n", n, cn);
            if (cn == n) {
                  ctrl = *i;
                  break;
                  }
            // wildcard?
            if (((cn & 0xff) == 0xff) && ((cn & ~0xff) == (n & ~0xff))) {
                  ctrl = *i;
                  break;
                  }
            }
      if (ctrl == 0) {
            printf("controller 0x%x not defined for instrument %s, channel %d\n",
               n, instr->iname().latin1(), channel);
// TODO: register default Controller
//      MidiController* MidiPort::midiController(int num) const
            }
      MidiCtrlValList* newValList = new MidiCtrlValList(n);
      vll->add(channel, newValList);
      }
