//=============================================================================
//  MuseScore
//  Music Composition & Notation
//
//  Copyright (C) 2013 Werner Schweer and others
//
//  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 LICENSE.GPL
//=============================================================================

#include "exampleview.h"
#include "preferences.h"
#include "libmscore/score.h"
#include "libmscore/element.h"
#include "libmscore/page.h"
#include "libmscore/system.h"
#include "libmscore/icon.h"
#include "libmscore/chord.h"
#include "libmscore/xml.h"

namespace Ms {

//---------------------------------------------------------
//   ExampleView
//---------------------------------------------------------

ExampleView::ExampleView(QWidget* parent)
   : QFrame(parent)
      {
      _score = 0;
      setAcceptDrops(true);
      setFocusPolicy(Qt::StrongFocus);
      resetMatrix();
      _fgPixmap = nullptr;
      if (preferences.fgUseColor)
            _fgColor = preferences.fgColor;
      else {
            _fgPixmap = new QPixmap(preferences.fgWallpaper);
            if (_fgPixmap == 0 || _fgPixmap->isNull())
                  qDebug("no valid pixmap %s", qPrintable(preferences.fgWallpaper));
            }
      // setup drag canvas state
      sm          = new QStateMachine(this);
      QState* stateActive = new QState;

      QState* s1 = new QState(stateActive);
      s1->setObjectName("example-normal");
      s1->assignProperty(this, "cursor", QCursor(Qt::ArrowCursor));

      QState* s = new QState(stateActive);
      s->setObjectName("example-drag");
      s->assignProperty(this, "cursor", QCursor(Qt::SizeAllCursor));
      QEventTransition* cl = new QEventTransition(this, QEvent::MouseButtonRelease);
      cl->setTargetState(s1);
      s->addTransition(cl);
      s1->addTransition(new DragTransitionExampleView(this));


      sm->addState(stateActive);
      stateActive->setInitialState(s1);
      sm->setInitialState(stateActive);

      sm->start();
      }

//---------------------------------------------------------
//   ~ExampleView
//---------------------------------------------------------

ExampleView::~ExampleView()
      {
      if (_fgPixmap)
            delete _fgPixmap;
      }

//---------------------------------------------------------
//   resetMatrix
//    used to reset scrolling in case time signature num or denom changed
//---------------------------------------------------------

void ExampleView::resetMatrix()
      {
      double mag = 0.9 * guiScaling * (DPI_DISPLAY / DPI);  // 90% of nominal
      qreal _spatium = SPATIUM20 * mag;
      // example would normally be 10sp from top of page; this leaves 3sp margin above
//      _matrix  = QTransform(mag, 0.0, 0.0, mag, _spatium, -_spatium * 7.0);
      _matrix  = QTransform(mag, 0.0, 0.0, mag, _spatium, -_spatium * 10.0);
      imatrix  = _matrix.inverted();
      }

void ExampleView::layoutChanged()
      {
      }

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

void ExampleView::updateAll()
      {
      update();
      }

void ExampleView::adjustCanvasPosition(const Element* /*el*/, bool /*playBack*/)
      {
      }

//---------------------------------------------------------
//   setScore
//---------------------------------------------------------

void ExampleView::setScore(Score* s)
      {
      delete _score;
      _score = s;
      _score->addViewer(this);
      _score->setLayoutMode(LayoutMode::LINE);
      _score->doLayout();
      update();
      }

void ExampleView::removeScore()
      {
      }

void ExampleView::changeEditElement(Element*)
      {
      }

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

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

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

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

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

void ExampleView::cmdAddSlur(Note* /*firstNote*/, Note* /*lastNote*/)
      {
      }

void ExampleView::startEdit()
      {
      }

void ExampleView::startEdit(Element*, Grip /*startGrip*/)
      {
      }

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

void ExampleView::drawBackground(QPainter* p, const QRectF& r) const
      {
      if (_fgPixmap == 0 || _fgPixmap->isNull())
            p->fillRect(r, _fgColor);
      else {
            p->drawTiledPixmap(r, *_fgPixmap, r.topLeft()
               - QPoint(lrint(_matrix.dx()), lrint(_matrix.dy())));
            }
      }

//---------------------------------------------------------
//   drawElements
//---------------------------------------------------------

void ExampleView::drawElements(QPainter& painter, const QList<Element*>& el)
      {
      for (Element* e : el) {
            e->itemDiscovered = 0;
            QPointF pos(e->pagePos());
            painter.translate(pos);
            e->draw(&painter);
            painter.translate(-pos);
            }
      }

//---------------------------------------------------------
//   paintEvent
//---------------------------------------------------------

void ExampleView::paintEvent(QPaintEvent* ev)
      {
      if (_score) {
            QPainter p(this);
            p.setRenderHint(QPainter::Antialiasing, preferences.antialiasedDrawing);
            p.setRenderHint(QPainter::TextAntialiasing, true);
            const QRect r(ev->rect());

            drawBackground(&p, r);

            p.setTransform(_matrix);
            QRectF fr = imatrix.mapRect(QRectF(r));

            QRegion r1(r);
            Page* page = _score->pages().front();
            QList<Element*> ell = page->items(fr);
            qStableSort(ell.begin(), ell.end(), elementLessThan);
            drawElements(p, ell);
            }
      QFrame::paintEvent(ev);
      }

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

void ExampleView::dragEnterEvent(QDragEnterEvent* event)
      {
      const QMimeData* data = event->mimeData();
      if (data->hasFormat(mimeSymbolFormat)) {
            event->acceptProposedAction();

            QByteArray a = data->data(mimeSymbolFormat);

// qDebug("ExampleView::dragEnterEvent Symbol: <%s>", a.data());

            XmlReader e(score(), a);
            QPointF dragOffset;
            Fraction duration;  // dummy
            ElementType type = Element::readType(e, &dragOffset, &duration);

            dragElement = Element::create(type, _score);
            if (dragElement) {
                  dragElement->setParent(0);
                  dragElement->read(e);
                  dragElement->layout();
                  }
            return;
            }
      }

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

void ExampleView::dragLeaveEvent(QDragLeaveEvent*)
      {
      if (dragElement) {
            delete dragElement;
            dragElement = 0;
            }
      setDropTarget(0);
      }

//---------------------------------------------------------
//   moveElement
//---------------------------------------------------------

static void moveElement(void* data, Element* e)
      {
      QPointF* pos = (QPointF*)data;
      e->score()->addRefresh(e->canvasBoundingRect());
      e->setPos(*pos);
      e->score()->addRefresh(e->canvasBoundingRect());
      }

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

void ExampleView::dragMoveEvent(QDragMoveEvent* event)
      {
      event->acceptProposedAction();

      if (!dragElement || dragElement->type() != ElementType::ICON)
            return;

      QPointF pos(imatrix.map(QPointF(event->pos())));
      QList<Element*> el = elementsAt(pos);
      bool found = false;
      foreach(const Element* e, el) {
            if (e->type() == ElementType::NOTE) {
                  setDropTarget(const_cast<Element*>(e));
                  found = true;
                  break;
                  }
            }
      if (!found)
            setDropTarget(0);
      dragElement->scanElements(&pos, moveElement, false);
      _score->update();
      return;
      }

//---------------------------------------------------------
//   setDropTarget
//---------------------------------------------------------

void ExampleView::setDropTarget(const Element* el)
      {
      if (dropTarget != el) {
            if (dropTarget) {
                  dropTarget->setDropTarget(false);
                  dropTarget = 0;
                  }
            dropTarget = el;
            if (dropTarget) {
                  dropTarget->setDropTarget(true);
                  }
            }
      if (!dropAnchor.isNull()) {
            QRectF r;
            r.setTopLeft(dropAnchor.p1());
            r.setBottomRight(dropAnchor.p2());
            dropAnchor = QLineF();
            }
      if (dropRectangle.isValid()) {
            dropRectangle = QRectF();
            }
      update();
      }

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

void ExampleView::dropEvent(QDropEvent* event)
      {
      QPointF pos(imatrix.map(QPointF(event->pos())));

      if (!dragElement)
           return;
      if (dragElement->type() != ElementType::ICON) {
            delete dragElement;
            dragElement = 0;
            return;
            }
      foreach (Element* e, elementsAt(pos)) {
            if (e->type() == ElementType::NOTE) {
                  Icon* icon = static_cast<Icon*>(dragElement);
                  Chord* chord = static_cast<Note*>(e)->chord();
                  emit beamPropertyDropped(chord, icon);
                  switch (icon->iconType()) {
                        case IconType::SBEAM:
                              chord->setBeamMode(Beam::Mode::BEGIN);
                              break;
                        case IconType::MBEAM:
                              chord->setBeamMode(Beam::Mode::AUTO);
                              break;
                        case IconType::BEAM32:
                              chord->setBeamMode(Beam::Mode::BEGIN32);
                              break;
                        case IconType::BEAM64:
                              chord->setBeamMode(Beam::Mode::BEGIN64);
                              break;
                        default:
                              break;
                        }
                  score()->doLayout();
                  break;
                  }
            }
      event->acceptProposedAction();
      delete dragElement;
      dragElement = 0;
      setDropTarget(0);
      }

//---------------------------------------------------------
//   mousePressEvent
//---------------------------------------------------------

void ExampleView::mousePressEvent(QMouseEvent* event)
      {
      startMove  = imatrix.map(QPointF(event->pos()));
      QPointF pos(imatrix.map(QPointF(event->pos())));

      foreach (Element* e, elementsAt(pos)) {
            if (e->type() == ElementType::NOTE) {
                  emit noteClicked(static_cast<Note*>(e));
                  break;
                  }
            }
      }

//---------------------------------------------------------
//   sizeHint
//---------------------------------------------------------

QSize ExampleView::sizeHint() const
      {
      qreal mag = 0.9 * guiScaling * (DPI_DISPLAY / DPI);
      qreal _spatium = SPATIUM20 * mag;
      // staff is 4sp tall with 3sp margin above; this leaves 3sp margin below
      qreal height = 10.0 * _spatium;
      if (score() && score()->pages().size() > 0)
            height = score()->pages()[0]->tbbox().height() * mag + (6 * _spatium);
      return QSize(1000 * mag, height);
      }

//---------------------------------------------------------
//   dragExampleView
//     constrained scrolling ensuring that this ExampleView won't be moved past the borders of its QFrame
//---------------------------------------------------------

void ExampleView::dragExampleView(QMouseEvent* ev)
      {
      QPoint d = ev->pos() - _matrix.map(startMove).toPoint();
      int dx   = d.x();
      if (dx == 0)
            return;

      constraintCanvas(&dx);

      // Perform the actual scrolling
      _matrix.setMatrix(_matrix.m11(), _matrix.m12(), _matrix.m13(), _matrix.m21(),
         _matrix.m22(), _matrix.m23(), _matrix.dx()+dx, _matrix.dy(), _matrix.m33());
      imatrix = _matrix.inverted();
      scroll(dx, 0);
      }

void DragTransitionExampleView::onTransition(QEvent* e)
      {
      QStateMachine::WrappedEvent* we = static_cast<QStateMachine::WrappedEvent*>(e);
      QMouseEvent* me = static_cast<QMouseEvent*>(we->event());
      canvas->dragExampleView(me);
      }

//---------------------------------------------------------
//   wheelEvent
//---------------------------------------------------------

void ExampleView::wheelEvent(QWheelEvent* event)
       {
      QPoint pixelsScrolled = event->pixelDelta();
      QPoint stepsScrolled = event->angleDelta();
      int dx = 0, dy = 0;
      if (!pixelsScrolled.isNull()) {
            dx = pixelsScrolled.x();
            dy = pixelsScrolled.y();
            }
      else if (!stepsScrolled.isNull()) {
            dx = static_cast<qreal>(stepsScrolled.x()) * qMax(2, width() / 10) / 120;
            dy = static_cast<qreal>(stepsScrolled.y()) * qMax(2, height() / 10) / 120;
            }

      if (dx == 0) {
            if (dy == 0)
                  return;
            else
                  dx = dy;
            }

      constraintCanvas(&dx);

      _matrix.setMatrix(_matrix.m11(), _matrix.m12(), _matrix.m13(), _matrix.m21(),
         _matrix.m22(), _matrix.m23(), _matrix.dx()+dx, _matrix.dy(), _matrix.m33());
      imatrix = _matrix.inverted();
      scroll(dx, 0);
      }

//-----------------------------------------------------------------------------
//   constraintCanvas
//-----------------------------------------------------------------------------

void ExampleView::constraintCanvas (int* dxx)
      {
      int dx = *dxx;

      Q_ASSERT(_score->pages().front()->system(0)); // should exist if doLayout ran

      // form rectangle bounding the the system with a spatium margin and translate relative to view space
      qreal xstart = _score->pages().front()->system(0)->bbox().left() - SPATIUM20;
      qreal xend = _score->pages().front()->system(0)->bbox().right() + 2.0 * SPATIUM20;
      QRectF systemScaledViewRect(xstart * _matrix.m11(), 0, xend * _matrix.m11(), 0);
      systemScaledViewRect.translate(_matrix.dx(), 0);

      qreal frameWidth = static_cast<QFrame*>(this)->frameRect().width();

      // constrain the dx of scrolling so that this ExampleView won't be moved past the borders of its QFrame
      if (dx > 0) {
            // when moving right, ensure the left edge of systemScaledViewRect won't be right of frame's left edge
            if (systemScaledViewRect.left() + dx > 0)
                  dx = -systemScaledViewRect.left();
            }
      else {
            // never move left if entire system already fits entirely within the frame
            if (systemScaledViewRect.width() < frameWidth)
                  dx = 0;
            // when moving left, ensure the right edge of systemScaledViewRect won't be left of frame's right edge
            else if (systemScaledViewRect.right() + dx < frameWidth)
                        dx = frameWidth - systemScaledViewRect.right();
            }

      *dxx = dx;
      }
}

