//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Overlay/PolygonOverlay.cpp
//! @brief     Implements class PolygonOverlay.
//!
//! @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/View/Overlay/PolygonOverlay.h"
#include "GUI/Model/Mask/MaskItems.h"
#include "GUI/Model/Mask/PointItem.h"
#include "GUI/View/Overlay/OverlayStyle.h"
#include "GUI/View/Overlay/VertexOverlay.h"
#include "GUI/View/Plotter/ColorMap.h"
#include <QCursor>

namespace {

const double bbox_margins = 5; // additional margins around points to form bounding box

} // namespace

PolygonOverlay::PolygonOverlay(PolygonItem* item, ColorMap* plot)
    : IMaskOverlay(plot)
    , m_item(item)
    , m_block_on_point_update(false)
    , m_close_polygon_request(false)
{
    m_item->maskVisibilityChanged();
}

OverlayItem* PolygonOverlay::parameterizedItem() const
{
    return m_item;
}

void PolygonOverlay::addOverlay(IOverlay* child)
{
    ASSERT(child);
    if (childItems().contains(child))
        return;

    auto* pointView = dynamic_cast<VertexOverlay*>(child);
    ASSERT(pointView);
    pointView->setParentItem(this);

    // polygon consisting from more than 2 points can be closed via hover event by clicking
    // on first polygon point
    if (!isClosedPolygon() && childItems().size() > 2)
        childItems()[0]->setAcceptHoverEvents(true);

    // during the drawing process the polygon is selected
    pointView->setVisible(isSelected());

    update_polygon();

    connect(pointView, &VertexOverlay::propertyChanged, this, &PolygonOverlay::update_view);
    connect(pointView, &VertexOverlay::closePolygonRequest, this,
            &PolygonOverlay::onClosePolygonRequest);
}

//! Returns last added poligon point in scene coordinates
QPointF PolygonOverlay::lastAddedPoint() const
{
    return !childItems().empty() ? childItems().back()->scenePos() : QPointF();
}

QPainterPath PolygonOverlay::shape() const
{
    QPainterPath path;
    path.addPolygon(m_polygon);
    path.closeSubpath();
    return path;
}

//! Returns true if there was a request to close polygon (emitted by its start point),
//! and then closes a polygon. Returns true if polygon was closed.
bool PolygonOverlay::closePolygonIfNecessary()
{
    if (m_close_polygon_request) {
        for (QGraphicsItem* childItem : childItems()) {
            childItem->setFlag(QGraphicsItem::ItemIsMovable);
            childItem->setFlag(QGraphicsItem::ItemSendsGeometryChanges);
            childItem->setAcceptHoverEvents(false);
            childItem->setCursor(Qt::SizeAllCursor);
        }
        m_item->setIsClosed(true);
        update();
    }
    return isClosedPolygon();
}

void PolygonOverlay::onClosePolygonRequest(bool value)
{
    m_close_polygon_request = value;
}

bool PolygonOverlay::isClosedPolygon()
{
    return m_item->isClosed();
}

void PolygonOverlay::paint(QPainter* painter, const QStyleOptionGraphicsItem* o, QWidget* w)
{
    if (isClosedPolygon())
        IMaskOverlay::paint(painter, o, w);
    else {
        ASSERT(m_item);
        const bool mask_value = static_cast<PolygonItem*>(m_item)->maskValue();
        painter->setRenderHints(QPainter::Antialiasing);
        painter->setPen(GUI::Overlay::getMaskPen(mask_value));
        painter->drawPolyline(m_polygon.toPolygon());
    }
}

QVariant PolygonOverlay::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant& value)
{
    if (change == QGraphicsItem::ItemSelectedHasChanged)
        setChildrenVisible(this->isSelected());

    return value;
}

void PolygonOverlay::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
    QGraphicsObject::mouseMoveEvent(event);
    update_points();
}

void PolygonOverlay::update_view()
{
    update_polygon();
    update();
}

//! Runs through all PointItem and calculate bounding rectangle.
//! Determines position of the rectangle in scene.
//! Calculates position of VertexOverlay in local polygon coordinates
void PolygonOverlay::update_polygon()
{
    if (m_block_on_point_update)
        return;

    m_block_on_point_update = true;

    if (!m_item->points().isEmpty()) {
        m_polygon.clear();

        for (auto* point : m_item->points())
            m_polygon << QPointF(m_plot->toSceneX(point->posX()), m_plot->toSceneY(point->posY()));

        const QRectF scene_rect = m_polygon.boundingRect().marginsAdded(
            QMarginsF(bbox_margins, bbox_margins, bbox_margins, bbox_margins));

        m_bounding_rect = QRectF(0.0, 0.0, scene_rect.width(), scene_rect.height());

        setPos(scene_rect.x(), scene_rect.y());
        update(); // to propagate changes to scene

        m_polygon = mapFromScene(m_polygon);

        int index(0);
        for (auto* child : childItems())
            child->setPos(m_polygon[index++]);

        setPos(scene_rect.x(), scene_rect.y());
    }
    m_block_on_point_update = false;
}

//! When polygon moves as a whole thing across the scene, given method updates coordinates
//! of PointItem's
void PolygonOverlay::update_points()
{
    if (m_block_on_point_update)
        return;

    for (QGraphicsItem* childItem : childItems()) {
        auto* view = dynamic_cast<VertexOverlay*>(childItem);
        QPointF pos = view->scenePos();
        disconnect(view, &VertexOverlay::propertyChanged, this, &PolygonOverlay::update_view);
        view->updateParameterizedItem(pos);
        connect(view, &VertexOverlay::propertyChanged, this, &PolygonOverlay::update_view);
    }
}

void PolygonOverlay::setChildrenVisible(bool value)
{
    for (QGraphicsItem* childItem : childItems())
        childItem->setVisible(value);
}
