/* ============================================================
 *
 * This file is a part of digiKam project
 * https://www.digikam.org
 *
 * Date        : 2023-05-15
 * Description : geolocation engine based on Marble.
 *               (c) 2007-2022 Marble Team
 *               https://invent.kde.org/education/marble/-/raw/master/data/credits_authors.html
 *
 * SPDX-FileCopyrightText: 2023-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * ============================================================ */

#include "PlacemarkLayout.h"

// Qt includes

#include <QAbstractItemModel>
#include <QList>
#include <QPoint>
#include <QVectorIterator>
#include <QFont>
#include <QFontMetrics>
#include <QItemSelectionModel>
#include <qmath.h>

// Local includes

#include "GeoDataLatLonAltBox.h"
#include "GeoDataLatLonBox.h"
#include "GeoDataStyle.h"
#include "GeoDataIconStyle.h"
#include "GeoDataLabelStyle.h"
#include "OsmPlacemarkData.h"
#include "MarbleGlobal.h"
#include "PlacemarkLayer.h"
#include "MarbleClock.h"
#include "MarblePlacemarkModel.h"
#include "MarbleDirs.h"
#include "ViewportParams.h"
#include "TileId.h"
#include "TileCoordsPyramid.h"
#include "VisiblePlacemark.h"
#include "MathHelper.h"
#include "StyleBuilder.h"
#include "digikam_debug.h"

namespace
{

/**
 * Helper function that checks for available room for the label
 */
bool hasRoomFor(const QVector<Marble::VisiblePlacemark*>& placemarks, const QRectF& boundingBox)
{
    // Check if there is another label or symbol that overlaps.

    QVector<Marble::VisiblePlacemark*>::const_iterator beforeItEnd = placemarks.constEnd();

    for (QVector<Marble::VisiblePlacemark*>::ConstIterator beforeIt = placemarks.constBegin();
         beforeIt != beforeItEnd; ++beforeIt)
    {
        if (boundingBox.intersects((*beforeIt)->boundingBox()))
        {
            return false;
        }
    }

    return true;
}

} // nmespace

namespace Marble
{

QSet<GeoDataPlacemark::GeoDataVisualCategory> acceptedVisualCategories()
{
    QSet<GeoDataPlacemark::GeoDataVisualCategory> visualCategories;

    visualCategories
            << GeoDataPlacemark::SmallCity
            << GeoDataPlacemark::SmallCountyCapital
            << GeoDataPlacemark::SmallStateCapital
            << GeoDataPlacemark::SmallNationCapital
            << GeoDataPlacemark::MediumCity
            << GeoDataPlacemark::MediumCountyCapital
            << GeoDataPlacemark::MediumStateCapital
            << GeoDataPlacemark::MediumNationCapital
            << GeoDataPlacemark::BigCity
            << GeoDataPlacemark::BigCountyCapital
            << GeoDataPlacemark::BigStateCapital
            << GeoDataPlacemark::BigNationCapital
            << GeoDataPlacemark::LargeCity
            << GeoDataPlacemark::LargeCountyCapital
            << GeoDataPlacemark::LargeStateCapital
            << GeoDataPlacemark::LargeNationCapital
            << GeoDataPlacemark::Nation
            << GeoDataPlacemark::Mountain
            << GeoDataPlacemark::Volcano
            << GeoDataPlacemark::Mons
            << GeoDataPlacemark::Valley
            << GeoDataPlacemark::Continent
            << GeoDataPlacemark::Ocean
            << GeoDataPlacemark::OtherTerrain
            << GeoDataPlacemark::Crater
            << GeoDataPlacemark::Mare
            << GeoDataPlacemark::GeographicPole
            << GeoDataPlacemark::MagneticPole
            << GeoDataPlacemark::ShipWreck
            << GeoDataPlacemark::PlaceSuburb
            << GeoDataPlacemark::PlaceHamlet
            << GeoDataPlacemark::PlaceLocality;

    return visualCategories;
}


PlacemarkLayout::PlacemarkLayout(QAbstractItemModel* placemarkModel,
                                 QItemSelectionModel* selectionModel,
                                 MarbleClock* clock,
                                 const StyleBuilder* styleBuilder,
                                 QObject* parent)
    : QObject(parent),
      m_placemarkModel(placemarkModel),
      m_selectionModel(selectionModel),
      m_clock(clock),
      m_acceptedVisualCategories(acceptedVisualCategories()),
      m_showPlaces(false),
      m_showCities(false),
      m_showTerrain(false),
      m_showOtherPlaces(false),
      m_showLandingSites(false),
      m_showCraters(false),
      m_showMaria(false),
      m_maxLabelHeight(maxLabelHeight()),
      m_styleResetRequested(true),
      m_styleBuilder(styleBuilder),
      m_lastPlacemarkAvailable(false)
{
    Q_ASSERT(m_placemarkModel);

    connect(m_selectionModel,  SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
            this, SLOT(requestStyleReset()));

    connect(m_placemarkModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
            this, SLOT(resetCacheData()));

    connect(m_placemarkModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
            this, SLOT(addPlacemarks(QModelIndex,int,int)));

    connect(m_placemarkModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
            this, SLOT(removePlacemarks(QModelIndex,int,int)));

    connect(m_placemarkModel, SIGNAL(modelReset()),
            this, SLOT(resetCacheData()));
}

PlacemarkLayout::~PlacemarkLayout()
{
    styleReset();
}

void PlacemarkLayout::setShowPlaces(bool show)
{
    m_showPlaces = show;
}

void PlacemarkLayout::setShowCities(bool show)
{
    m_showCities = show;
}

void PlacemarkLayout::setShowTerrain(bool show)
{
    m_showTerrain = show;
}

void PlacemarkLayout::setShowOtherPlaces(bool show)
{
    m_showOtherPlaces = show;
}

void PlacemarkLayout::setShowLandingSites(bool show)
{
    m_showLandingSites = show;
}

void PlacemarkLayout::setShowCraters(bool show)
{
    m_showCraters = show;
}

void PlacemarkLayout::setShowMaria(bool show)
{
    m_showMaria = show;
}

void PlacemarkLayout::requestStyleReset()
{
    qCDebug(DIGIKAM_MARBLE_LOG) << "Style reset requested.";
    m_styleResetRequested = true;
}

void PlacemarkLayout::styleReset()
{
    clearCache();
    m_maxLabelHeight = maxLabelHeight();
    m_styleResetRequested = false;
}

void PlacemarkLayout::clearCache()
{
    m_paintOrder.clear();
    m_lastPlacemarkAvailable = false;
    m_lastPlacemarkLabelRect = QRectF();
    m_lastPlacemarkSymbolRect = QRectF();
    m_labelArea = 0;
    qDeleteAll(m_visiblePlacemarks);
    m_visiblePlacemarks.clear();
}

QVector<const GeoDataFeature*> PlacemarkLayout::whichPlacemarkAt(const QPoint& curpos)
{
    if (m_styleResetRequested)
    {
        styleReset();
    }

    QVector<const GeoDataFeature*> ret;

    for (VisiblePlacemark* const mark : std::as_const(m_paintOrder))
    {
        if (mark->labelRect().contains(curpos) || mark->symbolRect().contains(curpos))
        {
            ret.append(mark->placemark());
        }
    }

    return ret;
}

int PlacemarkLayout::maxLabelHeight()
{
    QFont const standardFont(QStringLiteral("Sans Serif"));
    return QFontMetrics(standardFont).height();
}

/// feed an internal QMap of placemarks with TileId as key when model changes
void PlacemarkLayout::addPlacemarks(const QModelIndex& parent, int first, int last)
{
    Q_ASSERT(first < m_placemarkModel->rowCount());
    Q_ASSERT(last < m_placemarkModel->rowCount());

    for (int i = first; i <= last; ++i)
    {
        QModelIndex index = m_placemarkModel->index(i, 0, parent);

        Q_ASSERT(index.isValid());

        auto const object = qvariant_cast<GeoDataObject*>(index.data(MarblePlacemarkModel::ObjectPointerRole));

        if (const GeoDataPlacemark* placemark = geodata_cast<GeoDataPlacemark>(object))
        {
            const GeoDataCoordinates coordinates = placemarkIconCoordinates(placemark);

            if (!coordinates.isValid())
            {
                continue;
            }

            if (placemark->hasOsmData())
            {
                qint64 const osmId = placemark->osmData().id();

                if (osmId > 0)
                {
                    if (m_osmIds.contains(osmId))
                    {
                        continue; // placemark is already shown
                    }

                    m_osmIds << osmId;
                }
            }

            int zoomLevel = placemark->zoomLevel();
            TileId key = TileId::fromCoordinates(coordinates, zoomLevel);
            m_placemarkCache[key].append(placemark);
        }
    }

    Q_EMIT repaintNeeded();
}

void PlacemarkLayout::removePlacemarks(const QModelIndex& parent, int first, int last)
{
    Q_ASSERT(first < m_placemarkModel->rowCount());
    Q_ASSERT(last < m_placemarkModel->rowCount());

    for (int i = first; i <= last; ++i)
    {
        QModelIndex index = m_placemarkModel->index(i, 0, parent);

        Q_ASSERT(index.isValid());

        const GeoDataPlacemark* placemark = static_cast<GeoDataPlacemark*>(qvariant_cast<GeoDataObject*>(index.data(MarblePlacemarkModel::ObjectPointerRole)));
        const GeoDataCoordinates coordinates = placemarkIconCoordinates(placemark);

        if (!coordinates.isValid())
        {
            continue;
        }

        int zoomLevel = placemark->zoomLevel();
        TileId key    = TileId::fromCoordinates(coordinates, zoomLevel);
        delete m_visiblePlacemarks[placemark];
        m_visiblePlacemarks.remove(placemark);
        m_placemarkCache[key].removeAll(placemark);

        if (placemark->hasOsmData())
        {
            qint64 const osmId = placemark->osmData().id();

            if (osmId > 0)
            {
                m_osmIds.remove(osmId);
            }
        }
    }

    Q_EMIT repaintNeeded();
}

void PlacemarkLayout::resetCacheData()
{
    const int rowCount = m_placemarkModel->rowCount();

    m_osmIds.clear();
    m_placemarkCache.clear();
    qDeleteAll(m_visiblePlacemarks);
    m_visiblePlacemarks.clear();
    requestStyleReset();
    addPlacemarks(m_placemarkModel->index(0, 0), 0, rowCount);

    Q_EMIT repaintNeeded();
}

QSet<TileId> PlacemarkLayout::visibleTiles(const ViewportParams& viewport, int zoomLevel)
{
    /*
     * rely on m_placemarkCache to find the placemarks for the tiles which
     * matter. The top level tiles have the more popular placemarks,
     * the bottom level tiles have the smaller ones, and we only get the ones
     * matching our latLonAltBox.
     */

    qreal north, south, east, west;
    viewport.viewLatLonAltBox().boundaries(north, south, east, west);
    QSet<TileId> tileIdSet;
    QVector<GeoDataLatLonBox> geoRects;

    if (west <= east)
    {
        geoRects << GeoDataLatLonBox(north, south, east, west);
    }
    else
    {
        geoRects << GeoDataLatLonBox(north, south, M_PI, west);
        geoRects << GeoDataLatLonBox(north, south, east, -M_PI);
    }

    for (const GeoDataLatLonBox& geoRect : std::as_const(geoRects))
    {
        TileId key;
        QRect rect;

        key = TileId::fromCoordinates(GeoDataCoordinates(geoRect.west(), north), zoomLevel);
        rect.setLeft(key.x());
        rect.setTop(key.y());

        key = TileId::fromCoordinates(GeoDataCoordinates(geoRect.east(), south), zoomLevel);
        rect.setRight(key.x());
        rect.setBottom(key.y());

        TileCoordsPyramid pyramid(0, zoomLevel);
        pyramid.setBottomLevelCoords(rect);

        for (int level = pyramid.topLevel() ; level <= pyramid.bottomLevel() ; ++level)
        {
            QRect const coords = pyramid.coords(level);
            int x1, y1, x2, y2;
            coords.getCoords(&x1, &y1, &x2, &y2);

            for (int x = x1 ; x <= x2 ; ++x)
            {
                for (int y = y1 ; y <= y2 ; ++y)
                {
                    TileId const tileId(0, level, x, y);
                    tileIdSet.insert(tileId);
                }
            }
        }
    }

    return tileIdSet;
}

QVector<VisiblePlacemark*> PlacemarkLayout::generateLayout(const ViewportParams* viewport, int tileLevel)
{
    m_runtimeTrace.clear();

    if (m_placemarkModel->rowCount() <= 0)
    {
        clearCache();
        return QVector<VisiblePlacemark*>();
    }

    if (m_styleResetRequested)
    {
        styleReset();
    }

    if (m_maxLabelHeight == 0)
    {
        clearCache();
        return QVector<VisiblePlacemark*>();
    }

    QList<const GeoDataPlacemark*> placemarkList;
    int currentMaxLabelHeight;

    do
    {
        currentMaxLabelHeight = m_maxLabelHeight;
        const int secnumber   = viewport->height() / m_maxLabelHeight + 1;
        m_rowsection.clear();
        m_rowsection.resize(secnumber);

        m_paintOrder.clear();
        m_lastPlacemarkAvailable  = false;
        m_lastPlacemarkLabelRect  = QRectF();
        m_lastPlacemarkSymbolRect = QRectF();
        m_labelArea               = 0;

        // First handle the selected placemarks as they have the highest priority.

        const QModelIndexList selectedIndexes = m_selectionModel->selection().indexes();
        const auto& viewLatLonAltBox = viewport->viewLatLonAltBox();

        for (int i = 0 ; i < selectedIndexes.count() ; ++i)
        {
            const QModelIndex index              = selectedIndexes.at(i);
            const GeoDataPlacemark* placemark    = static_cast<GeoDataPlacemark*>(qvariant_cast<GeoDataObject*>(index.data(MarblePlacemarkModel::ObjectPointerRole)));
            const GeoDataCoordinates coordinates = placemarkIconCoordinates(placemark);

            if (!coordinates.isValid())
            {
                continue;
            }

            qreal x = 0;
            qreal y = 0;

            if (
                !viewLatLonAltBox.contains(coordinates) ||
                ! viewport->screenCoordinates(coordinates, x, y)
               )
            {
                continue;
            }

            if (layoutPlacemark(placemark, coordinates, x, y, true))
            {
                // Make sure not to draw more placemarks on the screen than
                // specified by placemarksOnScreenLimit().

                if (placemarksOnScreenLimit(viewport->size()))
                {
                    break;
                }
            }
        }

        // Now handle all other placemarks...

        const QItemSelection selection = m_selectionModel->selection();

        placemarkList.clear();
        const auto tiles = visibleTiles(*viewport, tileLevel);

        for (const TileId& tileId : tiles)
        {
            placemarkList += m_placemarkCache.value(tileId);
        }

        std::sort(placemarkList.begin(), placemarkList.end(), GeoDataPlacemark::placemarkLayoutOrderCompare);

        for (const GeoDataPlacemark* placemark : std::as_const(placemarkList))
        {
            const GeoDataCoordinates coordinates = placemarkIconCoordinates(placemark);

            if (!coordinates.isValid())
            {
                continue;
            }

            int zoomLevel = placemark->zoomLevel();

            if (zoomLevel > 20)
            {
                break;
            }

            qreal x = 0;
            qreal y = 0;

            if (
                !viewLatLonAltBox.contains(coordinates) ||
                ! viewport->screenCoordinates(coordinates, x, y)
               )
            {
                continue;
            }

            if (!placemark->isGloballyVisible())
            {
                continue;
            }

            const GeoDataPlacemark::GeoDataVisualCategory visualCategory = placemark->visualCategory();

            // Skip city marks if we're not showing cities.

            if (
                !m_showCities
                && (visualCategory >= GeoDataPlacemark::SmallCity)
                && (visualCategory <= GeoDataPlacemark::Nation)
               )
            {
                continue;
            }

            // Skip terrain marks if we're not showing terrain.

            if (
                !m_showTerrain
                && (visualCategory >= GeoDataPlacemark::Mountain)
                && (visualCategory <= GeoDataPlacemark::OtherTerrain)
               )
            {
                continue;
            }

            // Skip other places if we're not showing other places.

            if (!m_showOtherPlaces
                && visualCategory >= GeoDataPlacemark::GeographicPole
                && visualCategory <= GeoDataPlacemark::Observatory)
            {
                continue;
            }

            // Skip landing sites if we're not showing landing sites.

            if (!m_showLandingSites
                && visualCategory >= GeoDataPlacemark::MannedLandingSite
                && visualCategory <= GeoDataPlacemark::UnmannedHardLandingSite)
            {
                continue;
            }

            // Skip craters if we're not showing craters.

            if (!m_showCraters
                && visualCategory == GeoDataPlacemark::Crater)
            {
                continue;
            }

            // Skip maria if we're not showing maria.

            if (!m_showMaria
                && visualCategory == GeoDataPlacemark::Mare)
            {
                continue;
            }

            if (!m_showPlaces
                && visualCategory >= GeoDataPlacemark::GeographicPole
                && visualCategory <= GeoDataPlacemark::Observatory)
            {
                continue;
            }

            // We handled selected placemarks already, so we skip them here...
            // Assuming that only a small amount of places is selected
            // we check for the selected state after all other filters

            bool isSelected = false;
            const auto idx  = selection.indexes();

            for (const QModelIndex& index : idx)
            {
                const GeoDataPlacemark* mark = static_cast<GeoDataPlacemark*>(qvariant_cast<GeoDataObject*>(index.data(MarblePlacemarkModel::ObjectPointerRole)));

                if (mark == placemark)
                {
                    isSelected = true;
                    break;
                }
            }

            if (isSelected)
            {
                continue;
            }

            if (layoutPlacemark(placemark, coordinates, x, y, isSelected))
            {
                // Make sure not to draw more placemarks on the screen than
                // specified by placemarksOnScreenLimit().

                if (placemarksOnScreenLimit(viewport->size()))
                {
                    break;
                }
            }
        }

        if (m_visiblePlacemarks.size() > qMax(100, 4 * m_paintOrder.size()))
        {
            auto const extendedBox = viewLatLonAltBox.scaled(2.0, 2.0);
            QVector<VisiblePlacemark*> outdated;

            for (auto placemark : std::as_const(m_visiblePlacemarks))
            {
                if (!extendedBox.contains(placemark->coordinates()))
                {
                    outdated << placemark;
                }
            }

            for (auto placemark : std::as_const(outdated))
            {
                delete m_visiblePlacemarks.take(placemark->placemark());
            }
        }
    }
    while (currentMaxLabelHeight != m_maxLabelHeight);

    m_runtimeTrace = QStringLiteral("Placemarks: %1 Drawn: %2").arg(placemarkList.count()).arg(m_paintOrder.size());
    return m_paintOrder;
}

QString PlacemarkLayout::runtimeTrace() const
{
    return m_runtimeTrace;
}

QList<VisiblePlacemark*> PlacemarkLayout::visiblePlacemarks() const
{
    return m_visiblePlacemarks.values();
}

bool PlacemarkLayout::hasPlacemarkAt(const QPoint& pos)
{
    if (m_styleResetRequested)
    {
        styleReset();
    }

    if (m_lastPlacemarkAvailable &&
        (m_lastPlacemarkLabelRect.contains(pos) || m_lastPlacemarkSymbolRect.contains(pos)))
    {
        return true;
    }

    for (VisiblePlacemark* mark : m_paintOrder)
    {
        if (mark->labelRect().contains(pos) || mark->symbolRect().contains(pos))
        {
            m_lastPlacemarkLabelRect = mark->labelRect();
            m_lastPlacemarkSymbolRect = mark->symbolRect();
            m_lastPlacemarkAvailable = true;
            return true;
        }
    }

    return false;
}

bool PlacemarkLayout::layoutPlacemark(const GeoDataPlacemark* placemark, const GeoDataCoordinates& coordinates, qreal x, qreal y, bool selected)
{
    // Find the corresponding visible placemark

    VisiblePlacemark* mark = m_visiblePlacemarks.value(placemark);

    if (!mark)
    {
        // If there is no visible placemark yet for this index,
        // create a new one...

        StyleParameters parameters;

        // @todo: Set / adjust to tile level

        parameters.placemark = placemark;

        auto style = m_styleBuilder->createStyle(parameters);
        mark       = new VisiblePlacemark(placemark, coordinates, style);
        m_visiblePlacemarks.insert(placemark, mark);
        connect(mark, SIGNAL(updateNeeded()), this, SIGNAL(repaintNeeded()));
    }

    GeoDataStyle::ConstPtr style = mark->style();

    // Choose Section

    QPointF hotSpot = mark->hotSpot();
    mark->setSelected(selected);
    mark->setSymbolPosition(QPointF(x - hotSpot.x(), y - hotSpot.y()));

    // Find out whether the area around the placemark is covered already.
    // If there's not enough space free don't add a VisiblePlacemark here.

    const QString labelText = placemark->displayName();
    QRectF labelRect;

    if (!labelText.isEmpty())
    {
        labelRect = roomForLabel(style, x, y, labelText, mark);
    }

    if (labelRect.isEmpty() && mark->symbolPixmap().isNull())
    {
        return false;
    }

    if (!mark->symbolPixmap().isNull() && !hasRoomForPixmap(y, mark))
    {
        return false;
    }

    mark->setLabelRect(labelRect);

    // Add the current placemark to the matching row and its
    // direct neighbors.

    int idx = y / m_maxLabelHeight;

    if (idx - 1 >= 0)
    {
        m_rowsection[ idx - 1 ].append(mark);
    }

    m_rowsection[ idx ].append(mark);

    if (idx + 1 < m_rowsection.size())
    {
        m_rowsection[ idx + 1 ].append(mark);
    }

    m_paintOrder.append(mark);
    QRectF const boundingBox = mark->boundingBox();

    Q_ASSERT(!boundingBox.isEmpty());

    m_labelArea     += boundingBox.width() * boundingBox.height();
    m_maxLabelHeight = qMax(m_maxLabelHeight, qCeil(boundingBox.height()));

    return true;
}

GeoDataCoordinates PlacemarkLayout::placemarkIconCoordinates(const GeoDataPlacemark* placemark) const
{
    GeoDataCoordinates coordinates = placemark->coordinate(m_clock->dateTime());

    if (!m_acceptedVisualCategories.contains(placemark->visualCategory()))
    {
        StyleParameters parameters;
        parameters.placemark = placemark;
        auto style = m_styleBuilder->createStyle(parameters);

        if (style->iconStyle().scaledIcon().isNull())
        {
            return GeoDataCoordinates();
        }
    }

    return coordinates;
}

QRectF PlacemarkLayout::roomForLabel(const GeoDataStyle::ConstPtr& style,
                                     const qreal x, const qreal y,
                                     const QString& labelText,
                                     const VisiblePlacemark* placemark) const
{
    QFont labelFont = style->labelStyle().scaledFont();
    int textHeight = QFontMetrics(labelFont).height();

    int textWidth;

    if (style->labelStyle().glow())
    {
        labelFont.setWeight(QFont::Bold);   // Needed to calculate the correct pixmap size;
        textWidth = (QFontMetrics(labelFont).horizontalAdvance(labelText)
                     + qRound(2 * s_labelOutlineWidth));
    }

    else
    {
        textWidth = (QFontMetrics(labelFont).horizontalAdvance(labelText));
    }

    const QVector<VisiblePlacemark*> currentsec = m_rowsection.at(y / m_maxLabelHeight);
    QRectF const symbolRect = placemark->symbolRect();

    if (style->labelStyle().alignment() == GeoDataLabelStyle::Corner)
    {
        const int symbolWidth = style->iconStyle().scaledIcon().size().width();

        // Check the four possible positions by going through all of them

        for (int i = 0; i < 4; ++i)
        {
            const qreal xPos = (i / 2 == 0) ? x + symbolWidth / 2 + 1 :
                               x - symbolWidth / 2 - 1 - textWidth;
            const qreal yPos = (i % 2 == 0) ? y :
                               y - textHeight;
            const QRectF labelRect = QRectF(xPos, yPos, textWidth, textHeight);

            if (hasRoomFor(currentsec, labelRect.united(symbolRect)))
            {
                // claim the place immediately if it hasn't been used yet

                return labelRect;
            }
        }
    }
    else if (style->labelStyle().alignment() == GeoDataLabelStyle::Center)
    {
        int const offsetY = style->iconStyle().scaledIcon().height() / 2.0;
        QRectF  labelRect = QRectF(x - textWidth / 2, y - offsetY - textHeight,
                                   textWidth, textHeight);

        if (hasRoomFor(currentsec, labelRect.united(symbolRect)))
        {
            // claim the place immediately if it hasn't been used yet
            return labelRect;
        }
    }
    else if (style->labelStyle().alignment() == GeoDataLabelStyle::Right)
    {
        const int symbolWidth = style->iconStyle().scaledIcon().width();
        const qreal startY    = y - textHeight / 2;
        const qreal xPos      = x + symbolWidth / 2 + 1;

        // Check up to seven vertical positions (center, +3, -3 from center)

        for (int i = 0 ; i < 7 ; ++i)
        {
            const qreal increase   = (i / 2) * (textHeight + 1); //intentional integer arithmetics
            const qreal direction  = (i % 2 == 0 ? 1 : -1);
            const qreal yPos       = startY + increase * direction;
            const QRectF labelRect = QRectF(xPos, yPos, textWidth, textHeight);

            if (hasRoomFor(currentsec, labelRect.united(symbolRect)))
            {
                return labelRect;
            }
        }
    }

    // At this point there is no space left for the rectangle anymore.

    return QRectF();
}

bool PlacemarkLayout::hasRoomForPixmap(const qreal y, const VisiblePlacemark* placemark) const
{
    const QVector<VisiblePlacemark*> currentsec = m_rowsection.at(y / m_maxLabelHeight);

    return hasRoomFor(currentsec, placemark->symbolRect());
}

bool PlacemarkLayout::placemarksOnScreenLimit(const QSize& screenSize) const
{
    int ratio = (m_labelArea * 100) / (screenSize.width() * screenSize.height());

    return (ratio >= 40);
}

} // namespace Marble

#include "moc_PlacemarkLayout.cpp"
