//=============================================================================
//  MuseScore
//  Music Composition & Notation
//
//  Copyright (C) 2009-2013 Werner Schweer
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2
//  as published by the Free Software Foundation and appearing in
//  the file LICENCE.GPL
//=============================================================================

#include "config.h"
#include "pianoroll.h"
#include "piano.h"
#include "ruler.h"
#include "pianoview.h"
#include "libmscore/staff.h"
#include "libmscore/measure.h"
#include "libmscore/note.h"
#include "libmscore/repeatlist.h"
#include "awl/pitchlabel.h"
#include "awl/pitchedit.h"
#include "awl/poslabel.h"
#include "musescore.h"
#include "libmscore/undo.h"
#include "libmscore/part.h"
#include "libmscore/instrument.h"
#include "seq.h"
#include "preferences.h"
#include "seq.h"
#include "waveview.h"

namespace Ms {

extern bool useFactorySettings;

//---------------------------------------------------------
//   PianorollEditor
//---------------------------------------------------------

PianorollEditor::PianorollEditor(QWidget* parent)
   : QMainWindow(parent)
      {
      setWindowTitle(QString("MuseScore"));

      waveView = 0;
      _score   = 0;
      staff    = 0;

      QWidget* mainWidget = new QWidget;
      QToolBar* tb = addToolBar(tr("Toolbar 1"));
      tb->addAction(getAction("undo"));
      tb->addAction(getAction("redo"));
      tb->addSeparator();
#ifdef HAS_MIDI
      tb->addAction(getAction("midi-on"));
#endif
      tb->addSeparator();

      tb->addAction(getAction("rewind"));
      tb->addAction(getAction("play"));
      tb->addSeparator();

      tb->addAction(getAction("loop"));
      tb->addSeparator();
      tb->addAction(getAction("repeat"));
      QAction* followAction = getAction("follow");
      followAction->setChecked(preferences.followSong);
      tb->addAction(followAction);
      tb->addSeparator();
      tb->addAction(getAction("metronome"));

      showWave = new QAction(tr("Wave"), tb);
      showWave->setToolTip(tr("Show wave display"));
      showWave->setCheckable(true);
      showWave->setChecked(false);
      connect(showWave, SIGNAL(toggled(bool)), SLOT(showWaveView(bool)));
      tb->addAction(showWave);

      //-------------
      tb = addToolBar(tr("Toolbar 2"));
      for (int i = 0; i < VOICES; ++i) {
            QToolButton* b = new QToolButton(this);
            b->setToolButtonStyle(Qt::ToolButtonTextOnly);
            QPalette p(b->palette());
            p.setColor(QPalette::Base, MScore::selectColor[i]);
            b->setPalette(p);
            QAction* a = getAction(voiceActions[i]);
            b->setDefaultAction(a);
            tb->addWidget(b);
            }

      tb->addSeparator();
      tb->addWidget(new QLabel(tr("Cursor:")));
      pos = new Awl::PosLabel;
      pos->setFrameStyle(QFrame::NoFrame | QFrame::Plain);

      tb->addWidget(pos);
      Awl::PitchLabel* pl = new Awl::PitchLabel();
      pl->setFrameStyle(QFrame::NoFrame | QFrame::Plain);
      tb->addWidget(pl);

      tb->addSeparator();
      tb->addWidget(new QLabel(tr("Velocity:")));
      veloType = new QComboBox;
      veloType->addItem(tr("Offset"), int(Note::ValueType::OFFSET_VAL));
      veloType->addItem(tr("User"),   int (Note::ValueType::USER_VAL));
      tb->addWidget(veloType);

      velocity = new QSpinBox;
      velocity->setRange(-1, 127);
      velocity->setSpecialValueText("--");
      velocity->setReadOnly(true);
      tb->addWidget(velocity);

      tb->addWidget(new QLabel(tr("Pitch:")));
      pitch = new Awl::PitchEdit;
      pitch->setReadOnly(true);
      tb->addWidget(pitch);

      tb->addWidget(new QLabel(tr("OnTime:")));
      tb->addWidget((onTime = new QSpinBox));
      onTime->setRange(-2000, 2000);

      tb->addWidget(new QLabel(tr("Len:")));
      tb->addWidget((tickLen = new QSpinBox));
      tickLen->setRange(-2000, 2000);

      //-------------
      qreal xmag = .1;
      ruler = new Ruler;
      ruler->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
      ruler->setFixedHeight(rulerHeight);

      ruler->setMag(xmag, 1.0);

      Piano* piano = new Piano;
      piano->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
      piano->setFixedWidth(pianoWidth);

      gv  = new PianoView;
      gv->scale(xmag, 1.0);
      gv->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
      gv->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

      hsb = new QScrollBar(Qt::Horizontal);
      connect(gv->horizontalScrollBar(), SIGNAL(rangeChanged(int,int)),
         SLOT(rangeChanged(int,int)));

      // layout
      QHBoxLayout* hbox = new QHBoxLayout;
      hbox->setSpacing(0);
      hbox->addWidget(piano);
      hbox->addWidget(gv);

      split = new QSplitter(Qt::Vertical);
      split->setFrameShape(QFrame::NoFrame);

      QWidget* split1 = new QWidget;      // piano - pianoview
      split1->setLayout(hbox);
      split->addWidget(split1);

      QGridLayout* layout = new QGridLayout;
      layout->setContentsMargins(0, 0, 0, 0);
      layout->setSpacing(0);
      layout->setColumnMinimumWidth(0, pianoWidth + 5);
      layout->addWidget(tb,    0, 0, 1, 2);
      layout->addWidget(ruler, 1, 1);
      layout->addWidget(split, 2, 0, 1, 2);
      layout->addWidget(hsb,   3, 1);

      mainWidget->setLayout(layout);
      setCentralWidget(mainWidget);

      connect(gv->verticalScrollBar(), SIGNAL(valueChanged(int)), piano, SLOT(setYpos(int)));

      connect(gv,          SIGNAL(magChanged(double,double)),  ruler, SLOT(setMag(double,double)));
      connect(gv,          SIGNAL(magChanged(double,double)),  piano, SLOT(setMag(double,double)));
      connect(gv,          SIGNAL(pitchChanged(int)),          pl,    SLOT(setPitch(int)));
      connect(gv,          SIGNAL(pitchChanged(int)),          piano, SLOT(setPitch(int)));
      connect(piano,       SIGNAL(pitchChanged(int)),          pl,    SLOT(setPitch(int)));
      connect(gv,          SIGNAL(posChanged(const Pos&)),     pos,   SLOT(setValue(const Pos&)));
      connect(gv,          SIGNAL(posChanged(const Pos&)),     ruler, SLOT(setPos(const Pos&)));
      connect(ruler,       SIGNAL(posChanged(const Pos&)),     pos,   SLOT(setValue(const Pos&)));

      connect(hsb,         SIGNAL(valueChanged(int)),  SLOT(setXpos(int)));
      connect(gv,          SIGNAL(xposChanged(int)),   SLOT(setXpos(int)));
      connect(gv->horizontalScrollBar(), SIGNAL(valueChanged(int)), SLOT(setXpos(int)));

      connect(ruler,       SIGNAL(locatorMoved(int, const Pos&)), SLOT(moveLocator(int, const Pos&)));
      connect(veloType,    SIGNAL(activated(int)),     SLOT(veloTypeChanged(int)));
      connect(velocity,    SIGNAL(valueChanged(int)),  SLOT(velocityChanged(int)));
      connect(onTime,      SIGNAL(valueChanged(int)),  SLOT(onTimeChanged(int)));
      connect(tickLen,     SIGNAL(valueChanged(int)),  SLOT(tickLenChanged(int)));
      connect(gv->scene(), SIGNAL(selectionChanged()), SLOT(selectionChanged()));
      connect(piano,       SIGNAL(keyPressed(int)),    SLOT(keyPressed(int)));
      connect(piano,       SIGNAL(keyReleased(int)),   SLOT(keyReleased(int)));

      if (!useFactorySettings) {
            QSettings settings;
            settings.beginGroup("Pianoroll");
            resize(settings.value("size", QSize(900, 500)).toSize());
            move(settings.value("pos", QPoint(10, 10)).toPoint());
            settings.endGroup();
            }

      QActionGroup* ag = new QActionGroup(this);
      QAction* a = new QAction(this);
      a->setData("delete");
      a->setShortcut(Qt::Key_Delete);
      ag->addAction(a);
      addActions(ag->actions());
      connect(ag, SIGNAL(triggered(QAction*)), SLOT(cmd(QAction*)));
      setXpos(0);
      }

//---------------------------------------------------------
//   ~PianorollEditor
//---------------------------------------------------------

PianorollEditor::~PianorollEditor()
      {
      if (_score)
            _score->removeViewer(this);
      }

//---------------------------------------------------------
//   setStaff
//---------------------------------------------------------

void PianorollEditor::setStaff(Staff* st)
      {
      if ((st && st->score() != _score) || (!st && _score)) {
            if (_score) {
                  _score->removeViewer(this);
                  disconnect(_score, SIGNAL(posChanged(POS,unsigned)), this, SLOT(posChanged(POS,unsigned)));
                  disconnect(_score, SIGNAL(playlistChanged()), this, SLOT(playlistChanged()));
                  }
            _score = st ? st->score() : nullptr;
            if (_score) {
                  _score->addViewer(this);
                  setLocator(POS::CURRENT, _score->pos(POS::CURRENT));
                  setLocator(POS::LEFT,    _score->pos(POS::LEFT));
                  setLocator(POS::RIGHT,   _score->pos(POS::RIGHT));
                  connect(_score, SIGNAL(posChanged(POS,unsigned)), SLOT(posChanged(POS,unsigned)));
                  connect(_score, SIGNAL(playlistChanged()), SLOT(playlistChanged()));
                  }
            }
      staff = st;
      if (staff) {
            setWindowTitle(tr("MuseScore: <%1> Staff: %2").arg(_score->name()).arg(st->idx()));
            TempoMap* tl = _score->tempomap();
            TimeSigMap*  sl = _score->sigmap();
            for (int i = 0; i < 3; ++i)
                  locator[i].setContext(tl, sl);
            pos->setContext(tl, sl);
            showWave->setEnabled(_score->audio() != 0);
            }
      ruler->setScore(_score, locator);
      gv->setStaff(staff, locator);
      updateSelection();
      setEnabled(st != nullptr);
      }

//---------------------------------------------------------
//   writeSettings
//---------------------------------------------------------

void PianorollEditor::writeSettings()
      {
      QSettings settings;
      settings.beginGroup("Pianoroll");
      settings.setValue("size", size());
      settings.setValue("pos", QWidget::pos());
      settings.endGroup();
      }

//---------------------------------------------------------
//   setXpos
//---------------------------------------------------------

void PianorollEditor::setXpos(int x)
      {
      gv->horizontalScrollBar()->setValue(x);
      ruler->setXpos(x);
      if (waveView && showWave->isChecked())
            waveView->setXpos(x);
      }

//---------------------------------------------------------
//   rangeChanged
//---------------------------------------------------------

void PianorollEditor::rangeChanged(int min, int max)
      {
      hsb->setRange(min, max);
      }

//---------------------------------------------------------
//   updateSelection
//---------------------------------------------------------

void PianorollEditor::updateSelection()
      {
      QList<QGraphicsItem*> items = gv->scene()->selectedItems();
      if (items.size() == 1) {
            PianoItem* item = static_cast<PianoItem*>(items[0]);
            if (item->type() == PianoItemType) {
                  Note* note = item->note();
                  NoteEvent* event = item->event();
                  pitch->setEnabled(true);
                  pitch->setValue(note->pitch());
                  onTime->setValue(event->ontime());
                  tickLen->setValue(event->len());
                  updateVelocity(note);
                  }
            }
      bool b = items.size() != 0;
      velocity->setEnabled(b);
      pitch->setEnabled(b);
      veloType->setEnabled(b);
      onTime->setEnabled(b);
      tickLen->setEnabled(b);
      }

//---------------------------------------------------------
//   selectionChanged
//    called if selection in PianoView changed
//---------------------------------------------------------

void PianorollEditor::selectionChanged()
      {
      updateSelection();
      QList<QGraphicsItem*> items = gv->scene()->selectedItems();
      if (items.size() == 1) {
            QGraphicsItem* item = items[0];
            if (item->type() == PianoItemType) {
                  Note* note = static_cast<PianoItem*>(item)->note();
                  _score->select(note, SelectType::SINGLE, 0);
                  }
            }
      else if (items.size() == 0)
            _score->select(0, SelectType::SINGLE, 0);
      else {
            _score->deselectAll();
            for (QGraphicsItem* item : items) {
                  if (item->type() == PianoItemType) {
                        Note* note = static_cast<PianoItem*>(item)->note();
                        _score->select(note, SelectType::ADD, 0);
                        }
                  }
            }
      startTimer(0);    // delayed update
      }

//---------------------------------------------------------
//   timerEvent
//---------------------------------------------------------

void PianorollEditor::timerEvent(QTimerEvent* event)
      {
      killTimer(event->timerId());
      gv->updateNotes();
      updateSelection();
      }

//---------------------------------------------------------
//   changeSelection
//---------------------------------------------------------

void PianorollEditor::changeSelection(SelState)
      {
      gv->scene()->blockSignals(true);
      gv->scene()->clearSelection();
      QList<QGraphicsItem*> il = gv->scene()->items();
      for (QGraphicsItem* item : il) {
            if (item->type() == PianoItemType) {
                  Note* note = static_cast<PianoItem*>(item)->note();
                  item->setSelected(note->selected());
                  }
            }
      gv->scene()->blockSignals(false);
      }

//---------------------------------------------------------
//   veloTypeChanged
//---------------------------------------------------------

void PianorollEditor::veloTypeChanged(int val)
      {
      QList<QGraphicsItem*> items = gv->scene()->selectedItems();
      if (items.size() != 1)
            return;
      QGraphicsItem* item = items[0];
      if (item->type() != PianoItemType)
            return;
      Note* note = static_cast<PianoItem*>(item)->note();
      if (Note::ValueType(val) == note->veloType())
            return;

      _score->undo()->beginMacro();
      _score->undo(new ChangeVelocity(note, Note::ValueType(val), note->veloOffset()));
      _score->undo()->endMacro(_score->undo()->current()->childCount() == 0);
      updateVelocity(note);
      }

//---------------------------------------------------------
//   updateVelocity
//---------------------------------------------------------

void PianorollEditor::updateVelocity(Note* note)
      {
      Note::ValueType vt = note->veloType();
      if (vt != Note::ValueType(veloType->currentIndex())) {
            veloType->setCurrentIndex(int(vt));
            switch(vt) {
                  case Note::ValueType::USER_VAL:
                        velocity->setReadOnly(false);
                        velocity->setSuffix("");
                        velocity->setRange(0, 127);
                        break;
                  case Note::ValueType::OFFSET_VAL:
                        velocity->setReadOnly(false);
                        velocity->setSuffix("%");
                        velocity->setRange(-200, 200);
                        break;
                  }
            }
      switch(vt) {
            case Note::ValueType::USER_VAL:
                  // TODO velocity->setValue(note->velocity());
                  break;
            case Note::ValueType::OFFSET_VAL:
                  velocity->setValue(note->veloOffset());
                  break;
            }
      }

//---------------------------------------------------------
//   velocityChanged
//---------------------------------------------------------

void PianorollEditor::velocityChanged(int val)
      {
      QList<QGraphicsItem*> items = gv->scene()->selectedItems();
      if (items.size() != 1)
            return;
      QGraphicsItem* item = items[0];
      if (item->type() != PianoItemType)
            return;
      Note* note = static_cast<PianoItem*>(item)->note();
      Note::ValueType vt = note->veloType();

      if (vt == Note::ValueType::OFFSET_VAL)
            return;

      _score->undo()->beginMacro();
      _score->undo(new ChangeVelocity(note, vt, val));
      _score->undo()->endMacro(_score->undo()->current()->childCount() == 0);
      }

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

void PianorollEditor::keyPressed(int pitch)
      {
      seq->startNote(staff->part()->instrument()->channel(0)->channel, pitch, 80, 0, 0.0);
      }

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

void PianorollEditor::keyReleased(int /*pitch*/)
      {
      seq->stopNotes();
      }

//---------------------------------------------------------
//   heartBeat
//---------------------------------------------------------

void PianorollEditor::heartBeat(Seq* seq)
      {
      unsigned tick = seq->getCurTick();
      if (score()->repeatList())
            tick = score()->repeatList()->utick2tick(tick);
      if (locator[0].tick() != tick) {
            posChanged(POS::CURRENT, tick);
            if (preferences.followSong)
                  gv->ensureVisible(tick);
            }
      }

//---------------------------------------------------------
//   moveLocator
//---------------------------------------------------------

void PianorollEditor::moveLocator(int i, const Pos& pos)
      {
      if (locator[i].valid())
            score()->setPos(POS(i), pos.tick());
      }

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

void PianorollEditor::cmd(QAction* a)
      {
      score()->startCmd();
      if (a->data() == "delete") {
            QList<QGraphicsItem*> items = gv->items();
            foreach(QGraphicsItem* item, items) {
                  if (item->type() == PianoItemType) {
                        Note* note = static_cast<PianoItem*>(item)->note();
                        score()->deleteItem(note);
                        }
                  }
            }

      gv->setStaff(staff, locator);
      score()->endCmd();
      }

//---------------------------------------------------------
//   dataChanged
//---------------------------------------------------------

void PianorollEditor::dataChanged(const QRectF&)
      {
      }

//---------------------------------------------------------
//   adjustCanvasPosition
//---------------------------------------------------------

void PianorollEditor::adjustCanvasPosition(const Element*, bool)
      {
      }

//---------------------------------------------------------
//   removeScore
//---------------------------------------------------------

void PianorollEditor::removeScore()
      {
      setStaff(0);
      }

//---------------------------------------------------------
//   changeEditElement
//---------------------------------------------------------

void PianorollEditor::changeEditElement(Element*)
      {
      }

//---------------------------------------------------------
//   cursor
//---------------------------------------------------------

QCursor PianorollEditor::cursor() const
      {
      return QCursor();
      }

//---------------------------------------------------------
//   setCursor
//---------------------------------------------------------

void PianorollEditor::setCursor(const QCursor&)
      {
      }

//---------------------------------------------------------
//   gripCount
//---------------------------------------------------------

int PianorollEditor::gripCount() const
      {
      return 0;
      }

//---------------------------------------------------------
//   getGrip
//---------------------------------------------------------

const QRectF& PianorollEditor::getGrip(Grip) const
      {
      static QRectF r;
      return r;
      }

//---------------------------------------------------------
//   matrix
//---------------------------------------------------------

const QTransform& PianorollEditor::matrix() const
      {
      static QTransform t;
      return t;
      }

//---------------------------------------------------------
//   setDropRectangle
//---------------------------------------------------------

void PianorollEditor::setDropRectangle(const QRectF&)
      {
      }

//---------------------------------------------------------
//   cmdAddSlur
//---------------------------------------------------------

void PianorollEditor::cmdAddSlur(Note*, Note*)
      {
      }

//---------------------------------------------------------
//   startEdit
//---------------------------------------------------------

void PianorollEditor::startEdit()
      {
      }

//---------------------------------------------------------
//   startEdit
//---------------------------------------------------------

void PianorollEditor::startEdit(Element*, Grip)
      {
      }

//---------------------------------------------------------
//   elementNear
//---------------------------------------------------------

Element* PianorollEditor::elementNear(QPointF)
      {
      return 0;
      }

//---------------------------------------------------------
//   updateAll
//---------------------------------------------------------

void PianorollEditor::updateAll()
      {
//      startTimer(0);    // delayed update
//      gv->updateNotes();
//      gv->update();
      }

void PianorollEditor::playlistChanged()
      {
      startTimer(0);    // delayed update
      }

//---------------------------------------------------------
//   showWavView
//---------------------------------------------------------

void PianorollEditor::showWaveView(bool val)
      {
      if (val) {
            if (waveView == 0) {
                  waveView = new WaveView;
                  connect(gv, SIGNAL(magChanged(double,double)), waveView, SLOT(setMag(double,double)));
                  connect(gv, SIGNAL(posChanged(const Pos&)), waveView,   SLOT(setValue(const Pos&)));
                  waveView->setAudio(_score->audio());
                  waveView->setScore(_score, locator);
                  split->addWidget(waveView);
                  waveView->setMag(ruler->xmag(), 1.0);
                  waveView->setXpos(ruler->xpos());
                  }
            waveView->setVisible(true);
            }
      else {
            if (waveView)
                  waveView->setVisible(false);
            }
      }

//---------------------------------------------------------
//   posChanged
//    position in score has changed
//---------------------------------------------------------

void PianorollEditor::posChanged(POS pos, unsigned tick)
      {
      if (locator[int(pos)].tick() == unsigned(tick))
            return;
      setLocator(pos, tick);
      gv->moveLocator(int(pos));
      if (waveView)
            waveView->moveLocator(int(pos));
      ruler->update();
      }

//---------------------------------------------------------
//   onTimeChanged
//---------------------------------------------------------

void PianorollEditor::onTimeChanged(int val)
      {
      QList<QGraphicsItem*> items = gv->scene()->selectedItems();
      if (items.size() != 1)
            return;
      QGraphicsItem* item = items[0];
      if (item->type() != PianoItemType)
            return;
      PianoItem* pi = static_cast<PianoItem*>(item);
      Note* note       = pi->note();
      NoteEvent* event = pi->event();
      if (event->ontime() == val)
            return;

      NoteEvent ne = *event;
      ne.setOntime(val);
      _score->startCmd();
      _score->undo(new ChangeNoteEvent(note, event, ne));
      _score->endCmd();
      }

//---------------------------------------------------------
//   tickLenChanged
//---------------------------------------------------------

void PianorollEditor::tickLenChanged(int val)
      {
      QList<QGraphicsItem*> items = gv->scene()->selectedItems();
      if (items.size() != 1)
            return;
      QGraphicsItem* item = items[0];
      if (item->type() != PianoItemType)
            return;
      PianoItem* pi = static_cast<PianoItem*>(item);
      Note* note       = pi->note();
      NoteEvent* event = pi->event();
      if (event->len() == val)
            return;

      NoteEvent ne = *event;
      ne.setLen(val);
      _score->startCmd();
      _score->undo(new ChangeNoteEvent(note, event, ne));
      _score->endCmd();
      }

}
