// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2013 Adrian Draghici <draghici.adrian.b@gmail.com>
//

// Self
#include "GroundOverlayFrame.h"

// Marble
#include "GeoDataGroundOverlay.h"
#include "GeoDataLinearRing.h"
#include "GeoDataPlacemark.h"
#include "GeoDataPolygon.h"
#include "GeoPainter.h"
#include "MarbleDirs.h"
#include "SceneGraphicsTypes.h"
#include "TextureLayer.h"
#include "ViewportParams.h"

// Qt
#include <qmath.h>

namespace Marble
{

GroundOverlayFrame::GroundOverlayFrame(GeoDataPlacemark *placemark, GeoDataGroundOverlay *overlay, TextureLayer *textureLayer)
    : SceneGraphicsItem(placemark)
    , m_overlay(overlay)
    , m_textureLayer(textureLayer)
    , m_movedHandle(NoRegion)
    , m_hoveredHandle(NoRegion)
    , m_editStatus(Resize)
    , m_editStatusChangeNeeded(false)
    , m_previousRotation(0.0)
    , m_viewport(nullptr)
{
    m_resizeIcons.reserve(16);
    // NorthWest
    m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-diagonal-topleft.png")));
    m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-diagonal-topleft-active.png")));
    // SouthWest
    m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-diagonal-topright.png")));
    m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-diagonal-topright-active.png")));
    // SouthEast
    m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-diagonal-topleft.png")));
    m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-diagonal-topleft-active.png")));
    // NorthEast
    m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-diagonal-topright.png")));
    m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-diagonal-topright-active.png")));
    // North
    m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-vertical.png")));
    m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-vertical-active.png")));
    // South
    m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-vertical.png")));
    m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-vertical-active.png")));
    // East
    m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-horizontal.png")));
    m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-horizontal-active.png")));
    // West
    m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-horizontal.png")));
    m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-horizontal-active.png")));

    m_rotateIcons.reserve(16);
    // NorthWest
    m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-rotation-topleft.png")));
    m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-rotation-topleft-active.png")));
    // SouthWest
    m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-rotation-bottomleft.png")));
    m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-rotation-bottomleft-active.png")));
    // SouthEast
    m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-rotation-bottomright.png")));
    m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-rotation-bottomright-active.png")));
    // NorthEast
    m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-rotation-topright.png")));
    m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-rotation-topright-active.png")));
    // North
    m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-horizontal.png")));
    m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-horizontal-active.png")));
    // South
    m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-horizontal.png")));
    m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-horizontal-active.png")));
    // East
    m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-vertical.png")));
    m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-vertical-active.png")));
    // West
    m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-vertical.png")));
    m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1StringView("/bitmaps/editarrows/arrow-vertical-active.png")));

    update();
    setPaintLayers(QStringList() << QStringLiteral("GroundOverlayFrame"));
}

void GroundOverlayFrame::paint(GeoPainter *painter, const ViewportParams *viewport, const QString &layer, int tileZoomLevel)
{
    Q_UNUSED(layer);
    Q_UNUSED(tileZoomLevel);
    m_viewport = viewport;
    m_regionList.clear();

    painter->save();
    if (const auto polygon = geodata_cast<GeoDataPolygon>(placemark()->geometry())) {
        const GeoDataLinearRing &ring = polygon->outerBoundary();
        QList<GeoDataCoordinates> coordinateList;
        coordinateList.reserve(8);

        coordinateList.append(ring.at(NorthWest));
        coordinateList.append(ring.at(SouthWest));
        coordinateList.append(ring.at(SouthEast));
        coordinateList.append(ring.at(NorthEast));

        GeoDataCoordinates northernHandle = ring.at(NorthEast).interpolate(ring.at(NorthWest), 0.5);
        GeoDataCoordinates southernHandle = ring.at(SouthEast).interpolate(ring.at(SouthWest), 0.5);
        // Special case handle position to take tessellation
        // along latitude circles into account
        if (m_overlay->latLonBox().rotation() == 0) {
            northernHandle.setLatitude(ring.at(NorthEast).latitude());
            southernHandle.setLatitude(ring.at(SouthEast).latitude());
        }
        coordinateList.append(northernHandle);
        coordinateList.append(southernHandle);

        coordinateList.append(ring.at(NorthEast).interpolate(ring.at(SouthEast), 0.5));
        coordinateList.append(ring.at(NorthWest).interpolate(ring.at(SouthWest), 0.5));

        m_regionList.reserve(9);
        m_regionList.append(painter->regionFromEllipse(coordinateList.at(NorthWest), 16, 16));
        m_regionList.append(painter->regionFromEllipse(coordinateList.at(SouthWest), 16, 16));
        m_regionList.append(painter->regionFromEllipse(coordinateList.at(SouthEast), 16, 16));
        m_regionList.append(painter->regionFromEllipse(coordinateList.at(NorthEast), 16, 16));
        m_regionList.append(painter->regionFromEllipse(coordinateList.at(North), 16, 16));
        m_regionList.append(painter->regionFromEllipse(coordinateList.at(South), 16, 16));
        m_regionList.append(painter->regionFromEllipse(coordinateList.at(East), 16, 16));
        m_regionList.append(painter->regionFromEllipse(coordinateList.at(West), 16, 16));
        m_regionList.append(painter->regionFromPolygon(ring, Qt::OddEvenFill));

        // Calculate handle icon orientation due to the projection
        qreal xNW, yNW, xSW, ySW;
        viewport->screenCoordinates(ring.at(NorthWest), xNW, yNW);
        viewport->screenCoordinates(ring.at(SouthWest), xSW, ySW);
        qreal westernAngle = qAtan2(ySW - yNW, xSW - xNW) - M_PI / 2;
        qreal xNE, yNE, xSE, ySE;
        viewport->screenCoordinates(ring.at(NorthEast), xNE, yNE);
        viewport->screenCoordinates(ring.at(SouthEast), xSE, ySE);
        qreal easternAngle = qAtan2(ySE - yNE, xSE - xNE) - M_PI / 2;

        painter->setPen(Qt::DashLine);
        painter->setBrush(Qt::NoBrush);
        painter->drawPolygon(ring);

        qreal projectedAngle = 0;

        for (int i = NorthWest; i != Polygon; ++i) {
            // Assign handle icon orientation due to the projection
            if (i == NorthWest || i == West || i == SouthWest) {
                projectedAngle = westernAngle;
            } else if (i == NorthEast || i == East || i == SouthEast) {
                projectedAngle = easternAngle;
            } else if (i == North || i == South) {
                projectedAngle = (westernAngle + easternAngle) / 2;
            }
            QTransform trans;
            trans.rotateRadians(projectedAngle);
            if (m_editStatus == Resize) {
                if (m_hoveredHandle != i) {
                    painter->drawImage(coordinateList.at(i), m_resizeIcons.at(2 * i).transformed(trans, Qt::SmoothTransformation));
                } else {
                    painter->drawImage(coordinateList.at(i), m_resizeIcons.at(2 * i + 1).transformed(trans, Qt::SmoothTransformation));
                }
            } else if (m_editStatus == Rotate) {
                if (m_hoveredHandle != i) {
                    painter->drawImage(coordinateList.at(i), m_rotateIcons.at(2 * i).transformed(trans, Qt::SmoothTransformation));
                } else {
                    painter->drawImage(coordinateList.at(i), m_rotateIcons.at(2 * i + 1).transformed(trans, Qt::SmoothTransformation));
                }
            }
        }
    }
    painter->restore();
}

bool GroundOverlayFrame::containsPoint(const QPoint &eventPos) const
{
    for (const QRegion &region : m_regionList) {
        if (region.contains(eventPos)) {
            return true;
        }
    }

    // This is a bugfix to handle the events even if they occur outside of this object,
    // so when rotating or resizing the mouseReleaseEvent is handled successfully
    // TODO: maybe find a better way?
    return m_movedHandle != NoRegion || m_hoveredHandle != NoRegion;
}

void GroundOverlayFrame::dealWithItemChange(const SceneGraphicsItem *other)
{
    Q_UNUSED(other);
}

void GroundOverlayFrame::move(const GeoDataCoordinates &source, const GeoDataCoordinates &destination)
{
    // not implemented yet
    Q_UNUSED(source);
    Q_UNUSED(destination);
}

bool GroundOverlayFrame::mousePressEvent(QMouseEvent *event)
{
    // React to all ellipse as well as to the polygon.
    for (int i = 0; i < m_regionList.size(); ++i) {
        if (m_regionList.at(i).contains(event->pos())) {
            m_movedHandle = i;

            qreal lon, lat;
            m_viewport->geoCoordinates(event->pos().x(), event->pos().y(), lon, lat, GeoDataCoordinates::Radian);
            m_movedHandleGeoCoordinates.set(lon, lat);
            m_movedHandleScreenCoordinates = event->pos();
            m_previousRotation = m_overlay->latLonBox().rotation();

            if (m_movedHandle == Polygon) {
                m_editStatusChangeNeeded = true;
            }

            return true;
        }
    }

    return false;
}

bool GroundOverlayFrame::mouseMoveEvent(QMouseEvent *event)
{
    if (!m_viewport) {
        return false;
    }

    // Catch hover events.
    if (m_movedHandle == NoRegion) {
        for (int i = 0; i < m_regionList.size(); ++i) {
            if (m_regionList.at(i).contains(event->pos())) {
                if (i == Polygon) {
                    setRequest(ChangeCursorOverlayBodyHover);
                } else {
                    setRequest(ChangeCursorOverlayRotateHover);
                }
                m_hoveredHandle = i;
                return true;
            }
        }
        m_hoveredHandle = NoRegion;
        return true;
    } else {
        m_editStatusChangeNeeded = false;
    }

    if (geodata_cast<GeoDataPolygon>(placemark()->geometry())) {
        qreal lon, lat;
        m_viewport->geoCoordinates(event->pos().x(), event->pos().y(), lon, lat, GeoDataCoordinates::Radian);

        if (m_editStatus == Resize) {
            GeoDataCoordinates coord(lon, lat);
            GeoDataCoordinates rotatedCoord(coord);

            if (m_overlay->latLonBox().rotation() != 0) {
                rotatedCoord = coord.rotateAround(m_overlay->latLonBox().center(), -m_overlay->latLonBox().rotation());
            }

            if (m_movedHandle == NorthWest) {
                m_overlay->latLonBox().setNorth(rotatedCoord.latitude());
                m_overlay->latLonBox().setWest(rotatedCoord.longitude());
            } else if (m_movedHandle == SouthWest) {
                m_overlay->latLonBox().setSouth(rotatedCoord.latitude());
                m_overlay->latLonBox().setWest(rotatedCoord.longitude());
            } else if (m_movedHandle == SouthEast) {
                m_overlay->latLonBox().setSouth(rotatedCoord.latitude());
                m_overlay->latLonBox().setEast(rotatedCoord.longitude());
            } else if (m_movedHandle == NorthEast) {
                m_overlay->latLonBox().setNorth(rotatedCoord.latitude());
                m_overlay->latLonBox().setEast(rotatedCoord.longitude());
            } else if (m_movedHandle == North) {
                m_overlay->latLonBox().setNorth(rotatedCoord.latitude());
            } else if (m_movedHandle == South) {
                m_overlay->latLonBox().setSouth(rotatedCoord.latitude());
            } else if (m_movedHandle == East) {
                m_overlay->latLonBox().setEast(rotatedCoord.longitude());
            } else if (m_movedHandle == West) {
                m_overlay->latLonBox().setWest(rotatedCoord.longitude());
            }

        } else if (m_editStatus == Rotate) {
            if (m_movedHandle != Polygon) {
                QPoint center = m_regionList.at(Polygon).boundingRect().center();
                qreal angle1 = qAtan2(event->pos().y() - center.y(), event->pos().x() - center.x());
                qreal angle2 = qAtan2(m_movedHandleScreenCoordinates.y() - center.y(), m_movedHandleScreenCoordinates.x() - center.x());
                m_overlay->latLonBox().setRotation(angle2 - angle1 + m_previousRotation);
            }
        }

        if (m_movedHandle == Polygon) {
            const qreal centerLonDiff = lon - m_movedHandleGeoCoordinates.longitude();
            const qreal centerLatDiff = lat - m_movedHandleGeoCoordinates.latitude();

            m_overlay->latLonBox().setBoundaries(m_overlay->latLonBox().north() + centerLatDiff,
                                                 m_overlay->latLonBox().south() + centerLatDiff,
                                                 m_overlay->latLonBox().east() + centerLonDiff,
                                                 m_overlay->latLonBox().west() + centerLonDiff);

            m_movedHandleGeoCoordinates.set(lon, lat);
        }

        update();
        return true;
    }
    return false;
}

bool GroundOverlayFrame::mouseReleaseEvent(QMouseEvent *event)
{
    Q_UNUSED(event);

    m_movedHandle = NoRegion;
    m_textureLayer->reset();

    if (m_editStatusChangeNeeded) {
        if (m_editStatus == Resize) {
            m_editStatus = Rotate;
        } else {
            m_editStatus = Resize;
        }
    }

    return true;
}

void GroundOverlayFrame::update()
{
    GeoDataLatLonBox overlayLatLonBox = m_overlay->latLonBox();
    auto poly = dynamic_cast<GeoDataPolygon *>(placemark()->geometry());
    poly->outerBoundary().clear();

    GeoDataCoordinates rotatedCoord;

    GeoDataCoordinates northWest(overlayLatLonBox.west(), overlayLatLonBox.north());
    rotatedCoord = northWest.rotateAround(overlayLatLonBox.center(), overlayLatLonBox.rotation());
    poly->outerBoundary().append(rotatedCoord);

    GeoDataCoordinates southWest(overlayLatLonBox.west(), overlayLatLonBox.south());
    rotatedCoord = southWest.rotateAround(overlayLatLonBox.center(), overlayLatLonBox.rotation());
    poly->outerBoundary().append(rotatedCoord);

    GeoDataCoordinates southEast(overlayLatLonBox.east(), overlayLatLonBox.south());
    rotatedCoord = southEast.rotateAround(overlayLatLonBox.center(), overlayLatLonBox.rotation());
    poly->outerBoundary().append(rotatedCoord);

    GeoDataCoordinates northEast(overlayLatLonBox.east(), overlayLatLonBox.north());
    rotatedCoord = northEast.rotateAround(overlayLatLonBox.center(), overlayLatLonBox.rotation());
    poly->outerBoundary().append(rotatedCoord);
}

void GroundOverlayFrame::dealWithStateChange(SceneGraphicsItem::ActionState previousState)
{
    Q_UNUSED(previousState);
}

const char *GroundOverlayFrame::graphicType() const
{
    return SceneGraphicsTypes::SceneGraphicGroundOverlay;
}

}
