//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: undo.cpp,v 1.12.2.2 2004/12/31 15:01:23 lunar_shuttle Exp $
//
//  (C) Copyright 1999/2000 Werner Schweer (ws@seh.de)
//=========================================================

#include "undo.h"
#include "song.h"
#include "globals.h"

// iundo points to last Undo() in Undo-list

static bool undoMode = false;  // for debugging

//---------------------------------------------------------
//   typeName
//---------------------------------------------------------

const char* UndoOp::typeName()
      {
      static const char* name[] = {
            "AddTrack", "DeleteTrack", "ModifyTrack",
            "AddPart",  "DeletePart",  "ModifyPart",
            "AddEvent", "DeleteEvent", "ModifyEvent",
            "AddTempo", "DeleteTempo", "AddSig", "DeleteSig",
            "SwapTrack"
            };
      return name[type];
      }

//---------------------------------------------------------
//   dump
//---------------------------------------------------------

void UndoOp::dump()
      {
      printf("UndoOp: %s\n   ", typeName());
      switch(type) {
            case AddTrack:
            case DeleteTrack:
                  printf("%d %s\n", trackno, oTrack->name().latin1());
                  break;
            case ModifyTrack:
                  printf("%d <%s>-<%s>\n", trackno, oTrack->name().latin1(), nTrack->name().latin1());
                  break;
            case AddPart:
            case DeletePart:
            case ModifyPart:
                  break;
            case AddEvent:
            case DeleteEvent:
                  printf("old event:\n");
                  oEvent.dump(5);
                  printf("   new event:\n");
                  nEvent.dump(5);
                  printf("   Part:\n");
                  if (part)
                        part->dump(5);
                  break;
            case ModifyEvent:
            case AddTempo:
            case DeleteTempo:
            case AddSig:
            case SwapTrack:
                  break;
            }
      }

//---------------------------------------------------------
//    startUndo
//---------------------------------------------------------

void Song::startUndo()
      {
      undoList->push_back(Undo());
      updateFlags = 0;
      undoMode = true;
      }

//---------------------------------------------------------
//   endUndo
//---------------------------------------------------------

void Song::endUndo(int flags)
      {
      updateFlags |= flags;
      endMsgCmd();
      undoMode = false;
      }

//---------------------------------------------------------
//   doUndo2
//    real time part
//---------------------------------------------------------

void Song::doUndo2()
      {
      Undo& u = undoList->back();
      for (riUndoOp i = u.rbegin(); i != u.rend(); ++i) {
            switch(i->type) {
                  case UndoOp::AddTrack:
                        removeTrack2(i->oTrack);
                        updateFlags |= SC_TRACK_REMOVED;
                        break;
                  case UndoOp::DeleteTrack:
                        insertTrack2(i->oTrack, i->trackno);
                        updateFlags |= SC_TRACK_INSERTED;
                        break;
                  case UndoOp::ModifyTrack:
                        {
                        Track* track = i->nTrack->clone();
                        *(i->nTrack) = *(i->oTrack);
                        delete i->oTrack;
                        i->oTrack = track;
                        updateFlags |= SC_TRACK_MODIFIED;
                        }
                        break;
                  case UndoOp::SwapTrack:
                        {
                        updateFlags |= SC_TRACK_MODIFIED;
                        Track* track  = _tracks[i->a];
                        _tracks[i->a] = _tracks[i->b];
                        _tracks[i->b] = track;
                        updateFlags |= SC_TRACK_MODIFIED;
                        }
                        break;
                  case UndoOp::AddPart:
                        {
                        Part* part = i->oPart;
                        removePart(part);
                        updateFlags |= SC_PART_REMOVED;
                        i->oPart->events()->incARef(-1);
                        }
                        break;
                  case UndoOp::DeletePart:
                        addPart(i->oPart);
                        updateFlags |= SC_PART_INSERTED;
                        i->oPart->events()->incARef(1);
                        break;
                  case UndoOp::ModifyPart:
                        changePart(i->oPart, i->nPart);
                        i->oPart->events()->incARef(-1);
                        i->nPart->events()->incARef(1);
                        updateFlags |= SC_PART_MODIFIED;
                        break;
                  case UndoOp::AddEvent:
                        deleteEvent(i->nEvent, i->part);
                        updateFlags |= SC_EVENT_REMOVED;
                        break;
                  case UndoOp::DeleteEvent:
                        addEvent(i->nEvent, i->part);
                        updateFlags |= SC_EVENT_INSERTED;
                        break;
                  case UndoOp::ModifyEvent:
                        updateFlags |= SC_EVENT_MODIFIED;
                        changeEvent(i->oEvent, i->nEvent, i->part);
                        break;
                  case UndoOp::AddTempo:
                        //printf("doUndo2: UndoOp::AddTempo. deleting tempo at: %d\n", i->a);
                        tempomap.delTempo(i->a);
                        updateFlags |= SC_TEMPO;
                        break;
                  case UndoOp::DeleteTempo:
                        //printf("doUndo2: UndoOp::DeleteTempo. adding tempo at: %d, tempo=%d\n", i->a, i->b);
                        tempomap.addTempo(i->a, i->b);
                        updateFlags |= SC_TEMPO;
                        break;
                  case UndoOp::AddSig:
                        sigmap.del(i->a);
                        updateFlags |= SC_SIG;
                        break;
                  case UndoOp::DeleteSig:
                        sigmap.add(i->a, i->b, i->c);
                        updateFlags |= SC_SIG;
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   Song::doRedo2
//---------------------------------------------------------

void Song::doRedo2()
      {
      Undo& u = redoList->back();
      for (iUndoOp i = u.begin(); i != u.end(); ++i) {
            switch(i->type) {
                  case UndoOp::AddTrack:
                        insertTrack2(i->oTrack, i->trackno);
                        updateFlags |= SC_TRACK_INSERTED;
                        break;
                  case UndoOp::DeleteTrack:
                        removeTrack2(i->oTrack);
                        updateFlags |= SC_TRACK_REMOVED;
                        break;
                  case UndoOp::ModifyTrack:
                        {
                        Track* track = i->nTrack->clone();
                        *(i->nTrack) = *(i->oTrack);
                        delete i->oTrack;
                        i->oTrack = track;
                        updateFlags |= SC_TRACK_MODIFIED;
                        }
                        break;
                  case UndoOp::SwapTrack:
                        {
                        Track* track  = _tracks[i->a];
                        _tracks[i->a] = _tracks[i->b];
                        _tracks[i->b] = track;
                        updateFlags |= SC_TRACK_MODIFIED;
                        }
                        break;
                  case UndoOp::AddPart:
                        addPart(i->oPart);
                        updateFlags |= SC_PART_INSERTED;
                        i->oPart->events()->incARef(1);
                        break;
                  case UndoOp::DeletePart:
                        removePart(i->oPart);
                        updateFlags |= SC_PART_REMOVED;
                        i->oPart->events()->incARef(-1);
                        break;
                  case UndoOp::ModifyPart:
                        changePart(i->nPart, i->oPart);
                        updateFlags |= SC_PART_MODIFIED;
                        i->oPart->events()->incARef(1);
                        i->nPart->events()->incARef(-1);
                        break;
                  case UndoOp::AddEvent:
                        addEvent(i->nEvent, i->part);
                        updateFlags |= SC_EVENT_INSERTED;
                        break;
                  case UndoOp::DeleteEvent:
                        deleteEvent(i->nEvent, i->part);
                        updateFlags |= SC_EVENT_REMOVED;
                        break;
                  case UndoOp::ModifyEvent:
                        changeEvent(i->nEvent, i->oEvent, i->part);
                        updateFlags |= SC_EVENT_MODIFIED;
                        break;
                  case UndoOp::AddTempo:
                        //printf("doRedo2: UndoOp::AddTempo. adding tempo at: %d with tempo=%d\n", i->a, i->b);
                        tempomap.addTempo(i->a, i->b);
                        updateFlags |= SC_TEMPO;
                        break;
                  case UndoOp::DeleteTempo:
                        //printf("doRedo2: UndoOp::DeleteTempo. deleting tempo at: %d with tempo=%d\n", i->a, i->b);
                        tempomap.delTempo(i->a);
                        updateFlags |= SC_TEMPO;
                        break;
                  case UndoOp::AddSig:
                        sigmap.add(i->a, i->b, i->c);
                        updateFlags |= SC_SIG;
                        break;
                  case UndoOp::DeleteSig:
                        sigmap.del(i->a);
                        updateFlags |= SC_SIG;
                        break;
                  }
            }
      }

void Song::undoOp(UndoOp::UndoType type, int a, int b, int c)
      {
      UndoOp i;
      i.type = type;
      i.a  = a;
      i.b  = b;
      i.c  = c;
      addUndo(i);
      }

void Song::undoOp(UndoOp::UndoType type, Track* oldTrack, Track* newTrack)
      {
      UndoOp i;
      i.type    = type;
      i.oTrack  = oldTrack;
      i.nTrack  = newTrack;
      addUndo(i);
      }

void Song::undoOp(UndoOp::UndoType type, int n, Track* track)
      {
      UndoOp i;
      i.type    = type;
      i.trackno = n;
      i.oTrack  = track;
      if (type == UndoOp::AddTrack)
            updateFlags |= SC_TRACK_INSERTED;
      addUndo(i);
      }

void Song::undoOp(UndoOp::UndoType type, Part* part)
      {
      UndoOp i;
      i.type  = type;
      i.oPart = part;
      addUndo(i);
      }

void Song::undoOp(UndoOp::UndoType type, Event& oev, Event& nev, Part* part)
      {
      UndoOp i;
      i.type   = type;
      i.nEvent = nev;
      i.oEvent = oev;
      i.part   = part;
      addUndo(i);
      }

void Song::undoOp(UndoOp::UndoType type, Part* oPart, Part* nPart)
      {
      UndoOp i;
      i.type  = type;
      i.oPart = nPart;
      i.nPart = oPart;
      addUndo(i);
      }

void Song::undoOp(UndoOp::UndoType type, int c, int ctrl, int ov, int nv)
      {
      UndoOp i;
      i.type    = type;
      i.channel = c;
      i.ctrl    = ctrl;
      i.oVal    = ov;
      i.nVal    = nv;
      addUndo(i);
      }

void Song::undoOp(UndoOp::UndoType type, SigEvent* oevent, SigEvent* nevent)
      {
      UndoOp i;
      i.type       = type;
      i.oSignature = oevent;
      i.nSignature = nevent;
      addUndo(i);
      }

//---------------------------------------------------------
//   addUndo
//---------------------------------------------------------

void Song::addUndo(UndoOp& i)
      {
      if (!undoMode) {
            printf("internal error: undoOp without startUndo()\n");
            return;
            }
      undoList->back().push_back(i);
      dirty = true;
      }

//---------------------------------------------------------
//   doUndo1
//    non realtime context
//    return true if nothing to do
//---------------------------------------------------------

bool Song::doUndo1()
      {
      if (undoList->empty())
            return true;
      Undo& u = undoList->back();
      for (riUndoOp i = u.rbegin(); i != u.rend(); ++i) {
            switch(i->type) {
                  case UndoOp::AddTrack:
                        removeTrack1(i->oTrack);
                        break;
                  case UndoOp::DeleteTrack:
                        insertTrack1(i->oTrack, i->trackno);
                        break;
                  default:
                        break;
                  }
            }
      return false;
      }

//---------------------------------------------------------
//   doUndo3
//    non realtime context
//---------------------------------------------------------

void Song::doUndo3()
      {
      Undo& u = undoList->back();
      for (riUndoOp i = u.rbegin(); i != u.rend(); ++i) {
            switch(i->type) {
                  case UndoOp::AddTrack:
                        removeTrack3(i->oTrack);
                        break;
                  case UndoOp::DeleteTrack:
                        insertTrack3(i->oTrack, i->trackno);
                        break;
                  default:
                        break;
                  }
            }
      redoList->push_back(u); // put item on redo list
      undoList->pop_back();
      dirty = true;
      }

//---------------------------------------------------------
//   doRedo1
//    non realtime context
//    return true if nothing to do
//---------------------------------------------------------

bool Song::doRedo1()
      {
      if (redoList->empty())
            return true;
      Undo& u = redoList->back();
      for (iUndoOp i = u.begin(); i != u.end(); ++i) {
            switch(i->type) {
                  case UndoOp::AddTrack:
                        insertTrack1(i->oTrack, i->trackno);
                        break;
                  case UndoOp::DeleteTrack:
                        removeTrack1(i->oTrack);
                        break;
                  default:
                        break;
                  }
            }
      return false;
      }

//---------------------------------------------------------
//   doRedo3
//    non realtime context
//---------------------------------------------------------

void Song::doRedo3()
      {
      Undo& u = redoList->back();
      for (iUndoOp i = u.begin(); i != u.end(); ++i) {
            switch(i->type) {
                  case UndoOp::AddTrack:
                        insertTrack3(i->oTrack, i->trackno);
                        break;
                  case UndoOp::DeleteTrack:
                        removeTrack3(i->oTrack);
                        break;
                  default:
                        break;
                  }
            }
      undoList->push_back(u); // put item on undo list
      redoList->pop_back();
      dirty = true;
      }

