/**
 * UGENE - Integrated Bioinformatics Tools.
 * Copyright (C) 2008-2023 UniPro <ugene@unipro.ru>
 * http://ugene.net
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 */

#pragma once

#include <QAction>
#include <QFont>
#include <QScrollBar>

#include <U2Core/Annotation.h>
#include <U2Core/AppContext.h>
#include <U2Core/DNAAlphabet.h>
#include <U2Core/DocumentModel.h>
#include <U2Core/GObject.h>
#include <U2Core/IOAdapter.h>
#include <U2Core/Log.h>
#include <U2Core/ProjectModel.h>
#include <U2Core/Task.h>

#include <U2Gui/MainWindow.h>

#include <U2View/ADVConstants.h>
#include <U2View/ADVSequenceObjectContext.h>
#include <U2View/AnnotatedDNAView.h>
#include <U2View/GSequenceLineViewAnnotated.h>

namespace U2 {

class CircularAnnotationItem;
class CircularAnnotationLabel;
class CircularAnnotationRegionItem;
class CircularViewRenderArea;
struct CircularViewSettings;
class TextItem;

class CircularView : public GSequenceLineViewAnnotated {
    Q_OBJECT
public:
    CircularView(QWidget* p, ADVSequenceObjectContext* ctx, CircularViewSettings* settings);

    void pack() override;
    void mousePressEvent(QMouseEvent* e) override;
    void mouseMoveEvent(QMouseEvent* e) override;
    void mouseReleaseEvent(QMouseEvent* e) override;
    void resizeEvent(QResizeEvent* e) override;

    void keyPressEvent(QKeyEvent* e) override;
    void keyReleaseEvent(QKeyEvent* e) override;

    void wheelEvent(QWheelEvent* we) override;
    QSize sizeHint() const override;

    static qreal coordToAngle(const QPoint point);

    bool isCircularTopology() const;

    enum Direction { CW,
                     CCW,
                     UNKNOWN };

    static const int MIN_OUTER_SIZE;
    static const int CV_REGION_ITEM_WIDTH;
    static const int GRADUATION;
    static const int MAX_GRADUATION_ANGLE;

    void setAngle(int angle);
    void updateMinSize();

    // used by export to file function
    void paint(QPainter& p, int w, int h, bool paintSelection, bool paintMarker);

    CircularViewSettings* getSettings() {
        return settings;
    }
    void redraw();

signals:
    void si_wheelMoved(int);
    void si_zoomInDisabled(bool);
    void si_zoomOutDisabled(bool);
    void si_fitInViewDisabled(bool);

public slots:
    void sl_zoomIn();
    void sl_zoomOut();
    void sl_fitInView();
    void sl_onSequenceObjectRenamed(const QString& oldName);
    void sl_onCircularTopologyChange();

protected slots:
    void sl_onAnnotationSelectionChanged(AnnotationSelection*, const QList<Annotation*>& added, const QList<Annotation*>& removed) override;
    void sl_onDNASelectionChanged(LRegionsSelection* thiz, const QVector<U2Region>& added, const QVector<U2Region>& removed) override;

protected:
    void adaptSizes();
    void updateZoomActions();

    /**
     * Use for continuous region selection only.
     * TODO: if inverse selection function would be fully available (for splitted kind of selection), rewrite it.
     */
    void invertCurrentSelection();

    Direction getDirection(float a, float b) const;

    QVBoxLayout* layout;

    int lastMovePos;
    int currentSelectionLen;
    int lastMouseY;
    bool clockwise;
    bool holdSelection;
    qreal lastPressAngle;
    qreal lastMoveAngle;
    CircularViewSettings* settings;

    /** A renderArea from the base class with a correct type. Used to avoid casts in the code. */
    CircularViewRenderArea* circularViewRenderArea;
};

class CircularViewRenderArea : public GSequenceLineViewAnnotatedRenderArea {
    friend class CircularView;
    friend class CircularAnnotationItem;
    friend class CircularAnnotationLabel;
    friend class CircularAnnotationRegionItem;
    Q_OBJECT
public:
    enum DrawAnnotationPass {
        DrawAnnotationPass_DrawFill,
        DrawAnnotationPass_DrawBorder
    };

    CircularViewRenderArea(CircularView* d);
    ~CircularViewRenderArea();

    /** Returns all annotations by a coordinate inside render area. */
    QList<Annotation*> findAnnotationsByCoord(const QPoint& coord) const override;

    int getAnnotationYLevel(Annotation* a) const {
        return annotationYLevel.value(a);
    }

    static const int MIDDLE_ELLIPSE_SIZE;
    int getCenterY() const {
        return verticalOffset;
    }
    qreal coordToAsin(const QPoint& p) const;
    qint64 asinToPos(const qreal asin) const;

protected:
    qint64 coordToPos(const QPoint& p) const override;
    void resizeEvent(QResizeEvent* e) override;
    virtual void drawAll(QPaintDevice* pd) override;

    void buildAnnotationItem(DrawAnnotationPass pass, Annotation* a, int predefinedOrbit = -1, bool selected = false, const AnnotationSettings* as = nullptr);
    void buildAnnotationLabel(const QFont& font, Annotation* a, const AnnotationSettings* as, bool isAutoAnnotation = false);
    void buildItems(QFont labelFont);

    virtual void drawAnnotations(QPainter& p);

    void redraw();

    void paintContent(QPainter& p, bool paintSelection = true, bool paintMarker = true);
    void paintContent(QPainter& p, int w, int h, bool paintSelection, bool paintMarker);

    void drawSequenceName(QPainter& p);
    void drawRuler(QPainter& p);
    void drawRulerCoordinates(QPainter& p, int startPos, int seqLen);
    void drawRulerNotches(QPainter& p, int start, int span, int seqLen);
    void drawAnnotationsSelection(QPainter& p);
    void drawSequenceSelection(QPainter& p);
    void drawMarker(QPainter& p);
    void evaluateLabelPositions(const QFont& f);
    qreal getVisibleAngle() const;
    QPair<int, int> getVisibleRange() const;

private:
    int findOrbit(const QVector<U2Region>& location, Annotation* a);
    CircularAnnotationRegionItem* createAnnotationRegionItem(const U2Region& region, int seqLen, int yLevel, bool isComplementaryStrand, int index);
    QPainterPath createAnnotationArrowPath(float startAngle, float spanAngle, float dAlpha, const QRect& outerRect, const QRect& innerRect, const QRect& middleRect, bool complementary, bool isShort) const;
    void removeRegionsOutOfRange(QVector<U2Region>& location, int seqLen) const;

    static const int OUTER_ELLIPSE_SIZE;
    static const int ELLIPSE_DELTA;
    static const int INNER_ELLIPSE_SIZE;
    static const int RULER_ELLIPSE_SIZE;

    static const int ARROW_LENGTH;
    static const int ARROW_HEIGHT_DELTA;
    static const int MAX_DISPLAYING_LABELS;
    static const int MAX_LABEL_WIDTH;
    static const int FREE_SPACE_HEIGHT_FOR_INTERNAL_LABELS;

    static const int MARKER_LEN;
    static const int ARR_LEN;
    static const int ARR_WIDTH;
    static const int NOTCH_SIZE;

    int outerEllipseSize;
    int ellipseDelta;
    int innerEllipseSize;
    int rulerEllipseSize;
    int middleEllipseSize;
    int arrowLength;
    int arrowHeightDelta;
    int maxDisplayingLabels;
    int verticalOffset;
    int currentScale;  // > 0 -- zoomed IN; < 0 -- zoomed OUT; == 0 -- fits in view

    CircularViewSettings* settings;
    bool settingsWereChanged;

    CircularView* circularView;
    QList<QVector<U2Region>> regionY;

    QMap<Annotation*, CircularAnnotationItem*> circItems;
    TextItem* seqNameItem;
    TextItem* seqLenItem;
    QMap<Annotation*, int> annotationYLevel;
    QList<CircularAnnotationLabel*> labelList;
    qreal rotationDegree;
    qreal mouseAngle;
    QVector<QRect> positionsAvailableForLabels;
    QMap<int, CircularAnnotationLabel*> engagedLabelPositionToLabel;
    int oldYlevel;
};

}  // namespace U2
