//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: songfile.cpp,v 1.25 2004/06/28 21:13:16 wschweer Exp $
//
//  (C) Copyright 1999/2000 Werner Schweer (ws@seh.de)
//=========================================================

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

#include "app.h"
#include "song.h"
#include "arranger.h"
#include "transport.h"
#include "cobject.h"
#include "drumedit.h"
#include "pianoroll.h"
#include "globals.h"
#include "xml.h"
#include "drummap.h"
#include "event.h"
#include "marker/marker.h"
#include "midiport.h"
#include "audio.h"
#include "mitplugin.h"
#include "wave.h"
#include "midictrl.h"
#include "amixer.h"
#include "conf.h"

struct ClonePart {
      const EventList* el;
      int id;
      ClonePart(const EventList* e, int i) : el(e), id(i) {}
      };

typedef std::list<ClonePart> CloneList;
typedef CloneList::iterator iClone;

static CloneList cloneList;

//---------------------------------------------------------
//   NKey::write
//---------------------------------------------------------

void NKey::write(int level, Xml& xml) const
      {
      xml.intTag(level, "key", val);
      }

//---------------------------------------------------------
//   NKey::read
//---------------------------------------------------------

void NKey::read(Xml& xml)
      {
      for (;;) {
            Xml::Token token = xml.parse();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::Text:
                        val = xml.s1().toInt();
                        break;
                  case Xml::TagEnd:
                        if (xml.s1() == "key")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   Scale::write
//---------------------------------------------------------

void Scale::write(int level, Xml& xml) const
      {
      xml.intTag(level, "scale", val);
      }

//---------------------------------------------------------
//   Scale::read
//---------------------------------------------------------

void Scale::read(Xml& xml)
      {
      for (;;) {
            Xml::Token token = xml.parse();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::Text:
                        val = xml.s1().toInt();
                        break;
                  case Xml::TagEnd:
                        if (xml.s1() == "scale")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   Part::write
//---------------------------------------------------------

void Part::write(int level, Xml& xml) const
      {
      const EventList* el = cevents();
      int id              = -1;
      bool dumpEvents     = true;

      if (el->arefCount() > 1) {
            for (iClone i = cloneList.begin(); i != cloneList.end(); ++i) {
                  if (i->el == el) {
                        id = i->id;
                        dumpEvents = false;
                        break;
                        }
                  }
            if (id == -1) {
                  id = cloneList.size();
                  ClonePart cp(el, id);
                  cloneList.push_back(cp);
                  }
            }

      if (id != -1)
            xml.tag(level++, "part cloneId=\"%d\"", id);
      else
            xml.tag(level++, "part");
      xml.strTag(level, "name", _name);

//      PosLen poslen(*this);
//      int tickpos = tick();
//      poslen.setTick(tickpos);
      PosLen::write(level, xml, "poslen");
      xml.intTag(level, "selected", _selected);
      xml.intTag(level, "color", _colorIndex);
      if (_mute)
            xml.intTag(level, "mute", _mute);
      if (dumpEvents) {
            for (ciEvent e = el->begin(); e != el->end(); ++e)
                  e->second.write(level, xml, *this);
            }
      xml.etag(level, "part");
      }

//---------------------------------------------------------
//   Part::read
//---------------------------------------------------------

void Part::read(Xml& xml)
      {
      int id = -1;
      bool containsEvents = false;

      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 == "poslen") {
                              PosLen::read(xml, "poslen");
                              }
                        else if (tag == "pos") {
                              Pos pos;
                              pos.read(xml, "pos");  // obsolete
                              setTick(pos.tick());
                              }
                        else if (tag == "len") {
                              Pos len;
                              len.read(xml, "len");  // obsolete
                              setLenTick(len.tick());
                              }
                        else if (tag == "selected")
                              _selected = xml.parseInt();
                        else if (tag == "color")
                              _colorIndex = xml.parseInt();
                        else if (tag == "mute")
                              _mute = xml.parseInt();
                        else if (tag == "event") {
                              containsEvents = true;
                              EventType type = Wave;
                              if (_track->isMidiTrack())
                                    type = Note;
                              Event e(type);
                              e.read(xml);
                              // tickpos is relative to start of part
                              // TODO: better handling for wave event
                              e.move(-tick());
                              int tick = e.tick();
                              if ((tick < 0) || (tick >= lenTick())) {
                                    printf("ReadEvent: warning: event not in part: %d - %d -%d, discarded\n",
                                       0, tick, lenTick());
                                    }
                              else {
                                    _events->add(e);
                                    if (e.type() == Controller) {
                                          int port = ((MidiTrack*)_track)->outPort();
                                          int channel = ((MidiTrack*)_track)->outChannel();
                                          MidiPort* mp = &midiPorts[port];
                                          mp->setCtrl(channel, tick, e.dataA(), e.dataB());
                                          }
                                    }
                              }
                        else
                              xml.unknown("Part");
                        break;
                  case Xml::Attribut:
                        if (tag == "cloneId")
                              id = xml.s2().toInt();
                        break;
                  case Xml::TagEnd:
                        if (tag == "part") {
                              if (id != -1) {
                                    // clone part
                                    if (containsEvents) {
                                          // add to cloneList:
                                          ClonePart cp(_events, id);
                                          cloneList.push_back(cp);
                                          }
                                    else {
                                          // replace event list with clone event
                                          // list
                                          for (iClone i = cloneList.begin();
                                             i != cloneList.end(); ++i) {
                                                if (i->id == id) {
                                                      delete _events;
                                                      _events = (EventList*)(i->el);
                                                      _events->incRef(1);
                                                      _events->incARef(1);
                                                      break;
                                                      }
                                                }
                                          }
                                    }
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   writeFont
//---------------------------------------------------------

void Song::writeFont(int level, Xml& xml, const char* name,
   const QFont& font) const
      {
      xml.nput(level, "<%s family=\"%s\" size=\"%d\"",
         name, font.family().latin1(), font.pointSize());
      if (font.weight() != QFont::Normal)
            xml.nput(" weight=\"%d\"", font.weight());
      if (font.italic())
            xml.nput(" italic=\"1\"");
      xml.nput(" />\n");
      }

//---------------------------------------------------------
//   readFont
//---------------------------------------------------------

QFont Song::readFont(Xml& xml, const char* name)
      {
      QFont f;
      for (;;) {
            Xml::Token token = xml.parse();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return f;
                  case Xml::TagStart:
                        xml.unknown("readFont");
                        break;
                  case Xml::Attribut:
                        if (xml.s1() == "family")
                              f.setFamily(xml.s2());
                        else if (xml.s1() == "size")
                              f.setPointSize(xml.s2().toInt());
                        else if (xml.s1() == "weight")
                              f.setWeight(xml.s2().toInt());
                        else if (xml.s1() == "italic")
                              f.setItalic(xml.s2().toInt());
                        break;
                  case Xml::TagEnd:
                        if (xml.s1() == name)
                              return f;
                  default:
                        break;
                  }
            }
      return f;
      }

//---------------------------------------------------------
//   readPart
//---------------------------------------------------------

Part* MusE::readPart(Xml& xml)
      {
      Part* part = 0;
      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return part;
                  case Xml::Text:
                        {
                        int trackIdx, partIdx;
                        sscanf(tag.latin1(), "%d:%d", &trackIdx, &partIdx);
                        Track* track = song->tracks()->index(trackIdx);
                        if (track)
                              part = track->parts()->find(partIdx);
                        }
                        break;
                  case Xml::TagStart:
                        xml.unknown("readPart");
                        break;
                  case Xml::TagEnd:
                        if (tag == "part")
                              return part;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readToplevels
//---------------------------------------------------------

void MusE::readToplevels(Xml& xml)
      {
      PartList* pl = new PartList;
      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 == "part") {
                              Part* part = readPart(xml);
                              if (part)
                                    pl->add(part);
                              }
                        else if (tag == "pianoroll") {
                              startPianoroll(pl);
                              toplevels.back().cobject()->readStatus(xml);
                              pl = new PartList;
                              }
                        else if (tag == "drumedit") {
                              startDrumEditor(pl);
                              toplevels.back().cobject()->readStatus(xml);
                              pl = new PartList;
                              }
                        else if (tag == "listeditor") {
                              startListEditor(pl);
                              toplevels.back().cobject()->readStatus(xml);
                              pl = new PartList;
                              }
                        else if (tag == "master") {
                              startMasterEditor();
                              toplevels.back().cobject()->readStatus(xml);
                              }
                        else if (tag == "lmaster") {
                              startLMasterEditor();
                              toplevels.back().cobject()->readStatus(xml);
                              }
                        else if (tag == "marker") {
                              startMarkerView();
                              toplevels.back().cobject()->readStatus(xml);
                              }
                        else if (tag == "waveedit") {
                              startWaveEditor(pl);
                              toplevels.back().cobject()->readStatus(xml);
                              pl = new PartList;
                              }
                        else if (tag == "cliplist") {
                              startClipList();
                              toplevels.back().cobject()->readStatus(xml);
                              }
                        else
                              xml.unknown("MusE");
                        break;
                  case Xml::Attribut:
                        break;
                  case Xml::TagEnd:
                        if (tag == "toplevels") {
                              delete pl;
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readCtrl
//---------------------------------------------------------

void MusE::readCtrl(Xml&, int /*prt*/, int /*channel*/)
      {
#if 0
      ChannelState* iState = midiPorts[prt].iState(channel);

      int idx = 0;
      int val = -1;

      for (;;) {
            Xml::Token token = xml.parse();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        xml.unknown("readCtrl");
                        break;
                  case Xml::Attribut:
                        if (xml.s1() == "idx")
                              idx = xml.s2().toInt();
                        else if (xml.s1() == "val")
                              val = xml.s2().toInt();
                        break;
                  case Xml::TagEnd:
                        if (xml.s1() == "ctrl") {
                              iState->controller[idx] = val;
// printf("%d %d ctrl %d val %d\n", prt, channel, idx, val);
                              return;
                              }
                  default:
                        break;
                  }
            }
#endif
      }

//---------------------------------------------------------
//   readMidichannel
//---------------------------------------------------------

void MusE::readMidichannel(Xml& xml, int prt)
      {
      int channel = 0;
//      MidiPort* port = &midiPorts[prt];

      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 == "pitch") {
//TODO                              port->setCtrl(channel, 0, CTRL_PITCH, xml.parseInt());
                              }
                        else if (tag == "program") {
//TODO                              port->setCtrl(channel, 0, CTRL_PROGRAM, xml.parseInt());
                              }
                        else if (tag == "ctrl")
                              readCtrl(xml, prt, channel);
                        else {
                              xml.unknown("readMidichannel");
                              }
                        break;
                  case Xml::Attribut:
                        if (tag == "ch") {
                              channel = xml.s2().toInt();
                              }
                        break;
                  case Xml::TagEnd:
                        if (tag == "midichannel")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readMidiport
//---------------------------------------------------------

void MusE::readMidiport(Xml& xml)
      {
      int port = 0;
      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 == "midichannel")
                              readMidichannel(xml, port);
                        else {
                              xml.unknown("readMidiport");
                              }
                        break;
                  case Xml::Attribut:
                        if (tag == "port") {
                              port = xml.s2().toInt();
                              }
                        break;
                  case Xml::TagEnd:
                        if (tag == "midiport") {
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readMarker
//---------------------------------------------------------

void Song::readMarker(Xml& xml)
      {
      Marker m;
      m.read(xml);
      _markerList->add(m);
      }

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

void Song::read(Xml& xml)
      {
      cloneList.clear();
      for (;;) {
            Xml::Token token;
            token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        if (tag == "master")
                              setMasterFlag(xml.parseInt());
                        else if (tag == "loop")
                              setLoop(xml.parseInt());
                        else if (tag == "punchin")
                              setPunchin(xml.parseInt());
                        else if (tag == "punchout")
                              setPunchout(xml.parseInt());
                        else if (tag == "record")
                              setRecord(xml.parseInt());
                        else if (tag == "solo")
                              soloFlag = xml.parseInt();
                        else if (tag == "type")
                              _mtype  = MType(xml.parseInt());
                        else if (tag == "recmode")
                              _recMode  = xml.parseInt();
                        else if (tag == "cycle")
                              _cycleMode  = xml.parseInt();
                        else if (tag == "click")
                              setClick(xml.parseInt());
                        else if (tag == "quantize")
                              _quantize  = xml.parseInt();
                        else if (tag == "len")
                              _len  = xml.parseInt();
                        else if (tag == "follow")
                              _follow  = FollowMode(xml.parseInt());
                        else if (tag == "tempolist") {
                              tempomap.read(xml);
                              }
                        else if (tag == "siglist")
                              sigmap.read(xml);
                        else if (tag == "miditrack") {
                              MidiTrack* track = new MidiTrack();
                              track->read(xml);
                              insertTrack0(track, -1);
                              }
                        else if (tag == "drumtrack") {
                              MidiTrack* track = new MidiTrack();
                              track->setType(Track::DRUM);
                              track->read(xml);
                              insertTrack0(track, -1);
                              }
                        else if (tag == "wavetrack") {
                              WaveTrack* track = new WaveTrack();
                              track->read(xml);
                              insertTrack0(track,-1);
                              }
                        else if (tag == "AudioInput") {
                              AudioInput* track = new AudioInput();
                              track->read(xml);
                              insertTrack0(track,-1);
                              }
                        else if (tag == "AudioOutput") {
                              AudioOutput* track = new AudioOutput();
                              track->read(xml);
                              insertTrack0(track,-1);
                              }
                        else if (tag == "AudioGroup") {
                              AudioGroup* track = new AudioGroup();
                              track->read(xml);
                              insertTrack0(track,-1);
                              }
                        else if (tag == "AudioAux") {
                              AudioAux* track = new AudioAux();
                              track->read(xml);
                              insertTrack0(track,-1);
                              }
                        else if (tag == "SynthI") {
                              SynthI* track = new SynthI();
                              track->read(xml);
                              // insertTrack(track,-1);
                              }
                        else if (tag == "Route") {
                              readRoute(xml);
                              }
                        else if (tag == "marker")
                              readMarker(xml);
                        else if (tag == "globalPitchShift")
                              _globalPitchShift = xml.parseInt();
                        else if (tag == "automation")
                              automation = xml.parseInt();
                        else if (tag == "cpos") {
                              int pos = xml.parseInt();
                              Pos p(pos, true);
                              setPos(Song::CPOS, p, false, false, false);
                              }
                        else if (tag == "lpos") {
                              int pos = xml.parseInt();
                              Pos p(pos, true);
                              setPos(Song::LPOS, p, false, false, false);
                              }
                        else if (tag == "rpos") {
                              int pos = xml.parseInt();
                              Pos p(pos, true);
                              setPos(Song::RPOS, p, false, false, false);
                              }
                        else if (tag == "drummap")
                              readDrumMap(xml, false);
                        else
                              xml.unknown("Song");
                        break;
                  case Xml::Attribut:
                        break;
                  case Xml::TagEnd:
                        if (tag == "song") {
                              return;
                              }
                  default:
                        break;
                  }
            }
      dirty = false;
      }

//---------------------------------------------------------
//   read
//    read song
//---------------------------------------------------------

void MusE::read(Xml& xml, bool skipConfig)
      {
      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 == "configuration")
                              if (skipConfig)
                                    xml.skip(tag);
                              else
                                    readConfiguration(xml);
                        else if (tag == "song")
                              song->read(xml);
                        else if (tag == "midiport")
                              readMidiport(xml);
                        else if (tag == "Controller") {  // obsolete
                              MidiController* ctrl = new MidiController;
                              ctrl->read(xml);
                              delete ctrl;
                              }
                        else if (tag == "mplugin")
                              readStatusMidiInputTransformPlugin(xml);
                        else if (tag == "toplevels")
                              readToplevels(xml);
                        else
                              xml.unknown("muse");
                        break;
                  case Xml::Attribut:
                        if (tag == "version") {
                              int major = xml.s2().section('.', 0, 0).toInt();
                              int minor = xml.s2().section('.', 1, 1).toInt();
                              xml.setVersion(major, minor);
                              }
                        break;
                  case Xml::TagEnd:
                        if (!skipmode && tag == "muse")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   write
//---------------------------------------------------------

void Song::write(int level, Xml& xml) const
      {
      xml.tag(level++, "song");
      xml.intTag(level, "automation", automation);
      xml.intTag(level, "cpos", song->cpos());
      xml.intTag(level, "rpos", song->rpos());
      xml.intTag(level, "lpos", song->lpos());
      xml.intTag(level, "master", _masterFlag);
      xml.intTag(level, "loop", loopFlag);
      xml.intTag(level, "punchin", punchinFlag);
      xml.intTag(level, "punchout", punchoutFlag);
      xml.intTag(level, "record", recordFlag);
      xml.intTag(level, "solo", soloFlag);
      xml.intTag(level, "type", _mtype);
      xml.intTag(level, "recmode", _recMode);
      xml.intTag(level, "cycle", _cycleMode);
      xml.intTag(level, "click", _click);
      xml.intTag(level, "quantize", _quantize);
      xml.intTag(level, "len", _len);
      xml.intTag(level, "follow", _follow);
      if (_globalPitchShift)
            xml.intTag(level, "globalPitchShift", _globalPitchShift);

      cloneList.clear();

      // write tracks
      for (ciTrack i = _tracks.begin(); i != _tracks.end(); ++i)
            (*i)->write(level, xml);

      // write routing
      for (ciTrack i = _tracks.begin(); i != _tracks.end(); ++i) {
            if ((*i)->isMidiTrack())
                  continue;
            WaveTrack* track = (WaveTrack*)(*i);
            track->writeRouting(level, xml);
            }

      tempomap.write(level, xml);
      sigmap.write(level, xml);
      _markerList->write(level, xml);

      writeDrumMap(level, xml, false);
      xml.tag(level, "/song");
      }

//---------------------------------------------------------
//   write
//    write song
//---------------------------------------------------------

void MusE::write(Xml& xml) const
      {
      xml.header();

      int level = 0;
      xml.tag(level++, "muse version=\"2.0\"");
      writeConfiguration(level, xml);

      writeStatusMidiInputTransformPlugins(level, xml);

      song->write(level, xml);

      if (!toplevels.empty()) {
            xml.tag(level++, "toplevels");
            for (ciToplevel i = toplevels.begin(); i != toplevels.end(); ++i) {
                  if (i->cobject()->isVisible())
                        i->cobject()->writeStatus(level, xml);
                  }
            xml.tag(level--, "/toplevels");
            }

      xml.tag(level, "/muse");
      }

