/***
   This file is part of fly-ocr.

   Copyright (C) 2016-2020 Sandro Mani <manisandro@gmail.com>
   Copyright (C) 2021 LLC "RusBITech-Astra" (https://astralinux.ru)

    Based on gImageReader source code by Sandro Mani under the GNU
    General Public License (GPL) version 3 or later

   Foobar is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Foobar is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with fly-ocr. If not, see <https://www.gnu.org/licenses/>.
***/

#ifndef DISPLAYER_H
#define DISPLAYER_H

#include <QFutureWatcher>
#include <QGraphicsRectItem>
#include <QGraphicsView>
#include <QImage>
#include <QMap>
#include <QTimer>
#include <memory>
#include "sourcedocumentmodel.h"

class QActionGroup;
class DisplayerTool;
class DisplayRenderer;
class MainWindowUi;
class GraphicsScene;

class Displayer : public QGraphicsView
{
    Q_OBJECT
public:
    Displayer(const MainWindowUi* _ui, QWidget* parent = nullptr);
    ~Displayer();
    void setTool(DisplayerTool* tool);
    bool setPage(std::shared_ptr<SourceDocumentPage> page);
    bool setup(const int* page = nullptr, const int* resolution = nullptr, const double* angle = nullptr);
    int currentPage() const;
    int pageCount() const;
    int currentResolution() const;
    double currentAngle() const;
    double currentScale() const;
    QString currentImage() const;
    QImage getImage(const QRectF& rect);
    QRectF getSceneBoundingRect() const;
    QPointF mapToSceneClamped(const QPoint& p) const;
    bool hasMultipleOCRAreas();
    QList<QImage> getOCRAreas();
    bool allowAutodetectOCRAreas() const;
    void setCursor(const QCursor& cursor);
    void unsetCursor();
    void setBlockAutoscale(bool block);

signals:
    void viewportChanged();
    void imageChanged();


public slots:
    void autodetectOCRAreas();

private slots:
    void checkViewportChanged();
    void scaleImage();
    void queueRenderImage();
    bool renderImage();
    void rotate90();
    void setAngle(double angle);
    void setRotateMode(QAction* action);
    void setScaledImage(const QImage& image);
    void zoomIn();
    void zoomOut();
    void zoomFit();
    void zoomOriginal();
    void zoomFitWidth();

private:
    enum class RotateMode { CurrentPage, AllPages } m_rotateMode = RotateMode::CurrentPage;
    enum class Zoom { In, Out, FitPage, FitWidth, Original };
    const MainWindowUi* m_ui;
    GraphicsScene* m_scene;
    std::shared_ptr<SourceDocumentPage> m_page;

    QPixmap m_pixmap;
    QGraphicsPixmapItem* m_imageItem = nullptr;
    double m_scale = 1.0;
    DisplayerTool* m_tool = nullptr;
    QPoint m_panPos;
    QTimer m_renderTimer;
    QTransform m_viewportTransform;

    void keyPressEvent(QKeyEvent* event) override;
    void mousePressEvent(QMouseEvent* event) override;
    void mouseMoveEvent(QMouseEvent* event) override;
    void mouseReleaseEvent(QMouseEvent* event) override;
    void resizeEvent(QResizeEvent* event) override;
    void wheelEvent(QWheelEvent* event) override;

    void setZoom(Zoom action, QGraphicsView::ViewportAnchor anchor = QGraphicsView::AnchorViewCenter);

    QTimer m_scaleTimer;
    QActionGroup* m_scaleGroup;
    QFutureWatcher<QImage> m_scaleWatcher;
};


class DisplayerTool : public QObject
{
public:
    DisplayerTool(Displayer* displayer, QObject* parent = nullptr) : QObject(parent), m_displayer(displayer) {}
    virtual ~DisplayerTool() {}
    virtual void mousePressEvent(QMouseEvent* /*event*/) {}
    virtual void mouseMoveEvent(QMouseEvent* /*event*/) {}
    virtual void mouseReleaseEvent(QMouseEvent* /*event*/) {}
    virtual void pageChanged() {}
    virtual void resolutionChanged(double /*factor*/) {}
    virtual void rotationChanged(double /*delta*/) {}
    virtual QList<QImage> getOCRAreas() = 0;
    virtual bool hasMultipleOCRAreas() const;
    virtual bool allowAutodetectOCRAreas() const;
    virtual void autodetectOCRAreas() {}
    virtual void reset() {}
    Displayer* displayer() const;

protected:
    Displayer* m_displayer;
};

class DisplayerSelection : public QObject, public QGraphicsRectItem
{
    Q_OBJECT
public:
    DisplayerSelection(DisplayerTool* tool, const QPointF& anchor);
    void setAnchorAndPoint(const QPointF& anchor, const QPointF& point);
    void setMinimumRect(const QRectF& rect);
    void setPoint(const QPointF& point);
    void rotate(const QTransform& transform);
    void scale(double factor);

protected:
    DisplayerTool* m_tool;

    void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;

signals:
    void geometryChanged(QRectF rect);

private:
    typedef void(*ResizeHandler)(const QPointF&, QPointF&, QPointF&);

    QPointF m_anchor;
    QPointF m_point;
    QRectF m_minRect;
    QVector<ResizeHandler> m_resizeHandlers;
    QPointF m_resizeOffset;

    void hoverMoveEvent(QGraphicsSceneHoverEvent* event) override;
    void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
    void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
    void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;

    static void resizeAnchorX(const QPointF& pos, QPointF& anchor, QPointF& /*point*/);
    static void resizeAnchorY(const QPointF& pos, QPointF& anchor, QPointF& /*point*/);
    static void resizePointX(const QPointF& pos, QPointF& /*anchor*/, QPointF& point);
    static void resizePointY(const QPointF& pos, QPointF& /*anchor*/, QPointF& point);
};

#endif // IMAGEDISPLAYER_H
