// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org>
//

#include "RoutingLayer.h"

#include "AlternativeRoutesModel.h"
#include "GeoDataCoordinates.h"
#include "GeoDataLineString.h"
#include "GeoPainter.h"
#include "Maneuver.h"
#include "MarbleColors.h"
#include "MarbleModel.h"
#include "MarblePlacemarkModel.h"
#include "MarbleWidget.h"
#include "MarbleWidgetPopupMenu.h"
#include "RenderState.h"
#include "Route.h"
#include "RouteRequest.h"
#include "RoutingManager.h"
#include "RoutingModel.h"

#include <QAbstractItemModel>
#include <QAction>
#include <QFileDialog>
#include <QIcon>
#include <QItemSelectionModel>
#include <QMouseEvent>
#include <QPixmap>

namespace Marble
{

class RoutingLayerPrivate
{
    template<class T>
    struct PaintRegion {
        T index;
        QRegion region;

        PaintRegion(const T &index_, const QRegion &region_)
            : index(index_)
            , region(region_)
        {
            // nothing to do
        }
    };

    using ModelRegion = PaintRegion<QModelIndex>;
    using RequestRegion = PaintRegion<int>;

public:
    RoutingLayer *const q;

    QList<ModelRegion> m_instructionRegions;

    QList<RequestRegion> m_regions;

    QList<RequestRegion> m_alternativeRouteRegions;

    QList<ModelRegion> m_placemarks;

    QRegion m_routeRegion;

    int m_movingIndex;

    MarbleWidget *const m_marbleWidget;

    QPixmap m_targetPixmap;

    QPixmap m_standardRoutePoint;

    QPixmap m_activeRoutePoint;

    QRect m_dirtyRect;

    QPoint m_dropStopOver;

    QPoint m_dragStopOver;

    int m_dragStopOverRightIndex;

    RoutingModel *const m_routingModel;

    MarblePlacemarkModel *m_placemarkModel = nullptr;

    QItemSelectionModel *m_selectionModel = nullptr;

    QSize m_pixmapSize;

    RouteRequest *const m_routeRequest;

    MarbleWidgetPopupMenu *m_contextMenu = nullptr;

    QAction *m_removeViaPointAction = nullptr;

    int m_activeMenuIndex;

    AlternativeRoutesModel *const m_alternativeRoutesModel;

    ViewContext m_viewContext;

    bool m_viewportChanged;

    bool m_isInteractive;

    /** Constructor */
    explicit RoutingLayerPrivate(RoutingLayer *parent, MarbleWidget *widget);

    /** Update the cached drag position. Use an empty point to clear it. */
    void storeDragPosition(const QPoint &position);

    // The following methods are mostly only called at one place in the code, but often
    // Inlined to avoid the function call overhead. Having functions here is just to
    // keep the code clean

    /** Returns the same color as the given one with its alpha channel adjusted to the given value */
    static inline QColor alphaAdjusted(const QColor &color, int alpha);

    static QPixmap createRoutePoint(const QColor &penColor, const QColor &brushColor);

    /**
     * Returns the start or destination position if Ctrl key is among the
     * provided modifiers, the cached insert position otherwise
     */
    inline int viaInsertPosition(Qt::KeyboardModifiers modifiers) const;

    /** Paint icons for each placemark in the placemark model */
    inline void renderPlacemarks(GeoPainter *painter);

    /** Paint waypoint polygon */
    inline void renderRoute(GeoPainter *painter);

    /** Paint turn instruction for selected items */
    inline void renderAnnotations(GeoPainter *painter) const;

    /** Paint alternative routes in gray */
    inline void renderAlternativeRoutes(GeoPainter *painter);

    /** Paint icons for trip points etc */
    inline void renderRequest(GeoPainter *painter);

    /** Insert via points or Q_EMIT position signal, if appropriate */
    inline bool handleMouseButtonRelease(QMouseEvent *e);

    /** Select route instructions points, start dragging trip points */
    inline bool handleMouseButtonPress(QMouseEvent *e);

    /** Dragging trip points, route polygon hovering */
    inline bool handleMouseMove(QMouseEvent *e);

    /** True if the given point (screen coordinates) is among the route instruction points */
    inline bool isInfoPoint(const QPoint &point);

    /** True if the given point (screen coordinates) is above an alternative route */
    inline bool isAlternativeRoutePoint(const QPoint &point);

    /** Paint the stopover indicator pixmap at the given position. Also repaints the old position */
    inline void paintStopOver(QRect position);

    /** Removes the stopover indicator pixmap. Also repaints its old position */
    inline void clearStopOver();
};

RoutingLayerPrivate::RoutingLayerPrivate(RoutingLayer *parent, MarbleWidget *widget)
    : q(parent)
    , m_movingIndex(-1)
    , m_marbleWidget(widget)
    , m_targetPixmap(QStringLiteral(":/data/bitmaps/routing_pick.png"))
    , m_standardRoutePoint(
          createRoutePoint(widget->model()->routingManager()->routeColorStandard(), widget->model()->routingManager()->routeColorAlternative()))
    , m_activeRoutePoint(createRoutePoint(widget->model()->routingManager()->routeColorHighlighted(), alphaAdjusted(Oxygen::hotOrange4, 200)))
    , m_dragStopOverRightIndex(-1)
    , m_routingModel(widget->model()->routingManager()->routingModel())
    , m_placemarkModel(nullptr)
    , m_selectionModel(nullptr)
    , m_pixmapSize(22, 22)
    , m_routeRequest(widget->model()->routingManager()->routeRequest())
    , m_activeMenuIndex(-1)
    , m_alternativeRoutesModel(widget->model()->routingManager()->alternativeRoutesModel())
    , m_viewContext(Still)
    , m_viewportChanged(true)
    , m_isInteractive(true)
{
    m_contextMenu = new MarbleWidgetPopupMenu(m_marbleWidget, m_marbleWidget->model());
    m_removeViaPointAction = new QAction(QObject::tr("&Remove this Destination"), q);
    QObject::connect(m_removeViaPointAction, SIGNAL(triggered()), q, SLOT(removeViaPoint()));
    m_contextMenu->addAction(Qt::RightButton, m_removeViaPointAction);
    auto exportAction = new QAction(QObject::tr("&Export Route..."), q);
    QObject::connect(exportAction, SIGNAL(triggered()), q, SLOT(exportRoute()));
    m_contextMenu->addAction(Qt::RightButton, exportAction);
    if (MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen) {
        m_pixmapSize = QSize(38, 38);
    }
}

int RoutingLayerPrivate::viaInsertPosition(Qt::KeyboardModifiers modifiers) const
{
    if (modifiers & Qt::ControlModifier) {
        bool leftHand = m_routeRequest->size() / 2 >= m_dragStopOverRightIndex;
        if (leftHand && m_routeRequest->size() > 2) {
            return 0;
        } else {
            return m_routeRequest->size();
        }
    } else {
        return m_dragStopOverRightIndex;
    }
}

void RoutingLayerPrivate::renderPlacemarks(GeoPainter *painter)
{
    m_placemarks.clear();
    painter->setPen(QColor(Qt::black));
    for (int i = 0; i < m_placemarkModel->rowCount(); ++i) {
        QModelIndex index = m_placemarkModel->index(i, 0);
        QVariant data = index.data(MarblePlacemarkModel::CoordinateRole);
        if (index.isValid() && !data.isNull()) {
            auto pos = data.value<GeoDataCoordinates>();

            auto pixmap = index.data(Qt::DecorationRole).value<QPixmap>();
            if (!pixmap.isNull() && m_selectionModel->isSelected(index)) {
                QIcon selected = QIcon(pixmap);
                QPixmap result = selected.pixmap(m_pixmapSize, QIcon::Selected, QIcon::On);
                painter->drawPixmap(pos, result);
            } else {
                painter->drawPixmap(pos, pixmap);
            }

            const QRegion region = painter->regionFromPixmapRect(pos, m_targetPixmap.width(), m_targetPixmap.height());
            m_placemarks.push_back(ModelRegion(index, region));
        }
    }
}

void RoutingLayerPrivate::renderAlternativeRoutes(GeoPainter *painter)
{
    QPen alternativeRoutePen(m_marbleWidget->model()->routingManager()->routeColorAlternative());
    alternativeRoutePen.setWidth(5);
    painter->setPen(alternativeRoutePen);

    for (int i = 0; i < m_alternativeRoutesModel->rowCount(); ++i) {
        const GeoDataDocument *route = m_alternativeRoutesModel->route(i);
        if (route && route != m_alternativeRoutesModel->currentRoute()) {
            const GeoDataLineString *points = AlternativeRoutesModel::waypoints(route);
            if (points) {
                painter->drawPolyline(*points);
                if (m_viewportChanged && m_isInteractive && m_viewContext == Still) {
                    QRegion region = painter->regionFromPolyline(*points, 8);
                    m_alternativeRouteRegions.push_back(RequestRegion(i, region));
                }
            }
        }
    }
}

void RoutingLayerPrivate::renderRoute(GeoPainter *painter)
{
    GeoDataLineString waypoints = m_routingModel->route().path();

    QPen standardRoutePen(m_marbleWidget->model()->routingManager()->routeColorStandard());
    standardRoutePen.setWidth(5);
    if (m_marbleWidget->model()->routingManager()->state() == RoutingManager::Downloading) {
        standardRoutePen.setStyle(Qt::DotLine);
    }
    painter->setPen(standardRoutePen);

    painter->drawPolyline(waypoints);
    if (m_viewportChanged && m_viewContext == Still) {
        int const offset = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ? 24 : 8;
        if (m_isInteractive) {
            m_routeRegion = painter->regionFromPolyline(waypoints, offset);
        }
    }

    standardRoutePen.setWidth(2);
    painter->setPen(standardRoutePen);

    // Map matched position
    // painter->setBrush( QBrush( Oxygen::brickRed4) );
    // painter->drawEllipse( m_routingModel->route().positionOnRoute(), 8, 8 );

    painter->setBrush(QBrush(m_marbleWidget->model()->routingManager()->routeColorAlternative()));

    if (!m_dropStopOver.isNull()) {
        int dx = 1 + m_pixmapSize.width() / 2;
        int dy = 1 + m_pixmapSize.height() / 2;
        QPoint center = m_dropStopOver - QPoint(dx, dy);
        painter->drawPixmap(center, m_targetPixmap);

        if (!m_dragStopOver.isNull() && m_dragStopOverRightIndex >= 0 && m_dragStopOverRightIndex <= m_routeRequest->size()) {
            QPoint moved = m_dropStopOver - m_dragStopOver;
            if (moved.manhattanLength() > 10) {
                qreal lon(0.0), lat(0.0);
                if (m_marbleWidget->geoCoordinates(m_dropStopOver.x(), m_dropStopOver.y(), lon, lat, GeoDataCoordinates::Radian)) {
                    GeoDataCoordinates drag(lon, lat);
                    standardRoutePen.setStyle(Qt::DotLine);
                    painter->setPen(standardRoutePen);
                    GeoDataLineString lineString;
                    if (m_dragStopOverRightIndex > 0) {
                        lineString << m_routeRequest->at(m_dragStopOverRightIndex - 1);
                    }
                    lineString << drag;
                    if (m_dragStopOverRightIndex < m_routeRequest->size()) {
                        lineString << m_routeRequest->at(m_dragStopOverRightIndex);
                    }
                    painter->drawPolyline(lineString);
                    standardRoutePen.setStyle(Qt::SolidLine);
                    painter->setPen(standardRoutePen);
                }
            }
        }
    }

    if (m_viewContext == Animation) {
        return;
    }

    if (m_routingModel->rowCount() == m_routingModel->route().size()) {
        m_instructionRegions.clear();

        QPen activeRouteSegmentPen(m_marbleWidget->model()->routingManager()->routeColorHighlighted());
        activeRouteSegmentPen.setWidth(6);
        if (m_marbleWidget->model()->routingManager()->state() == RoutingManager::Downloading) {
            activeRouteSegmentPen.setStyle(Qt::DotLine);
        }
        painter->setPen(activeRouteSegmentPen);

        for (int i = 0; i < m_routingModel->rowCount(); ++i) {
            QModelIndex index = m_routingModel->index(i, 0);
            auto pos = index.data(MarblePlacemarkModel::CoordinateRole).value<GeoDataCoordinates>();

            if (m_selectionModel && m_selectionModel->selection().contains(index)) {
                const RouteSegment &segment = m_routingModel->route().at(i);
                const GeoDataLineString currentRoutePoints = segment.path();

                painter->drawPolyline(currentRoutePoints);

                painter->drawPixmap(pos, m_activeRoutePoint);
            } else {
                painter->drawPixmap(pos, m_standardRoutePoint);
            }

            if (m_isInteractive) {
                QRegion region = painter->regionFromEllipse(pos, 12, 12);
                m_instructionRegions.push_front(ModelRegion(index, region));
            }
        }
    }

    if (!m_routingModel->deviatedFromRoute()) {
        GeoDataCoordinates location = m_routingModel->route().currentSegment().nextRouteSegment().maneuver().position();
        QString nextInstruction = m_routingModel->route().currentSegment().nextRouteSegment().maneuver().instructionText();
        if (!nextInstruction.isEmpty()) {
            painter->drawPixmap(location, m_activeRoutePoint);
        }
    }
}

void RoutingLayerPrivate::renderAnnotations(GeoPainter *painter) const
{
    if (!m_selectionModel || m_selectionModel->selection().isEmpty()) {
        // nothing to do
        return;
    }

    for (int i = 0; i < m_routingModel->rowCount(); ++i) {
        QModelIndex index = m_routingModel->index(i, 0);

        if (m_selectionModel->selection().contains(index)) {
            bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
            auto pos = index.data(MarblePlacemarkModel::CoordinateRole).value<GeoDataCoordinates>();
            painter->setPen(QColor(Qt::black));
            painter->setBrush(QBrush(Oxygen::sunYellow6));
            painter->drawAnnotation(pos, index.data().toString(), QSize(smallScreen ? 240 : 120, 0), 10, 30, 5, 5);
        }
    }
}

void RoutingLayerPrivate::renderRequest(GeoPainter *painter)
{
    m_regions.clear();
    for (int i = 0; i < m_routeRequest->size(); ++i) {
        const GeoDataCoordinates pos = m_routeRequest->at(i);
        if (pos.isValid()) {
            QPixmap pixmap = m_routeRequest->pixmap(i);
            painter->drawPixmap(pos, pixmap);
            const QRegion region = painter->regionFromPixmapRect(pos, pixmap.width(), pixmap.height());
            m_regions.push_front(RequestRegion(i, region));
        }
    }
}

void RoutingLayerPrivate::storeDragPosition(const QPoint &pos)
{
    m_dragStopOver = pos;
    m_dragStopOverRightIndex = -1;

    qreal lon(0.0), lat(0.0);
    if (m_routeRequest && !pos.isNull() && m_marbleWidget->geoCoordinates(pos.x(), pos.y(), lon, lat, GeoDataCoordinates::Radian)) {
        GeoDataCoordinates waypoint(lon, lat);
        m_dragStopOverRightIndex = m_routingModel->rightNeighbor(waypoint, m_routeRequest);
    }
}

QColor RoutingLayerPrivate::alphaAdjusted(const QColor &color, int alpha)
{
    QColor result(color);
    result.setAlpha(alpha);
    return result;
}

QPixmap RoutingLayerPrivate::createRoutePoint(const QColor &penColor, const QColor &brushColor)
{
    QPen pen(penColor);
    pen.setWidth(2);

    const QBrush brush(brushColor);

    QPixmap routePoint(QSize(8, 8));
    routePoint.fill(Qt::transparent);

    QPainter painter(&routePoint);
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.setPen(pen);
    painter.setBrush(brush);
    painter.drawEllipse(1, 1, 6, 6);

    return routePoint;
}

bool RoutingLayerPrivate::handleMouseButtonPress(QMouseEvent *e)
{
    for (const RequestRegion &region : std::as_const(m_regions)) {
        if (region.region.contains(e->pos())) {
            if (e->button() == Qt::LeftButton) {
                m_movingIndex = region.index;
                m_dropStopOver = QPoint();
                m_dragStopOver = QPoint();
                return true;
            } else if (e->button() == Qt::RightButton) {
                m_removeViaPointAction->setEnabled(true);
                m_activeMenuIndex = region.index;
                m_contextMenu->showRmbMenu(e->x(), e->y());
                return true;
            } else
                return false;
        }
    }

    for (const ModelRegion &region : std::as_const(m_instructionRegions)) {
        if (region.region.contains(e->pos()) && m_selectionModel) {
            if (e->button() == Qt::LeftButton) {
                QItemSelectionModel::SelectionFlag command = QItemSelectionModel::ClearAndSelect;
                if (m_selectionModel->isSelected(region.index)) {
                    command = QItemSelectionModel::Clear;
                }
                m_selectionModel->select(region.index, command);
                m_dropStopOver = e->pos();
                storeDragPosition(e->pos());
                // annotation and old annotation are dirty, large region
                Q_EMIT q->repaintNeeded();
                return true;
            } else if (e->button() == Qt::RightButton) {
                m_removeViaPointAction->setEnabled(false);
                m_contextMenu->showRmbMenu(e->x(), e->y());
                return true;
            } else
                return false;
        }
    }

    if (m_routeRegion.contains(e->pos())) {
        if (e->button() == Qt::LeftButton) {
            /** @todo: Determine the neighbored via points and insert in order */
            m_dropStopOver = e->pos();
            storeDragPosition(e->pos());
            return true;
        } else if (e->button() == Qt::RightButton) {
            m_removeViaPointAction->setEnabled(false);
            m_contextMenu->showRmbMenu(e->x(), e->y());
            return true;
        } else
            return false;
    }

    if (e->button() != Qt::LeftButton) {
        return false;
    }

    for (const RequestRegion &region : std::as_const(m_alternativeRouteRegions)) {
        if (region.region.contains(e->pos())) {
            m_alternativeRoutesModel->setCurrentRoute(region.index);
            return true;
        }
    }

    for (const ModelRegion &region : std::as_const(m_placemarks)) {
        if (region.region.contains(e->pos())) {
            Q_EMIT q->placemarkSelected(region.index);
            return true;
        }
    }

    return false;
}

bool RoutingLayerPrivate::handleMouseButtonRelease(QMouseEvent *e)
{
    if (e->button() != Qt::LeftButton) {
        return false;
    }

    if (m_movingIndex >= 0) {
        m_movingIndex = -1;
        clearStopOver();
        m_marbleWidget->model()->routingManager()->retrieveRoute();
        return true;
    }

    if (!m_dropStopOver.isNull() && !m_dragStopOver.isNull()) {
        QPoint moved = e->pos() - m_dragStopOver;
        if (moved.manhattanLength() < 10) {
            return false;
        }

        qreal lon(0.0), lat(0.0);
        if (m_dragStopOverRightIndex >= 0 && m_dragStopOverRightIndex <= m_routeRequest->size()
            && m_marbleWidget->geoCoordinates(m_dropStopOver.x(), m_dropStopOver.y(), lon, lat, GeoDataCoordinates::Radian)) {
            GeoDataCoordinates position(lon, lat);
            m_dragStopOverRightIndex = viaInsertPosition(e->modifiers());
            m_routeRequest->insert(m_dragStopOverRightIndex, position);
            clearStopOver();
            m_marbleWidget->model()->routingManager()->retrieveRoute();
            return true;
        }
    }

    return false;
}

bool RoutingLayerPrivate::handleMouseMove(QMouseEvent *e)
{
    qreal lon(0.0), lat(0.0);
    if (m_marbleWidget->geoCoordinates(e->pos().x(), e->pos().y(), lon, lat, GeoDataCoordinates::Radian)) {
        if (m_movingIndex >= 0) {
            GeoDataCoordinates moved(lon, lat);
            m_routeRequest->setPosition(m_movingIndex, moved);
            m_marbleWidget->setCursor(Qt::ArrowCursor);
        } else if (!m_dragStopOver.isNull()) {
            // Repaint only that region of the map that is affected by the change
            m_dragStopOverRightIndex = viaInsertPosition(e->modifiers());
            QRect dirty = m_routeRegion.boundingRect();
            dirty |= QRect(m_dropStopOver, m_pixmapSize);
            dirty |= QRect(e->pos(), m_pixmapSize);
            if (e->buttons() & Qt::LeftButton) {
                m_dropStopOver = e->pos();
            } else {
                m_dragStopOver = QPoint();
                m_dropStopOver = QPoint();
            }
            Q_EMIT q->repaintNeeded(dirty);
            m_marbleWidget->setCursor(Qt::ArrowCursor);
        } else if (isInfoPoint(e->pos())) {
            clearStopOver();
            m_marbleWidget->setCursor(Qt::ArrowCursor);
        } else if (m_routeRegion.contains(e->pos())) {
            m_dropStopOver = e->pos();
            m_marbleWidget->setCursor(Qt::ArrowCursor);
        } else if (!m_dropStopOver.isNull()) {
            clearStopOver();
        } else if (isAlternativeRoutePoint(e->pos())) {
            m_marbleWidget->setCursor(Qt::ArrowCursor);
        } else {
            return false;
        }

        // Update pixmap in the map (old and new position needs repaint)
        paintStopOver(QRect(e->pos(), m_pixmapSize));
        return true;
    }

    return false;
}

bool RoutingLayerPrivate::isInfoPoint(const QPoint &point)
{
    for (const RequestRegion &region : std::as_const(m_regions)) {
        if (region.region.contains(point)) {
            return true;
        }
    }

    for (const ModelRegion &region : std::as_const(m_instructionRegions)) {
        if (region.region.contains(point)) {
            return true;
        }
    }

    return false;
}

bool RoutingLayerPrivate::isAlternativeRoutePoint(const QPoint &point)
{
    for (const RequestRegion &region : std::as_const(m_alternativeRouteRegions)) {
        if (region.region.contains(point)) {
            return true;
        }
    }

    return false;
}

void RoutingLayerPrivate::paintStopOver(QRect dirty)
{
    Q_EMIT q->repaintNeeded(m_dirtyRect);
    int dx = 1 + m_pixmapSize.width() / 2;
    int dy = 1 + m_pixmapSize.height() / 2;
    dirty.adjust(-dx, -dy, -dx, -dy);
    Q_EMIT q->repaintNeeded(dirty);
    m_dirtyRect = dirty;
}

void RoutingLayerPrivate::clearStopOver()
{
    m_dropStopOver = QPoint();
    m_dragStopOver = QPoint();
    Q_EMIT q->repaintNeeded(m_dirtyRect);
}

RoutingLayer::RoutingLayer(MarbleWidget *widget, QWidget *parent)
    : QObject(parent)
    , d(new RoutingLayerPrivate(this, widget))
{
    connect(widget->model()->routingManager(), SIGNAL(stateChanged(RoutingManager::State)), this, SLOT(updateRouteState()));
    connect(widget, SIGNAL(visibleLatLonAltBoxChanged(GeoDataLatLonAltBox)), this, SLOT(setViewportChanged()));
    connect(widget->model()->routingManager()->alternativeRoutesModel(),
            SIGNAL(currentRouteChanged(const GeoDataDocument *)),
            this,
            SLOT(setViewportChanged()));
    connect(widget->model()->routingManager()->alternativeRoutesModel(), SIGNAL(currentRouteChanged(const GeoDataDocument *)), this, SIGNAL(repaintNeeded()));
    connect(widget->model()->routingManager()->alternativeRoutesModel(), SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(showAlternativeRoutes()));
}

RoutingLayer::~RoutingLayer()
{
    delete d;
}

QStringList RoutingLayer::renderPosition() const
{
    return QStringList(QStringLiteral("HOVERS_ABOVE_SURFACE"));
}

qreal RoutingLayer::zValue() const
{
    return 1.0;
}

bool RoutingLayer::render(GeoPainter *painter, ViewportParams *viewport, const QString &renderPos, GeoSceneLayer *layer)
{
    Q_UNUSED(viewport)
    Q_UNUSED(renderPos)
    Q_UNUSED(layer)

    painter->save();

    if (d->m_placemarkModel) {
        d->renderPlacemarks(painter);
    }

    if (d->m_alternativeRoutesModel) {
        d->renderAlternativeRoutes(painter);
    }

    d->renderRoute(painter);

    if (d->m_routeRequest) {
        d->renderRequest(painter);
    }

    d->renderAnnotations(painter);

    painter->restore();
    if (d->m_viewportChanged && d->m_viewContext == Still) {
        d->m_viewportChanged = false;
    }
    return true;
}

RenderState RoutingLayer::renderState() const
{
    return RenderState(QStringLiteral("Routing"),
                       d->m_marbleWidget->model()->routingManager()->state() == RoutingManager::Downloading ? WaitingForUpdate : Complete);
}

bool RoutingLayer::eventFilter(QObject *obj, QEvent *event)
{
    Q_UNUSED(obj)

    if (!d->m_isInteractive) {
        return false;
    }

    if (event->type() == QEvent::MouseButtonPress) {
        auto e = static_cast<QMouseEvent *>(event);
        return d->handleMouseButtonPress(e);
    }

    if (event->type() == QEvent::MouseButtonRelease) {
        auto e = static_cast<QMouseEvent *>(event);
        return d->handleMouseButtonRelease(e);
    }

    if (event->type() == QEvent::MouseMove) {
        auto e = static_cast<QMouseEvent *>(event);
        return d->handleMouseMove(e);
    }

    return false;
}

void RoutingLayer::setPlacemarkModel(MarblePlacemarkModel *model)
{
    d->m_placemarkModel = model;
    setViewportChanged();
}

void RoutingLayer::synchronizeWith(QItemSelectionModel *selection)
{
    d->m_selectionModel = selection;
}

void RoutingLayer::removeViaPoint()
{
    if (d->m_activeMenuIndex >= 0) {
        d->m_routeRequest->remove(d->m_activeMenuIndex);
        d->m_activeMenuIndex = -1;
        Q_EMIT repaintNeeded();
        d->m_marbleWidget->model()->routingManager()->retrieveRoute();
    }
}

void RoutingLayer::showAlternativeRoutes()
{
    setViewportChanged();
    Q_EMIT repaintNeeded();
}

void RoutingLayer::exportRoute()
{
    QString fileName = QFileDialog::getSaveFileName(d->m_marbleWidget,
                                                    tr("Export Route"), // krazy:exclude=qclasses
                                                    QDir::homePath(),
                                                    tr("GPX and KML files (*.gpx *.kml)"));

    if (!fileName.isEmpty()) {
        if (fileName.endsWith(QLatin1StringView(".gpx"), Qt::CaseInsensitive)) {
            QFile gpx(fileName);
            if (gpx.open(QFile::WriteOnly)) {
                d->m_routingModel->exportGpx(&gpx);
                gpx.close();
            }
        } else {
            d->m_marbleWidget->model()->routingManager()->saveRoute(fileName);
        }
    }
}

void RoutingLayer::updateRouteState()
{
    setViewportChanged();
    Q_EMIT repaintNeeded();
}

void RoutingLayer::setViewportChanged()
{
    d->m_viewportChanged = true;
    d->m_routeRegion = QRegion();
    d->m_instructionRegions.clear();
    d->m_alternativeRouteRegions.clear();
}

void RoutingLayer::setViewContext(ViewContext viewContext)
{
    d->m_viewContext = viewContext;
}

void RoutingLayer::setInteractive(bool interactive)
{
    d->m_isInteractive = interactive;
}

bool RoutingLayer::isInteractive() const
{
    return d->m_isInteractive;
}

QString RoutingLayer::runtimeTrace() const
{
    return QStringLiteral("Routing Layer");
}

} // namespace Marble

#include "moc_RoutingLayer.cpp"
