//  ************************************************************************************************
//
//  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 <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 XUp("XUp");
const QString YCenter("YCenter");
const QString YLow("YLow");
const QString YRadius("YRadius");
const QString YUp("YUp");

} // 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::writeAttribute(w, XML::Attrib::version, uint(1));

    // name
    w->writeStartElement(Tag::Name);
    XML::writeAttribute(w, XML::Attrib::value, name());
    w->writeEndElement();

    // mask value
    w->writeStartElement(Tag::MaskValue);
    XML::writeAttribute(w, XML::Attrib::value, m_mask_value);
    w->writeEndElement();

    // is visible?
    w->writeStartElement(Tag::IsVisible);
    XML::writeAttribute(w, XML::Attrib::value, m_is_visible);
    w->writeEndElement();
}

void MaskItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

    while (r->readNextStartElement()) {
        QString tag = r->name().toString();

        // name
        if (tag == Tag::Name) {
            setName(XML::readAttributeString(r, XML::Attrib::value));
            XML::gotoEndElementOfTag(r, tag);

            // mask value
        } else if (tag == Tag::MaskValue) {
            XML::readAttribute(r, XML::Attrib::value, &m_mask_value);
            XML::gotoEndElementOfTag(r, tag);

            // is visible?
        } else if (tag == Tag::IsVisible) {
            XML::readAttribute(r, XML::Attrib::value, &m_is_visible);
            m_was_visible = m_is_visible;
            XML::gotoEndElementOfTag(r, tag);

        } 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
{
    double xlow = xLow();
    double ylow = yLow();
    double xup = xUp();
    double yup = yUp();
    return std::make_unique<Rectangle>(xlow, ylow, xup, yup);
}

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

void RectangleItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeAttribute(w, XML::Attrib::version, uint(1));

    // parameters from base class
    w->writeStartElement(Tag::BaseData);
    MaskItem::writeTo(w);
    w->writeEndElement();

    // x low
    w->writeStartElement(Tag::XLow);
    m_x_low.writeTo(w);
    w->writeEndElement();

    // y low
    w->writeStartElement(Tag::YLow);
    m_y_low.writeTo(w);
    w->writeEndElement();

    // x up
    w->writeStartElement(Tag::XUp);
    m_x_up.writeTo(w);
    w->writeEndElement();

    // y up
    w->writeStartElement(Tag::YUp);
    m_y_up.writeTo(w);
    w->writeEndElement();
}

void RectangleItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

    while (r->readNextStartElement()) {
        QString tag = r->name().toString();

        // parameters from base class
        if (tag == Tag::BaseData) {
            MaskItem::readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // x low
        } else if (tag == Tag::XLow) {
            m_x_low.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // y low
        } else if (tag == Tag::YLow) {
            m_y_low.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // x up
        } else if (tag == Tag::XUp) {
            m_x_up.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // y up
        } else if (tag == Tag::YUp) {
            m_y_up.readFrom(r);
            XML::gotoEndElementOfTag(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(), item->posY()});
    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::writeAttribute(w, XML::Attrib::version, uint(1));

    // parameters from base class
    w->writeStartElement(Tag::BaseData);
    MaskItem::writeTo(w);
    w->writeEndElement();

    // is closed?
    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) {
        w->writeStartElement(Tag::Vertex);
        p->writeTo(w);
        w->writeEndElement();
    }
}

void PolygonItem::readFrom(QXmlStreamReader* r)
{
    m_points.clear();

    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

    while (r->readNextStartElement()) {
        QString tag = r->name().toString();

        // parameters from base class
        if (tag == Tag::BaseData) {
            MaskItem::readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // is closed?
        } else if (tag == Tag::IsClosed) {
            XML::readAttribute(r, XML::Attrib::value,
                               &m_is_closed); // TODO do we need to read 'isClosed'?
            XML::gotoEndElementOfTag(r, tag);

            // polygon point
        } else if (tag == Tag::Vertex) {
            PointItem point;
            point.readFrom(r);
            addPoint(point.posX(), point.posY());
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

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

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

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

void LineItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeAttribute(w, XML::Attrib::version, uint(1));

    // parameters from base class
    w->writeStartElement(Tag::BaseData);
    MaskItem::writeTo(w);
    w->writeEndElement();

    // position
    w->writeStartElement(Tag::Position);
    m_pos.writeTo(w);
    w->writeEndElement();
}

void LineItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

    while (r->readNextStartElement()) {
        QString tag = r->name().toString();

        // parameters from base class
        if (tag == Tag::BaseData) {
            MaskItem::readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // position
        } else if (tag == Tag::Position) {
            m_pos.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

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

VerticalLineItem::VerticalLineItem()
{
    setName("VerticalLine");
}

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

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

HorizontalLineItem::HorizontalLineItem()
{
    setName("HorizontalLine");
}

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

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

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, RealLimits::limited(0., 360.), "angle");
}

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

    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::writeAttribute(w, XML::Attrib::version, uint(1));

    // parameters from base class
    w->writeStartElement(Tag::BaseData);
    MaskItem::writeTo(w);
    w->writeEndElement();

    // x center
    w->writeStartElement(Tag::XCenter);
    m_x_center.writeTo(w);
    w->writeEndElement();

    // y center
    w->writeStartElement(Tag::YCenter);
    m_y_center.writeTo(w);
    w->writeEndElement();

    // x radius
    w->writeStartElement(Tag::XRadius);
    m_x_radius.writeTo(w);
    w->writeEndElement();

    // y radius
    w->writeStartElement(Tag::YRadius);
    m_y_radius.writeTo(w);
    w->writeEndElement();

    // rotation angle
    w->writeStartElement(Tag::Angle);
    m_angle.writeTo(w);
    w->writeEndElement();
}

void EllipseItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

    while (r->readNextStartElement()) {
        QString tag = r->name().toString();

        // parameters from base class
        if (tag == Tag::BaseData) {
            MaskItem::readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // x center
        } else if (tag == Tag::XCenter) {
            m_x_center.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // y center
        } else if (tag == Tag::YCenter) {
            m_y_center.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // x radius
        } else if (tag == Tag::XRadius) {
            m_x_radius.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // y radius
        } else if (tag == Tag::YRadius) {
            m_y_radius.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // rotation angle
        } else if (tag == Tag::Angle) {
            m_angle.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

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

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

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