//=========================================================
//  MusE
//  Linux Music Editor
//    $Id: dcanvas.cpp,v 1.2 2003/10/29 14:24:55 lunar_shuttle Exp $
//  (C) Copyright 1999 Werner Schweer (ws@seh.de)
//=========================================================

#include <stdio.h>
#include <qpainter.h>
#include <qapplication.h>
#include <qclipboard.h>
#include <qdragobject.h>
#include <values.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/mman.h>

#include "dcanvas.h"
#include "midieditor.h"
#include "drummap.h"
#include "event.h"
#include "mpevent.h"
#include "xml.h"
#include "globals.h"
#include "midiport.h"
#include "midithread.h"

#define CARET   10
#define CARET2   5

//---------------------------------------------------------
//   DEvent
//---------------------------------------------------------

DEvent::DEvent(MidiEvent* e, Part* p)
  : CItem(e, p)
      {
      int instr = drumInmap[e->pitch()];
      int y  = instr * TH + TH/2;
      int tick = e->posTick() + p->posTick();
      setPos(QPoint(tick, y));
      setBBox(QRect(-CARET2, -CARET2, CARET, CARET));
      }

//---------------------------------------------------------
//   addItem
//---------------------------------------------------------

void DrumCanvas::addItem(Part* part, Event* event)
      {
      DEvent* ev = new DEvent((MidiEvent*)event, part);
      items.add(ev);
      }

//---------------------------------------------------------
//   DrumCanvas
//---------------------------------------------------------

DrumCanvas::DrumCanvas(MidiEditor* pr, QWidget* parent, int sx,
   int sy, const char* name)
   : EventCanvas(pr, parent, sx, sy, name)
      {
      setVirt(false);
      songChanged(SC_TRACK_INSERTED);
      }

//---------------------------------------------------------
//   moveItem
//---------------------------------------------------------

bool DrumCanvas::moveItem(CItem* item, const QPoint& pos, DragType dtype)
      {
      DEvent* nevent   = (DEvent*) item;
      MidiPart* part   = (MidiPart*)nevent->part();
      MidiEvent* event = (MidiEvent*)nevent->event();
      int ntick        = editor->rasterVal(pos.x());
      int npitch       = drumMap[y2pitch(pos.y())].enote;
      MidiEvent* newEvent = new MidiEvent(*event);

      newEvent->setPitch(npitch);
      newEvent->setPosTick(ntick - part->posTick());

      if (dtype == MOVE_COPY || dtype == MOVE_CLONE) {
            midiThread->msgAddEvent(newEvent, part, false);
            }
      else {
            midiThread->msgChangeEvent(event, newEvent, part, false);
            }
      return true;
      }

//---------------------------------------------------------
//   newItem
//---------------------------------------------------------

CItem* DrumCanvas::newItem(const QPoint& p, int state)
      {
      int instr = y2pitch(p.y());
      int velo  = drumMap[instr].lv4;
      if (state == ShiftButton)
            velo = drumMap[instr].lv3;
      else if (state == ControlButton)
            velo = drumMap[instr].lv2;
      else if (state == (ControlButton | ShiftButton))
            velo = drumMap[instr].lv1;
      int tick = editor->rasterVal(p.x());
      tick     -= curPart->posTick();
      MidiEvent* e = new MidiEvent(
         tick, MidiEvent::Note, drumMap[instr].enote, velo, 0,
         drumMap[instr].len);
      return new DEvent(e, curPart);
      }

//---------------------------------------------------------
//   resizeItem
//---------------------------------------------------------

void DrumCanvas::resizeItem(CItem* item, bool)
      {
      DEvent* nevent = (DEvent*) item;
      midiThread->msgDeleteEvent(nevent->event(), nevent->part());
      }

//---------------------------------------------------------
//   newItem
//---------------------------------------------------------

void DrumCanvas::newItem(CItem* item, bool noSnap)
      {
      DEvent* nevent = (DEvent*) item;
      MidiEvent* event = (MidiEvent*)nevent->event();
      int x = item->x();
      if (!noSnap)
            x = editor->rasterVal(x);
      event->setPosTick(x - nevent->part()->posTick());
      int npitch = drumMap[y2pitch(item->y())].enote;
      event->setPitch(npitch);

      //
      // check for existing event
      //    if found change command semantic from insert to delete
      //
      EventList* el = nevent->part()->events();
      iEvent lower = el->lower_bound(event->posTick());
      iEvent upper = el->upper_bound(event->posTick());

      for (iEvent i = lower; i != upper; ++i) {
            MidiEvent* ev = (MidiEvent*)(i->second);
            if (ev->pitch() == npitch) {
                  delete event;
                  midiThread->msgDeleteEvent(ev, nevent->part());
                  return;
                  }
            }

      midiThread->msgAddEvent(event, nevent->part());
      }

//---------------------------------------------------------
//   deleteItem
//---------------------------------------------------------

bool DrumCanvas::deleteItem(CItem* item)
      {
      midiThread->msgDeleteEvent(((DEvent*)item)->event(), ((DEvent*)item)->part());
      return false;
      }

//---------------------------------------------------------
//   drawItem
//---------------------------------------------------------

void DrumCanvas::drawItem(QPainter&p, const CItem*item, const QRect&) const
      {
      p.setPen(black);

      int x = mapx(item->pos().x());
      int y = mapy(item->pos().y());

      QPointArray pa(4);
      pa.setPoint(0, x - CARET2, y);
      pa.setPoint(1, x,          y - CARET2);
      pa.setPoint(2, x + CARET2, y);
      pa.setPoint(3, x,          y + CARET2);

      DEvent* e   = (DEvent*) item;
      MidiEvent* me = (MidiEvent*)e->event();
      int velo    = me->velo();
      DrumMap* dm = &drumMap[y2pitch(y)]; //Get the drum item 

      QColor color;
      if (velo < dm->lv1)
            color.setRgb(240, 240, 255);
      else if (velo < dm->lv2)
            color.setRgb(200, 200, 255);
      else if (velo < dm->lv3)
            color.setRgb(170, 170, 255);
      else
            color.setRgb(0, 0, 255);

      if (e->part() != curPart)
            p.setBrush(lightGray);
      else if (item->isMoving()) {
            p.setBrush(gray);
            p.drawPolygon(pa);
            p.setBrush(black);
            QPoint p = map(item->mp());
            x = p.x();
            y = p.y();
            pa.setPoint(0, x-CARET2,  y + TH/2);
            pa.setPoint(1, x,         y + TH/2+CARET2);
            pa.setPoint(2, x+CARET2,  y + TH/2);
            pa.setPoint(3, x,         y + (TH-CARET)/2);
            }
      else if (item->isSelected())
            p.setBrush(black);
      else
            p.setBrush(color);
      p.drawPolygon(pa);
      }

//---------------------------------------------------------
//   drawCanvas
//---------------------------------------------------------

extern void drawTickRaster(QPainter& p, int, int, int, int, int);

void DrumCanvas::drawCanvas(QPainter& p, const QRect& rect)
      {
      int x = rect.x();
      int y = rect.y();
      int w = rect.width();
      int h = rect.height();

      //---------------------------------------------------
      //  horizontal lines
      //---------------------------------------------------

      int yy  = ((y-1) / TH) * TH + TH;
      for (; yy < y + h; yy += TH) {
            p.setPen(gray);
            p.drawLine(x, yy, x + w, yy);
            }

      //---------------------------------------------------
      // vertical lines
      //---------------------------------------------------

      drawTickRaster(p, x, y, w, h, editor->quant());
      }

//---------------------------------------------------------
//   y2pitch
//---------------------------------------------------------

int DrumCanvas::y2pitch(int y) const
      {
      int pitch = y/TH;
      if (pitch >= DRUM_MAPSIZE)
            pitch = DRUM_MAPSIZE-1;
      return pitch;
      }

//---------------------------------------------------------
//   pitch2y
//---------------------------------------------------------

int DrumCanvas::pitch2y(int pitch) const
      {
      return pitch * TH;
      }

//---------------------------------------------------------
//   cmd
//---------------------------------------------------------

void DrumCanvas::cmd(int cmd)
      {
      switch(cmd) {
            case CMD_CUT:
                  copy();
                  song->startUndo();
                  for (iCItem i = items.begin(); i != items.end(); ++i) {
                        if (!i->second->isSelected())
                              continue;
                        DEvent* e = (DEvent*)(i->second);
                        Event* event = e->event();
                        midiThread->msgDeleteEvent(event, e->part(), false);
                        }
                  song->endUndo(SC_EVENT_REMOVED);
                  break;
            case CMD_COPY:
                  copy();
                  break;
            case CMD_PASTE:
                  paste();
                  break;
            case CMD_SELECT_ALL:     // select all
                  for (iCItem k = items.begin(); k != items.end(); ++k) {
                        if (!k->second->isSelected())
                              selectItem(k->second, true);
                        }
                  break;
            case CMD_SELECT_NONE:     // select none
                  deselectAll();
                  break;
            case CMD_SELECT_INVERT:     // invert selection
                  for (iCItem k = items.begin(); k != items.end(); ++k) {
                        selectItem(k->second, !k->second->isSelected());
                        }
                  break;
            case CMD_SELECT_ILOOP:     // select inside loop
                  for (iCItem k = items.begin(); k != items.end(); ++k) {
                        DEvent* nevent =(DEvent*)(k->second);
                        Part* part = nevent->part(); 
                        Event* event = nevent->event();
                        int tick  = event->posTick() + part->posTick(); 
                        if (tick < song->lpos() || tick >= song->rpos())
                              selectItem(k->second, false);
                        else
                              selectItem(k->second, true);
                        }
                  break;
            case CMD_SELECT_OLOOP:     // select outside loop
                  for (iCItem k = items.begin(); k != items.end(); ++k) {
                        DEvent* nevent =(DEvent*)(k->second);
                        Part* part = nevent->part();
                        MidiEvent* event = (MidiEvent*)nevent->event();
                        int tick  = event->posTick() + part->posTick();
                        if (tick < song->lpos() || tick >= song->rpos())
                              selectItem(k->second, true);
                        else
                              selectItem(k->second, false);
                        }
                  break;
            case CMD_DEL:
                  if (selectionSize()) {
                        song->startUndo();
                        for (iCItem i = items.begin(); i != items.end(); ++i) {
                              if (!i->second->isSelected())
                                    continue;
                              midiThread->msgDeleteEvent(i->second->event(), i->second->part(), false);
                              }
                        song->endUndo(SC_EVENT_REMOVED);
                        }
                  return;

            case CMD_SAVE:
            case CMD_LOAD:
                  printf("DrumCanvas:: cmd not implemented %d\n", cmd);
                  break; 
            case CMD_FIXED_LEN: //Set notes to the length specified in the drummap 
                  if (selectionSize()) { 
                        song->startUndo(); 
                        for (iCItem k = items.begin(); k != items.end(); ++k) { 
                              if (k->second->isSelected()) { 
                                    DEvent* devent =(DEvent*)(k->second); 
                                    MidiEvent* event = (MidiEvent*)devent->event(); 
                                    MidiEvent* newEvent = new MidiEvent(*event);
                                    int instr = drumInmap[event->pitch()];
                                    newEvent->setLenTick(drumMap[instr].len);
                                    midiThread->msgChangeEvent(event, newEvent, devent->part() , false);
                              }
                        }
                        song->endUndo(SC_EVENT_MODIFIED);
                  }
                  break;
            }
      redraw();
      }

//---------------------------------------------------------
//   getTextDrag
//---------------------------------------------------------

QTextDrag* DrumCanvas::getTextDrag(QWidget* parent)
      {
      //---------------------------------------------------
      //   generate event list from selected events
      //---------------------------------------------------

      EventList el;
      int startTick = -1;
      for (iCItem i = items.begin(); i != items.end(); ++i) {
            if (!i->second->isSelected())
                  continue;
            DEvent* ne = (DEvent*)(i->second);
            Event* e = ne->event();
            if (startTick == -1)
                  startTick = e->posTick();
            el.add(e);
            }

      //---------------------------------------------------
      //    write events as XML into tmp file
      //---------------------------------------------------

      FILE* tmp = tmpfile();
      if (tmp == 0) {
            fprintf(stderr, "EventCanvas::copy() fopen failed: %s\n",
               strerror(errno));
            return 0;
            }
      Xml xml(tmp);

      int level = 0;
      for (ciEvent e = el.begin(); e != el.end(); ++e)
            e->second->write(level, xml, -startTick);

      //---------------------------------------------------
      //    read tmp file into QTextDrag Object
      //---------------------------------------------------

      fflush(tmp);
      struct stat f_stat;
      if (fstat(fileno(tmp), &f_stat) == -1) {
            fprintf(stderr, "EventCanvas::copy() fstat failes:<%s>\n",
               strerror(errno));
            fclose(tmp);
            return 0;
            }
      int n = f_stat.st_size;
      char* fbuf  = (char*)mmap(0, n+1, PROT_READ|PROT_WRITE,
         MAP_PRIVATE, fileno(tmp), 0);
      fbuf[n] = 0;
      QTextDrag* drag = new QTextDrag(QString(fbuf), parent);
      drag->setSubtype("eventlist");
      munmap(fbuf, n);
      fclose(tmp);
      return drag;
      }

//---------------------------------------------------------
//   copy
//    cut copy paste
//---------------------------------------------------------

void DrumCanvas::copy()
      {
      QTextDrag* drag = getTextDrag(0);
      if (drag)
            QApplication::clipboard()->setData(drag, QClipboard::Clipboard);
      }

//---------------------------------------------------------
//   paste
//---------------------------------------------------------

int DrumCanvas::pasteAt(const QString& pt, int pos)
      {
      const char* p = pt.latin1();
      Xml xml(p);

      song->startUndo();
      for (;;) {
            Xml::Token token = xml.parse();
            QString tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        song->endUndo(SC_EVENT_INSERTED);
                        return pos;
                  case Xml::TagStart:
                        if (tag == "event") {
                              MidiEvent* e = new MidiEvent();
                              e->read(xml);
                              e->setPosTick(e->posTick() + pos);
                              midiThread->msgAddEvent(e, curPart, false);
                              }
                        else
                              xml.unknown("EventCanvas::paste");
                        break;
                  case Xml::TagEnd:
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   paste
//    paste events
//---------------------------------------------------------

void DrumCanvas::paste()
      {
      QCString subtype("eventlist");
      QMimeSource* ms = QApplication::clipboard()->data();
      QString pt;
      if (!QTextDrag::decode(ms, pt, subtype)) {
            printf("cannot paste: bad data type\n");
            return;
            }
      pasteAt(pt, song->cpos());
      }

//---------------------------------------------------------
//   startDrag
//---------------------------------------------------------

void DrumCanvas::startDrag(CItem* /* item*/, bool copymode)
      {
      QTextDrag* drag = getTextDrag(this);
      if (drag) {
//            QApplication::clipboard()->setData(drag, QClipboard::Clipboard);

            if (copymode)
                  drag->dragCopy();
            else
                  drag->dragMove();
            }
      }

//---------------------------------------------------------
//   dragEnterEvent
//---------------------------------------------------------

void DrumCanvas::dragEnterEvent(QDragEnterEvent* event)
      {
      event->accept(QTextDrag::canDecode(event));
      }

//---------------------------------------------------------
//   dragMoveEvent
//---------------------------------------------------------

void DrumCanvas::dragMoveEvent(QDragMoveEvent*)
      {
//      printf("drag move %x\n", this);
      }

//---------------------------------------------------------
//   dragLeaveEvent
//---------------------------------------------------------

void DrumCanvas::dragLeaveEvent(QDragLeaveEvent*)
      {
//      printf("drag leave\n");
      }

//---------------------------------------------------------
//   dropEvent
//---------------------------------------------------------

void DrumCanvas::viewDropEvent(QDropEvent* event)
      {
      QString text;
      if (event->source() == this) {
            printf("local DROP\n");
            return;
            }
      if (QTextDrag::decode(event, text)) {
//            printf("drop <%s>\n", text.ascii());
            int x = editor->rasterVal(event->pos().x());
            if (x < 0)
                  x = 0;
            pasteAt(text, x);
            }
      }

//---------------------------------------------------------
//   keyPressed
//---------------------------------------------------------

void DrumCanvas::keyPressed(int pitch, bool)
      {
      int port    = track()->outPort();
      int channel = track()->outChannel();

      // play note:
      MidiPlayEvent e(port, channel, 0x90, pitch, 127);
      midiThread->playMidiEvent(&e);
      }

//---------------------------------------------------------
//   keyReleased
//---------------------------------------------------------

void DrumCanvas::keyReleased(int pitch, bool)
      {
      int port    = track()->outPort();
      int channel = track()->outChannel();

      // release note:
      MidiPlayEvent e(port, channel, 0x90, pitch, 0);
      midiThread->playMidiEvent(&e);
      }

//---------------------------------------------------------
//   resizeEvent
//---------------------------------------------------------

void DrumCanvas::resizeEvent(QResizeEvent* ev)
      {
      if (ev->size().width() != ev->oldSize().width())
            emit newWidth(ev->size().width());
      EventCanvas::resizeEvent(ev);
      }


