//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Data/MaskItems.h
//! @brief     Defines MaskItems classes
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#ifndef BORNAGAIN_GUI_MODEL_DATA_MASKITEMS_H
#define BORNAGAIN_GUI_MODEL_DATA_MASKITEMS_H

#include "Base/Types/OwningVector.h"
#include "Base/Util/Assert.h"
#include "GUI/Model/Data/MaskItemCatalog.h"
#include "GUI/Model/Descriptor/SelectionProperty.h"
#include <QAbstractListModel>
#include <QItemSelectionModel>

class IShape2D;
class MessageService;

//! QObject with signals used in masks

class MaskItemObject : public QObject {
    Q_OBJECT
public:
    explicit MaskItemObject();
    ~MaskItemObject();

signals:
    void maskGeometryChanged(MaskItemObject* sender = nullptr);
    void maskVisibilityChanged();
    void maskToBeDestroyed(MaskItemObject* sender = nullptr);
};

//! A base class for all mask items

class MaskItem : public MaskItemObject {
public:
    virtual std::unique_ptr<IShape2D> createShape(double scale) const = 0;

    QString maskName() const;
    void setMaskName(const QString& name);

    bool maskValue() const;
    void setMaskValue(bool mask_value);

    bool isVisible() const;
    void setIsVisible(bool visible);

    bool wasVisible() const { return m_wasVisible; }
    void setWasVisible(bool v) { m_wasVisible = v; }

    virtual void writeTo(QXmlStreamWriter* w) const;
    virtual void readFrom(QXmlStreamReader* r);

protected:
    explicit MaskItem();
    QString m_maskName = "nameless by default";
    bool m_maskValue = true;
    bool m_isVisible = true;
    bool m_wasVisible = m_isVisible; // do not serialize
};

class RectangleItem : public MaskItem {
public:
    explicit RectangleItem();
    std::unique_ptr<IShape2D> createShape(double scale) const override;

    double xLow() const;
    void setXLow(double val);
    double yLow() const;
    void setYLow(double val);
    double xUp() const;
    void setXUp(double val);
    double yUp() const;
    void setYUp(double val);

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

protected:
    explicit RectangleItem(const QString& modelType);

private:
    double m_xLow;
    double m_yLow;
    double m_xUp;
    double m_yUp;
};

class RegionOfInterestItem : public RectangleItem {
public:
    RegionOfInterestItem();
    std::unique_ptr<IShape2D> createShape(double scale) const override;
};

class PolygonPointItem : public MaskItemObject {
public:
    static constexpr auto M_TYPE{"PolygonPoint"};

    PolygonPointItem();

    double posX() const;
    void setPosX(double val);
    double posY() const;
    void setPosY(double val);

    void writeTo(QXmlStreamWriter* w) const;
    void readFrom(QXmlStreamReader* r);

private:
    double m_posX = 0;
    double m_posY = 0;
};

class PolygonItem : public MaskItem {
public:
    PolygonItem();
    std::unique_ptr<IShape2D> createShape(double scale) const override;

    bool isClosed() const;
    void setIsClosed(bool closed);

    QVector<PolygonPointItem*> points() const;
    void addPoint(double x, double y);

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

private:
    bool m_isClosed = false;
    OwningVector<PolygonPointItem> m_points;
};

class VerticalLineItem : public MaskItem {
public:
    VerticalLineItem();
    std::unique_ptr<IShape2D> createShape(double scale) const override;

    double posX() const;
    void setPosX(double val);

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

private:
    double m_posX = 0;
};

class HorizontalLineItem : public MaskItem {
public:
    HorizontalLineItem();
    std::unique_ptr<IShape2D> createShape(double scale) const override;

    double posY() const;
    void setPosY(double pos_y);

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

private:
    double m_posY = 0;
};

class EllipseItem : public MaskItem {
public:
    EllipseItem();
    std::unique_ptr<IShape2D> createShape(double scale) const override;

    double xCenter() const;
    void setXCenter(double val);

    double yCenter() const;
    void setYCenter(double val);

    double xRadius() const;
    void setXRadius(double val);

    double yRadius() const;
    void setYRadius(double val);

    double angle() const;
    void setAngle(double val);

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

private:
    double m_xCenter = 0;
    double m_yCenter = 0;
    double m_xRadius = 0;
    double m_yRadius = 0;
    double m_angle = 0;
};

class MaskAllItem : public MaskItem {
public:
    MaskAllItem();
    std::unique_ptr<IShape2D> createShape(double scale) const override;
};


class MaskContainerModel;

//! Container holding various masks as children

class MaskContainerItem : public MaskItemObject {
public:
    MaskContainerItem();

    QVector<MaskItem*> maskItems() const;

    //! Insert mask at given row.
    virtual void insertMask(int row, MaskItem* maskItem);

    virtual void addMask(MaskItem* maskItem);

    //! Move mask to a given row
    virtual void moveMask(int from_row, int to_row);

    //! Remove a given mask
    virtual void removeMask(MaskItem* maskItem);

    virtual void removeMaskAt(int row);

    //! Can be nullptr.
    RegionOfInterestItem* regionOfInterestItem() const;

    //! Remove all masks
    void clear();

    //! Return true if the container has no MaskItems
    bool isEmpty() const;

    //! Return the number of MaskItems
    int size() const;

    //! Return the MaskItem at a given index
    int indexOfItem(MaskItem* maskItem) const;

    //! Return the index corresponding to a given MaskItem
    MaskItem* at(const int idx);

    //! Copy masks from another MaskContainerItem
    void copyFrom(const MaskContainerItem* maskContainer);

    //! Serialize/deserialize
    void writeTo(QXmlStreamWriter* w) const;
    void readFrom(QXmlStreamReader* r, MessageService* messageService = nullptr);

    //! Return the corresponding MaskContainerModel
    MaskContainerModel* model();

    //! Return the corresponding QItemSelectionModel
    QItemSelectionModel* selectionModel();

    //! Update numbers in mask names
    void updateMaskNames();

public:
    const QModelIndex rootIndex;

protected:
    SelectionVector<MaskItemCatalog> m_maskItems;
    std::unique_ptr<MaskContainerModel> m_model;
    std::unique_ptr<QItemSelectionModel> m_selectionModel;
};

//! Provides interfaces to a MaskContainerItem, allowing its contents to be displayed and modified
//! using the Qt mechanisms.

class MaskContainerModel : public QAbstractListModel {
    Q_OBJECT

public:
    MaskContainerModel(MaskContainerItem* container);
    ~MaskContainerModel();

    //! Insert mask at given row.
    void insertMask(int row, MaskItem* maskItem);

    void addMask(MaskItem* maskItem);

    //! Remove a given mask
    void removeMask(MaskItem* maskItem);

    //! Remove all masks
    void clear();

    //! Copy masks from another MaskContainerItem
    void copy(const MaskContainerModel* src);

    // Begin overridden methods from QAbstractListModel
    QVariant data(const QModelIndex& index, int role) const override;
    int rowCount(const QModelIndex& parent) const override;
    // End overridden methods from QAbstractListModel

    void moveMask(int from_row, int to_row);
    void removeMaskAt(int row);

    QModelIndex indexOfItem(MaskItemObject* item) const; // TODO: change this to MaskItem*
    MaskItem* itemForIndex(const QModelIndex& index) const;

    RegionOfInterestItem* regionOfInterestItem() const;

public:
    MaskContainerItem* maskContainer = nullptr;
};

#endif // BORNAGAIN_GUI_MODEL_DATA_MASKITEMS_H
