// Copyright (c) 2008  GeometryFactory Sarl (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL: https://github.com/CGAL/cgal/blob/v6.0.2/GraphicsView/include/CGAL/Qt/GraphicsViewNavigation_impl.h $
// $Id: include/CGAL/Qt/GraphicsViewNavigation_impl.h e13ef800cb7 $
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s)     : Andreas Fabri <Andreas.Fabri@geometryfactory.com>
//                 Laurent Rineau <Laurent.Rineau@geometryfactory.com>

#ifdef CGAL_HEADER_ONLY
#define CGAL_INLINE_FUNCTION inline

#include <CGAL/license/GraphicsView.h>

#else
#define CGAL_INLINE_FUNCTION
#endif

#include <CGAL/Qt/GraphicsViewNavigation.h>
#include <CGAL/Qt/utility.h> // for mapToScene(QGraphicsView*, QRect)
#include <cmath>
#include <iostream>
#include <boost/format.hpp>

#include <QEvent>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QFlags>
#include <QScrollBar>

namespace CGAL {
namespace Qt {

  CGAL_INLINE_FUNCTION
  GraphicsViewNavigation::GraphicsViewNavigation()
    : rectItem(new QGraphicsRectItem),
      dragging(false)
  {
    QColor rect_color(250, 221, 0);
    rect_color.setAlpha(50);
    rectItem->setBrush(rect_color);
    rect_color.setAlpha(255);
    rectItem->setPen(QPen(rect_color, 0, ::Qt::DashLine));
    rectItem->hide();
    rectItem->setZValue(10000);
  }

  CGAL_INLINE_FUNCTION
  GraphicsViewNavigation::~GraphicsViewNavigation()
  {
    delete rectItem;
  }

  CGAL_INLINE_FUNCTION
  bool
  GraphicsViewNavigation::eventFilter(QObject *obj, QEvent *event)
  {
    QGraphicsView* v = qobject_cast<QGraphicsView*>(obj);
    if(v == nullptr) {
      QWidget* viewport = qobject_cast<QWidget*>(obj);
      if(viewport == nullptr) {
        return false;
      }
      v = qobject_cast<QGraphicsView*>(viewport->parent());
      if(v == nullptr) {
        return false;
      }
    }
    switch(event->type())
    {
    case QEvent::KeyPress: {
      QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
      int offset = 10;
      if( (keyEvent->modifiers() & ::Qt::ShiftModifier)
          || (keyEvent->modifiers() & ::Qt::ControlModifier) ) {
        offset = 20;
      }
      switch (keyEvent->key()) {
      case ::Qt::Key_Up:
        translateView(v, 0, -offset);
        break;
      case ::Qt::Key_Down:
        translateView(v, 0, offset);
        break;
      case ::Qt::Key_Left:
        translateView(v, -offset, 0);
        break;
      case ::Qt::Key_Right:
        translateView(v, offset, 0);
        break;
      case ::Qt::Key_PageUp:
        v->rotate(-6);
        break;
      case ::Qt::Key_PageDown:
        v->rotate(6);
        break;
      case ::Qt::Key_Plus:
        scaleView(v, 1.2);
        break;
      case ::Qt::Key_Minus:
        scaleView(v, 1 / 1.2);
        break;
      case ::Qt::Key_Control:
        cursor_backup = v->cursor();
        v->setCursor(::Qt::CrossCursor);
        CGAL_FALLTHROUGH;
      default:
        return false;
      }
      //         display_parameters();
      return true;
      break;
    } // end case KeyPress
    case QEvent::KeyRelease: {
      QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
      if(keyEvent->key() == ::Qt::Key_Control) {
        if(rectItem->isVisible() ) {
          dragging = false;
          v->scene()->removeItem(rectItem);
          rectItem->hide();
        }
        v->setCursor(cursor_backup);
        return true;
      }
      return false;
      break;
    } // end case KeyRelease
    case QEvent::Wheel: {
      QWheelEvent *wheelEvent = static_cast<QWheelEvent*>(event);
      double zoom_ratio = 240.0;
      if( (wheelEvent->modifiers() & ::Qt::ShiftModifier)
          || (wheelEvent->modifiers() & ::Qt::ControlModifier) ) {
        zoom_ratio = 120.0;
      }
      scaleView(v, pow((double)2, -wheelEvent->angleDelta().y() / zoom_ratio));

      //         display_parameters();
      return true;
      break;
    } // end case Wheel
    case QEvent::MouseButtonPress: {
      QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
      if( (mouseEvent->modifiers() == (::Qt::ControlModifier | ::Qt::ShiftModifier))
          && mouseEvent->button() == ::Qt::RightButton )
      {
        QPoint offset = mouseEvent->pos() - v->viewport()->rect().center();
        translateView(v, offset.x(), offset.y());
        return true;
      }
      else if( mouseEvent->modifiers() == ::Qt::ControlModifier ) {
        if(mouseEvent->button() == ::Qt::LeftButton) {
          rect_first_point = v->mapToScene(mouseEvent->pos());
          rectItem->setRect(QRectF(rect_first_point, QSizeF(0.,0.)));
          rectItem->show();
          v->scene()->addItem(rectItem);
          return true;
        }
        else if( mouseEvent->button() == ::Qt::RightButton) {
          dragging = true;
          dragging_start = v->mapToScene(mouseEvent->pos());
          v->setCursor(::Qt::ClosedHandCursor);
          return true;
        }
      }
      return false;
      break;
    } // end case MouseRelease
    case QEvent::MouseMove: {
      QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
      QPointF pos = v->mapToScene(mouseEvent->pos());
      QString xy = QString(" ") + QString::number(pos.x(),'g', 6) + " , " + QString::number(pos.y(),'g', 6) + " ";
      Q_EMIT mouseCoordinates(xy);
      if(rectItem->isVisible()) {
        QPointF size = v->mapToScene(mouseEvent->pos());
        size = size - rect_first_point;
        rectItem->setRect(rect_first_point.x(),
                          rect_first_point.y(),
                          size.x(),
                          size.y());
      }
      if( dragging )
      {
//         std::cerr << boost::format("mouseMove: globalpos=(%1%, %2%)\n"
//                                    "           pos=(%3%, %4%)\n"
//                                    "           sender=%5% (class %6%), parent class %7%\n")
//           % mouseEvent->globalPos().x()
//           % mouseEvent->globalPos().y()
//           % mouseEvent->pos().x()
//           % mouseEvent->pos().y()
//           % (&*obj)
//           % obj->metaObject()->className()
//           % obj->parent()->metaObject()->className();

//         drag_to(mouseEvent->pos());
      }
      break;
    } // end MouseMove
    case QEvent::MouseButtonRelease: {
      QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
      if(rectItem->isVisible() && mouseEvent->button() == ::Qt::LeftButton){
        v->setSceneRect(v->sceneRect() | rectItem->rect());
        v->fitInView(rectItem->rect(), ::Qt::KeepAspectRatio);
        v->scene()->removeItem(rectItem);
        rectItem->hide();
        return true;
      }
      else if(  mouseEvent->button() == ::Qt::RightButton ) {
        if(dragging) {
          dragging = false;
          drag_to(v, mouseEvent->pos());
          v->setCursor(cursor_backup);
          return true;
        }
      }
      return false;
      break;
    } // end MouseRelease
    default:
      return false;
    } // end switch
    return false;
  }


  CGAL_INLINE_FUNCTION
  void
  GraphicsViewNavigation::scaleView(QGraphicsView* v, qreal scaleFactor)
  {
    QPointF center = v->mapToScene(v->viewport()->rect().center());
//     qreal factor = v->matrix().scale(scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width();
    //if (factor < 0.001 || factor > 2000)
    //    return;

    v->scale(scaleFactor, scaleFactor);
    QPoint offset = v->mapFromScene(center) - v->viewport()->rect().center();
    translateView(v, offset.x(), offset.y());
  }

  CGAL_INLINE_FUNCTION
  void GraphicsViewNavigation::drag_to(QGraphicsView* v, QPoint new_pos)
  {
    QPoint dragging_start_in_view = v->mapFromScene(dragging_start);
    QPoint offset = new_pos - dragging_start_in_view;
//     std::cerr << boost::format("drag_to: origin=(%1%, %2%)\n"
//                                "         offset=(%3%, %4%)\n")
//       % dragging_start_in_view.x() % dragging_start_in_view.y()
//       % offset.x() % offset.y();
    translateView(v, -offset.x(), -offset.y());
    dragging_start_in_view = v->mapFromScene(dragging_start);
//     std::cerr << boost::format("         after=(%1%, %2%)\n")
//       % dragging_start_in_view.x() % dragging_start_in_view.y();
  }

  CGAL_INLINE_FUNCTION
  void GraphicsViewNavigation::translateView(QGraphicsView* v, int dx,  int dy)
  {
    if( dx == 0 && dy == 0 ) {
      return;
    }

    int horizontalScrollBarValue = v->horizontalScrollBar()->value();
    int verticalScrollBarValue = v->verticalScrollBar()->value();

    if( (horizontalScrollBarValue + dx <=
         v->horizontalScrollBar()->maximum()) &&
        (horizontalScrollBarValue + dx >=
         v->horizontalScrollBar()->minimum()) &&
        (verticalScrollBarValue + dy <=
         v->verticalScrollBar()->maximum()) &&
        (verticalScrollBarValue + dy >=
         v->verticalScrollBar()->minimum()) )
    {
      v->horizontalScrollBar()->setValue(horizontalScrollBarValue + dx);
      v->verticalScrollBar()->setValue(verticalScrollBarValue + dy);
    }
    else
    {
      QRect vp_rect = v->viewport()->rect();
      QPointF new_center = v->mapToScene(vp_rect.center() + QPoint(dx, dy));
      vp_rect |= vp_rect.translated(dx, dy);
      QRectF rect = mapToScene(v, vp_rect);
      v->setSceneRect(v->sceneRect() | rect);
      v->centerOn(new_center);

      // QGraphicsView::centerOn makes rounding errors.
      // The following two "if" make them unnoticeable when dx==0 or dy==0.
      if(dx == 0) {
        v->horizontalScrollBar()->setValue(horizontalScrollBarValue);
      }
      if(dy == 0) {
        v->verticalScrollBar()->setValue(verticalScrollBarValue);
      }
    }
//     display_parameters();
  }

  CGAL_INLINE_FUNCTION
  void GraphicsViewNavigation::display_parameters(QGraphicsView* v)
  {
    std::cerr <<
      boost::format("matrix translation=(%1%, %2%)\n"
                    "       rotation=(%3% - %4% )\n"
                    "                (%5% - %6% )\n")
      % v->transform().dx()
      % v->transform().dy()
      % v->transform().m11()
      % v->transform().m12()
      % v->transform().m21()
      % v->transform().m22();

    QRect vp_rect = v->viewport()->rect();
    QPoint vp_top_left = vp_rect.topLeft();
    QPoint vp_bottom_right = vp_rect.bottomRight();
    QPointF top_left = v->mapToScene(vp_top_left);
    QPointF bottom_right = v->mapToScene(vp_bottom_right);

    std::cerr <<
      boost::format("view=(%1% - %2%) x (%3% - %4%)\n")
      % top_left.x() % bottom_right.x()
      % top_left.y() % bottom_right.y();
    std::cerr <<
      boost::format("viewport=(%1% - %2%) x (%3% - %4%)\n")
      % vp_top_left.x() % vp_bottom_right.x()
      % vp_top_left.y() % vp_bottom_right.y();
    std::cerr <<
      boost::format("scrollbars=(%1% <= %2% <= %3%) x (%4% <= %5% <= %6%)\n")
      % v->horizontalScrollBar()->minimum()
      % v->horizontalScrollBar()->value()
      % v->horizontalScrollBar()->maximum()
      % v->verticalScrollBar()->minimum()
      % v->verticalScrollBar()->value()
      % v->verticalScrollBar()->maximum();
  }

} // namespace Qt
} // namespace CGAL

