// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2011 Konstantin Oblaukhov <oblaukhov.konstantin@gmail.com>
//

#include "AbstractGeoPolygonGraphicsItem.h"

#include "GeoDataBuilding.h"
#include "GeoDataIconStyle.h"
#include "GeoDataLatLonAltBox.h"
#include "GeoDataLineStyle.h"
#include "GeoDataLinearRing.h"
#include "GeoDataPlacemark.h"
#include "GeoDataPolyStyle.h"
#include "GeoDataPolygon.h"
#include "GeoDataStyle.h"
#include "GeoPainter.h"
#include "MarbleDebug.h"
#include "OsmPlacemarkData.h"
#include "ViewportParams.h"

#include <QImageReader>
#include <QPixmapCache>
#include <QtMath>

namespace Marble
{

const void *AbstractGeoPolygonGraphicsItem::s_previousStyle = nullptr;

AbstractGeoPolygonGraphicsItem::AbstractGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataPolygon *polygon)
    : GeoGraphicsItem(placemark)
    , m_polygon(polygon)
    , m_ring(nullptr)
    , m_building(nullptr)
{
}

AbstractGeoPolygonGraphicsItem::AbstractGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataLinearRing *ring)
    : GeoGraphicsItem(placemark)
    , m_polygon(nullptr)
    , m_ring(ring)
    , m_building(nullptr)
{
}

AbstractGeoPolygonGraphicsItem::AbstractGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataBuilding *building)
    : GeoGraphicsItem(placemark)
    , m_polygon(nullptr)
    , m_ring(nullptr)
    , m_building(building)
{
}

AbstractGeoPolygonGraphicsItem::~AbstractGeoPolygonGraphicsItem() = default;

const GeoDataLatLonAltBox &AbstractGeoPolygonGraphicsItem::latLonAltBox() const
{
    if (m_polygon) {
        return m_polygon->latLonAltBox();
    } else if (m_ring) {
        return m_ring->latLonAltBox();
    }

    return m_building->latLonAltBox();
}

void AbstractGeoPolygonGraphicsItem::paint(GeoPainter *painter, const ViewportParams *viewport, const QString &layer, int tileZoomLevel)
{
    Q_UNUSED(layer);
    Q_UNUSED(tileZoomLevel);

    bool isValid = true;
    if (s_previousStyle != style().data()) {
        isValid = configurePainter(painter, *viewport);
    }
    s_previousStyle = style().data();

    if (!isValid)
        return;

    if (m_polygon) {
        bool innerResolved = false;

        for (auto const &ring : m_polygon->innerBoundaries()) {
            if (viewport->resolves(ring.latLonAltBox(), 4)) {
                innerResolved = true;
                break;
            }
        }

        if (innerResolved) {
            painter->drawPolygon(*m_polygon);
        } else {
            painter->drawPolygon(m_polygon->outerBoundary());
        }
    } else if (m_ring) {
        painter->drawPolygon(*m_ring);
    }
}

bool AbstractGeoPolygonGraphicsItem::contains(const QPoint &screenPosition, const ViewportParams *viewport) const
{
    auto const visualCategory = static_cast<const GeoDataPlacemark *>(feature())->visualCategory();
    if (visualCategory == GeoDataPlacemark::Landmass || visualCategory == GeoDataPlacemark::UrbanArea
        || (visualCategory >= GeoDataPlacemark::LanduseAllotments && visualCategory <= GeoDataPlacemark::LanduseVineyard)) {
        return false;
    }

    double lon, lat;
    viewport->geoCoordinates(screenPosition.x(), screenPosition.y(), lon, lat, GeoDataCoordinates::Radian);
    auto const coordinates = GeoDataCoordinates(lon, lat);
    if (m_polygon) {
        return m_polygon->contains(coordinates);
    } else if (m_ring) {
        return m_ring->contains(coordinates);
    }
    return false;
}

bool AbstractGeoPolygonGraphicsItem::configurePainter(GeoPainter *painter, const ViewportParams &viewport) const
{
    QPen currentPen = painter->pen();
    GeoDataStyle::ConstPtr style = this->style();
    if (!style) {
        painter->setPen(QPen()); // "style-less" polygons: a 1px black solid line
    } else {
        const GeoDataPolyStyle &polyStyle = style->polyStyle();

        if (polyStyle.outline()) {
            const GeoDataLineStyle &lineStyle = style->lineStyle();

            // To save performance we avoid making changes to the painter's pen.
            // So we first take a copy of the actual painter pen, make changes to it
            // and only if the resulting pen is different from the actual pen
            // we replace the painter's pen with our new pen.

            // We want to avoid the mandatory detach in QPen::setColor(),
            // so we carefully check whether applying the setter is needed
            currentPen.setColor(lineStyle.paintedColor());
            currentPen.setWidthF(lineStyle.width());
            currentPen.setCapStyle(lineStyle.capStyle());
            currentPen.setStyle(lineStyle.penStyle());

            if (painter->pen().color() != currentPen.color()) {
                painter->setPen(currentPen);
            }
        } else {
            // polygons without outline: Qt::NoPen (not drawn)
            if (currentPen.style() != Qt::NoPen) {
                painter->setPen(Qt::NoPen);
            }
        }

        if (!polyStyle.fill()) {
            painter->setBrush(Qt::transparent);
        } else {
            const QColor paintedColor = polyStyle.paintedColor();
            if (painter->brush().color() != paintedColor || painter->brush().style() != polyStyle.brushStyle()) {
                if (!polyStyle.texturePath().isEmpty() || !polyStyle.textureImage().isNull()) {
                    GeoDataCoordinates coords = latLonAltBox().center();
                    qreal x, y;
                    viewport.screenCoordinates(coords, x, y);
                    QBrush brush(texture(polyStyle.texturePath(), paintedColor));
                    painter->setBrush(brush);
                    painter->setBrushOrigin(QPoint(x, y));
                } else {
                    painter->setBrush(QBrush(paintedColor, polyStyle.brushStyle()));
                }
            }
        }
    }

    return true;
}

int AbstractGeoPolygonGraphicsItem::extractElevation(const GeoDataPlacemark &placemark)
{
    int elevation = 0;

    const OsmPlacemarkData &osmData = placemark.osmData();

    const auto tagIter = osmData.findTag(QStringLiteral("ele"));
    if (tagIter != osmData.tagsEnd()) {
        elevation = tagIter.value().toInt();
    }

    return elevation;
}

QPixmap AbstractGeoPolygonGraphicsItem::texture(const QString &texturePath, const QColor &color) const
{
    QString const key = QString::number(color.rgba()) + QLatin1Char('/') + texturePath;
    QPixmap texture;
    if (!QPixmapCache::find(key, &texture)) {
        QImageReader imageReader(style()->polyStyle().resolvePath(texturePath));
        texture = QPixmap::fromImageReader(&imageReader);

        if (texture.hasAlphaChannel()) {
            QPixmap pixmap(texture.size());
            pixmap.fill(color);
            QPainter imagePainter(&pixmap);
            imagePainter.drawPixmap(0, 0, texture);
            imagePainter.end();
            texture = pixmap;
        }
        QPixmapCache::insert(key, texture);
    }
    return texture;
}

void AbstractGeoPolygonGraphicsItem::setLinearRing(GeoDataLinearRing *ring)
{
    Q_ASSERT(m_building);
    Q_ASSERT(!m_polygon);
    m_ring = ring;
}

void AbstractGeoPolygonGraphicsItem::setPolygon(GeoDataPolygon *polygon)
{
    Q_ASSERT(m_building);
    Q_ASSERT(!m_ring);
    m_polygon = polygon;
}

}
