//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Mask/MaskItems.cpp
//! @brief     Implements classes MaskItem, RectangleItem, ..., FullframeItem.
//!
//! @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)
//
//  ************************************************************************************************

#include "GUI/Model/Mask/MaskItems.h"
#include "Device/Mask/Ellipse.h"
#include "Device/Mask/InfinitePlane.h"
#include "Device/Mask/Line.h"
#include "Device/Mask/Polygon.h"
#include "Device/Mask/Rectangle.h"
#include "GUI/Model/Mask/PointItem.h"
#include "GUI/Model/Util/Backup.h"
#include "GUI/Model/Util/UtilXML.h"
#include <QRegularExpression>

namespace {
namespace Tag {

const QString Angle("Angle");
const QString BaseData("BaseData");
const QString IsClosed("IsClosed");
const QString IsVisible("IsVisible");
const QString MaskValue("MaskValue");
const QString Name("Name");
const QString Position("Position");
const QString Vertex("Vertex");
const QString XCenter("XCenter");
const QString XLow("XLow");
const QString XRadius("XRadius");
const QString XHig("XHig");
const QString YCenter("YCenter");
const QString YLow("YLow");
const QString YRadius("YRadius");
const QString YHig("YHig");

} // namespace Tag
} // namespace


MaskItem::MaskItem() = default;

MaskItem::~MaskItem() = default;

void MaskItem::setMaskValue(bool mask_value)
{
    m_mask_value = mask_value;
    emit maskVisibilityChanged();
}

void MaskItem::setIsVisible(bool visible)
{
    m_is_visible = visible;
    emit maskVisibilityChanged();
}

void MaskItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeTaggedValue(w, Tag::Name, name());
    XML::writeTaggedValue(w, Tag::MaskValue, m_mask_value);
    XML::writeTaggedValue(w, Tag::IsVisible, m_is_visible);
}

void MaskItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::Name)
            setName(XML::readTaggedString(r, tag));
        else if (tag == Tag::MaskValue)
            m_mask_value = XML::readTaggedBool(r, tag);
        else if (tag == Tag::IsVisible) {
            m_is_visible = XML::readTaggedBool(r, tag);
            m_was_visible = m_is_visible;

        } else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------------------------------- */

RectangleItem::RectangleItem()
{
    setName("Rectangle");
    m_x_low.init("x left", "left x coordinate", 0, 4, RealLimits::limitless(), "xLow");
    m_x_up.init("x right", "right x coordinate", 0, 4, RealLimits::limitless(), "xUp");
    m_y_low.init("y bottom", "bottom y coordinate", 0, 4, RealLimits::limitless(), "yLow");
    m_y_up.init("y top", "top y coordinate", 0, 4, RealLimits::limitless(), "yUp");
}

std::unique_ptr<IShape2D> RectangleItem::createShape() const
{
    return std::make_unique<Rectangle>(xLow().dVal(), yLow().dVal(), xUp().dVal(), yUp().dVal());
}

void RectangleItem::setXLow(double val)
{
    m_x_low.setAndNotify(val);
}
void RectangleItem::setYLow(double val)
{
    m_y_low.setAndNotify(val);
}
void RectangleItem::setXHig(double val)
{
    m_x_up.setAndNotify(val);
}
void RectangleItem::setYHig(double val)
{
    m_y_up.setAndNotify(val);
}

void RectangleItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<MaskItem>(w, XML::Tag::BaseData, this);

    // x low
    m_x_low.writeTo2(w, Tag::XLow);

    // y low
    m_y_low.writeTo2(w, Tag::YLow);

    // x up
    m_x_up.writeTo2(w, Tag::XHig);

    // y up
    m_y_up.writeTo2(w, Tag::YHig);
}

void RectangleItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<MaskItem>(r, tag, this);
        else if (tag == Tag::XLow) {
            m_x_low.readFrom2(r, tag);
        } else if (tag == Tag::YLow) {
            m_y_low.readFrom2(r, tag);
        } else if (tag == Tag::XHig) {
            m_x_up.readFrom2(r, tag);
        } else if (tag == Tag::YHig) {
            m_y_up.readFrom2(r, tag);

        } else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------------------------------- */

RegionOfInterestItem::RegionOfInterestItem()
{
    setName("RegionOfInterest");
    setMaskValue(false);
}

std::unique_ptr<IShape2D> RegionOfInterestItem::createShape() const
{
    auto shape = RectangleItem::createShape();
    dynamic_cast<Rectangle*>(shape.get())->setInverted(true);
    return shape;
}

/* ------------------------------------------------------------------------- */

PolygonItem::PolygonItem()
{
    setName("Polygon");
}

std::unique_ptr<IShape2D> PolygonItem::createShape() const
{
    std::vector<std::pair<double, double>> pts;
    for (PointItem* item : m_points)
        pts.push_back({item->posX().dVal(), item->posY().dVal()});
    return std::make_unique<Polygon>(pts);
}

void PolygonItem::setIsClosed(bool closed)
{
    m_is_closed = closed;
}

QVector<PointItem*> PolygonItem::points() const
{
    return QVector<PointItem*>(m_points.begin(), m_points.end());
}

void PolygonItem::addPoint(double x, double y)
{
    auto* pointItem = new PointItem;
    pointItem->setPosX(x);
    pointItem->setPosY(y);
    m_points.push_back(pointItem); // TODO: check whether the order of points matter
}

void PolygonItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<MaskItem>(w, XML::Tag::BaseData, this);
    w->writeStartElement(Tag::IsClosed);
    XML::writeAttribute(w, XML::Attrib::value, m_is_closed); // TODO do we need to save 'isClosed'?
    w->writeEndElement();

    // polygon points
    for (const auto* p : m_points) {
        XML::writeTaggedElement(w, Tag::Vertex, *p);
    }
}

void PolygonItem::readFrom(QXmlStreamReader* r)
{
    m_points.clear();
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<MaskItem>(r, tag, this);
        else if (tag == Tag::IsClosed)
            m_is_closed = XML::readTaggedBool(r, tag); // TODO need to read 'isClosed'?
        else if (tag == Tag::Vertex) {
            PointItem point;
            point.readFrom(r);
            addPoint(point.posX().dVal(), point.posY().dVal());
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------------------------------- */

LineItem::LineItem(double pos)
{
    m_pos.init("pos", "position", pos, 4, RealLimits::limitless(), "pos");
}

void LineItem::setPos(double val)
{
    m_pos.setAndNotify(val);
}

void LineItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<MaskItem>(w, XML::Tag::BaseData, this);

    // position
    m_pos.writeTo2(w, Tag::Position);
}

void LineItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<MaskItem>(r, tag, this);
        else if (tag == Tag::Position)
            m_pos.readFrom2(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------------------------------- */

VerticalLineItem::VerticalLineItem(double pos)
    : LineItem(pos)
{
    setName("VerticalLine");
}

std::unique_ptr<IShape2D> VerticalLineItem::createShape() const
{
    return std::make_unique<VerticalLine>(pos().dVal());
}

/* ------------------------------------------------------------------------- */

HorizontalLineItem::HorizontalLineItem(double pos)
    : LineItem(pos)
{
    setName("HorizontalLine");
}

std::unique_ptr<IShape2D> HorizontalLineItem::createShape() const
{
    return std::make_unique<HorizontalLine>(pos().dVal());
}

/* ------------------------------------------------------------------------- */

EllipseItem::EllipseItem()
{
    setName("Ellipse");
    m_x_center.init("x center", "x center", 0, 4, RealLimits::limitless(), "xCenter");
    m_y_center.init("y center", "y center", 0, 4, RealLimits::limitless(), "yCenter");
    m_x_radius.init("x radius", "x radius", 0, 4, RealLimits::lowerLimited(0.), "xRadius");
    m_y_radius.init("y radius", "y radius", 0, 4, RealLimits::lowerLimited(0.), "yRadius");
    m_angle.init("angle", "angle", 0, 4, 1.0, RealLimits::limited(0., 360.), "angle");
}

std::unique_ptr<IShape2D> EllipseItem::createShape() const
{
    double xcenter = xCenter().dVal();
    double ycenter = yCenter().dVal();
    double xradius = xRadius().dVal();
    double yradius = yRadius().dVal();
    double _angle = angle().dVal();

    return std::make_unique<Ellipse>(xcenter, ycenter, xradius, yradius, _angle);
}

void EllipseItem::setXCenter(double val)
{
    m_x_center.setAndNotify(val);
}
void EllipseItem::setYCenter(double val)
{
    m_y_center.setAndNotify(val);
}
void EllipseItem::setXRadius(double val)
{
    m_x_radius.setAndNotify(val);
}
void EllipseItem::setYRadius(const double val)
{
    m_y_radius.setAndNotify(val);
}
void EllipseItem::setAngle(const double val)
{
    m_angle.setAndNotify(val);
}

void EllipseItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<MaskItem>(w, XML::Tag::BaseData, this);

    // x center
    m_x_center.writeTo2(w, Tag::XCenter);

    // y center
    m_y_center.writeTo2(w, Tag::YCenter);

    // x radius
    m_x_radius.writeTo2(w, Tag::XRadius);

    // y radius
    m_y_radius.writeTo2(w, Tag::YRadius);

    // rotation angle
    m_angle.writeTo2(w, Tag::Angle);
}

void EllipseItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<MaskItem>(r, tag, this);
        else if (tag == Tag::XCenter) {
            m_x_center.readFrom2(r, tag);
        } else if (tag == Tag::YCenter) {
            m_y_center.readFrom2(r, tag);
        } else if (tag == Tag::XRadius) {
            m_x_radius.readFrom2(r, tag);
        } else if (tag == Tag::YRadius) {
            m_y_radius.readFrom2(r, tag);
        } else if (tag == Tag::Angle) {
            m_angle.readFrom2(r, tag);

        } else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------------------------------- */

FullframeItem::FullframeItem()
{
    setName("FullFrame");
}

std::unique_ptr<IShape2D> FullframeItem::createShape() const
{
    return std::make_unique<InfinitePlane>();
}
