// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2008 Torsten Rahn <tackat@kde.org>
//

#include "OverviewMap.h"

#include <QColorDialog>
#include <QCursor>
#include <QFileDialog>
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QPainter>
#include <QRect>
#include <QStringList>

#include "MarbleDirs.h"
#include "MarbleModel.h"
#include "ui_OverviewMapConfigWidget.h"

#include "MarbleWidget.h"
#include "PlanetFactory.h"
#include "ViewportParams.h"

namespace Marble
{

OverviewMap::OverviewMap()
    : AbstractFloatItem(nullptr)
    , ui_configWidget(nullptr)
    , m_configDialog(nullptr)
    , m_mapChanged(false)
{
}

OverviewMap::OverviewMap(const MarbleModel *marbleModel)
    : AbstractFloatItem(marbleModel, QPointF(10.5, 10.5), QSizeF(166.0, 86.0))
    , m_target()
    , m_planetID(PlanetFactory::planetList())
    , m_defaultSize(AbstractFloatItem::size())
    , ui_configWidget(nullptr)
    , m_configDialog(nullptr)
    , m_mapChanged(false)
{
    // cache is no needed because:
    // (1) the SVG overview map is already rendered and stored in m_worldmap pixmap
    // (2) bounding box and location dot keep changing during navigation
    setCacheMode(NoCache);
    connect(this, &RenderPlugin::settingsChanged, this, &OverviewMap::updateSettings);

    restoreDefaultSettings();
}

OverviewMap::~OverviewMap()
{
    QHash<QString, QSvgWidget *>::const_iterator pos = m_svgWidgets.constBegin();
    QHash<QString, QSvgWidget *>::const_iterator const end = m_svgWidgets.constEnd();
    for (; pos != end; ++pos) {
        delete pos.value();
    }
    delete ui_configWidget;
}

QStringList OverviewMap::backendTypes() const
{
    return QStringList(QStringLiteral("overviewmap"));
}

QString OverviewMap::name() const
{
    return tr("Overview Map");
}

QString OverviewMap::guiString() const
{
    return tr("&Overview Map");
}

QString OverviewMap::nameId() const
{
    return QStringLiteral("overviewmap");
}

QString OverviewMap::version() const
{
    return QStringLiteral("1.0");
}

QString OverviewMap::description() const
{
    return tr("This is a float item that provides an overview map.");
}

QString OverviewMap::copyrightYears() const
{
    return QStringLiteral("2008");
}

QList<PluginAuthor> OverviewMap::pluginAuthors() const
{
    return QList<PluginAuthor>() << PluginAuthor(QStringLiteral("Torsten Rahn"), QStringLiteral("tackat@kde.org"));
}

QIcon OverviewMap::icon() const
{
    return QIcon(QStringLiteral(":/icons/worldmap.png"));
}

QDialog *OverviewMap::configDialog()
{
    if (!m_configDialog) {
        // Initializing configuration dialog
        m_configDialog = new QDialog();
        ui_configWidget = new Ui::OverviewMapConfigWidget;
        ui_configWidget->setupUi(m_configDialog);
        for (int i = 0; i < m_planetID.size(); ++i) {
            ui_configWidget->m_planetComboBox->addItem(PlanetFactory::localizedName(m_planetID.value(i)));
        }
        ui_configWidget->m_planetComboBox->setCurrentIndex(2);
        readSettings();
        loadMapSuggestions();
        connect(ui_configWidget->m_buttonBox, &QDialogButtonBox::accepted, this, &OverviewMap::writeSettings);
        connect(ui_configWidget->m_buttonBox, &QDialogButtonBox::rejected, this, &OverviewMap::readSettings);
        connect(ui_configWidget->m_buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, this, &RenderPlugin::restoreDefaultSettings);
        QPushButton *applyButton = ui_configWidget->m_buttonBox->button(QDialogButtonBox::Apply);
        connect(applyButton, &QAbstractButton::clicked, this, &OverviewMap::writeSettings);
        connect(ui_configWidget->m_fileChooserButton, &QAbstractButton::clicked, this, &OverviewMap::chooseCustomMap);
        connect(ui_configWidget->m_widthBox, &QSpinBox::valueChanged, this, &OverviewMap::synchronizeSpinboxes);
        connect(ui_configWidget->m_heightBox, &QSpinBox::valueChanged, this, &OverviewMap::synchronizeSpinboxes);
        connect(ui_configWidget->m_planetComboBox, &QComboBox::currentIndexChanged, this, &OverviewMap::showCurrentPlanetPreview);
        connect(ui_configWidget->m_colorChooserButton, &QAbstractButton::clicked, this, &OverviewMap::choosePositionIndicatorColor);
        connect(ui_configWidget->m_tableWidget, &QTableWidget::cellClicked, this, &OverviewMap::useMapSuggestion);
    }
    return m_configDialog;
}

void OverviewMap::initialize()
{
}

bool OverviewMap::isInitialized() const
{
    return true;
}

void OverviewMap::setProjection(const ViewportParams *viewport)
{
    GeoDataLatLonAltBox latLonAltBox = viewport->latLonAltBox(QRect(QPoint(0, 0), viewport->size()));
    const qreal centerLon = viewport->centerLongitude();
    const qreal centerLat = viewport->centerLatitude();
    QString target = marbleModel()->planetId();

    if (target != m_target) {
        changeBackground(target);
        m_target = target;
        update();
    }

    if (!(m_latLonAltBox == latLonAltBox && m_centerLon == centerLon && m_centerLat == centerLat)) {
        m_latLonAltBox = latLonAltBox;
        m_centerLon = centerLon;
        m_centerLat = centerLat;
        update();
    }

    AbstractFloatItem::setProjection(viewport);
}

void OverviewMap::paintContent(QPainter *painter)
{
    painter->save();

    QRectF mapRect(contentRect());

    if (m_svgobj.isValid()) {
        // Rerender worldmap pixmap if the size or map has changed
        if (m_worldmap.size() != mapRect.size().toSize() || m_mapChanged) {
            m_mapChanged = false;
            m_worldmap = QPixmap(mapRect.size().toSize());
            m_worldmap.fill(Qt::transparent);
            QPainter mapPainter;
            mapPainter.begin(&m_worldmap);
            mapPainter.setViewport(m_worldmap.rect());
            m_svgobj.render(&mapPainter);
            mapPainter.end();
        }

        painter->drawPixmap(QPoint(0, 0), m_worldmap);
    } else {
        painter->setPen(QPen(Qt::DashLine));
        painter->drawRect(QRectF(QPoint(0, 0), mapRect.size().toSize()));

        for (int y = 1; y < 4; ++y) {
            if (y == 2) {
                painter->setPen(QPen(Qt::DashLine));
            } else {
                painter->setPen(QPen(Qt::DotLine));
            }

            painter->drawLine(0.0, 0.25 * y * mapRect.height(), mapRect.width(), 0.25 * y * mapRect.height());
        }
        for (int x = 1; x < 8; ++x) {
            if (x == 4) {
                painter->setPen(QPen(Qt::DashLine));
            } else {
                painter->setPen(QPen(Qt::DotLine));
            }

            painter->drawLine(0.125 * x * mapRect.width(), 0, 0.125 * x * mapRect.width(), mapRect.height());
        }
    }

    // Now draw the latitude longitude bounding box
    qreal xWest = mapRect.width() / 2.0 + mapRect.width() / (2.0 * M_PI) * m_latLonAltBox.west();
    qreal xEast = mapRect.width() / 2.0 + mapRect.width() / (2.0 * M_PI) * m_latLonAltBox.east();
    qreal xNorth = mapRect.height() / 2.0 - mapRect.height() / M_PI * m_latLonAltBox.north();
    qreal xSouth = mapRect.height() / 2.0 - mapRect.height() / M_PI * m_latLonAltBox.south();

    qreal lon = m_centerLon;
    qreal lat = m_centerLat;
    GeoDataCoordinates::normalizeLonLat(lon, lat);
    qreal x = mapRect.width() / 2.0 + mapRect.width() / (2.0 * M_PI) * lon;
    qreal y = mapRect.height() / 2.0 - mapRect.height() / M_PI * lat;

    painter->setPen(QPen(Qt::white));
    painter->setBrush(QBrush(Qt::transparent));
    painter->setRenderHint(QPainter::Antialiasing, false);

    qreal boxWidth = xEast - xWest;
    qreal boxHeight = xSouth - xNorth;

    qreal minBoxSize = 2.0;
    if (boxHeight < minBoxSize)
        boxHeight = minBoxSize;

    if (m_latLonAltBox.west() <= m_latLonAltBox.east()) {
        // Make sure the latLonBox is still visible
        if (boxWidth < minBoxSize)
            boxWidth = minBoxSize;

        painter->drawRect(QRectF(xWest, xNorth, boxWidth, boxHeight));
    } else {
        // If the dateline is shown in the viewport  and if the poles are not
        // then there are two boxes that represent the latLonBox of the view.

        boxWidth = xEast;

        // Make sure the latLonBox is still visible
        if (boxWidth < minBoxSize)
            boxWidth = minBoxSize;

        painter->drawRect(QRectF(0, xNorth, boxWidth, boxHeight));

        boxWidth = mapRect.width() - xWest;

        // Make sure the latLonBox is still visible
        if (boxWidth < minBoxSize)
            boxWidth = minBoxSize;

        painter->drawRect(QRectF(xWest, xNorth, boxWidth, boxHeight));
    }

    painter->setPen(QPen(m_posColor));
    painter->setBrush(QBrush(m_posColor));

    qreal circleRadius = 2.5;
    painter->setRenderHint(QPainter::Antialiasing, true);
    painter->drawEllipse(QRectF(x - circleRadius, y - circleRadius, 2 * circleRadius, 2 * circleRadius));

    painter->restore();
}

QHash<QString, QVariant> OverviewMap::settings() const
{
    QHash<QString, QVariant> result = AbstractFloatItem::settings();

    typedef QHash<QString, QVariant>::ConstIterator Iterator;
    Iterator end = m_settings.constEnd();
    for (Iterator iter = m_settings.constBegin(); iter != end; ++iter) {
        result.insert(iter.key(), iter.value());
    }

    return result;
}

void OverviewMap::setSettings(const QHash<QString, QVariant> &settings)
{
    AbstractFloatItem::setSettings(settings);

    m_settings.insert(QStringLiteral("width"), settings.value(QStringLiteral("width"), m_defaultSize.toSize().width()));
    m_settings.insert(QStringLiteral("height"), settings.value(QStringLiteral("height"), m_defaultSize.toSize().height()));

    for (const QString &planet : PlanetFactory::planetList()) {
        QString mapFile = MarbleDirs::path(QLatin1StringView("svg/") + planet + QLatin1StringView("map.svg"));

        if (planet == QLatin1StringView("moon")) {
            mapFile = MarbleDirs::path(QStringLiteral("svg/lunarmap.svg"));
        } else if (planet == QLatin1StringView("earth") || mapFile.isEmpty()) {
            mapFile = MarbleDirs::path(QStringLiteral("svg/worldmap.svg"));
        }

        const QString id = QLatin1StringView("path_") + planet;
        m_settings.insert(id, settings.value(id, mapFile));
    }

    m_settings.insert(QStringLiteral("posColor"), settings.value(QStringLiteral("posColor"), QColor(Qt::white).name()));

    m_target.clear(); // FIXME: forces execution of changeBackground() in changeViewport()

    readSettings();
    Q_EMIT settingsChanged(nameId());
}

void OverviewMap::readSettings()
{
    if (!m_configDialog) {
        return;
    }

    ui_configWidget->m_widthBox->setValue(m_settings.value(QStringLiteral("width")).toInt());
    ui_configWidget->m_heightBox->setValue(m_settings.value(QStringLiteral("height")).toInt());
    QPalette palette = ui_configWidget->m_colorChooserButton->palette();
    palette.setColor(QPalette::Button, QColor(m_settings.value(QStringLiteral("posColor")).toString()));
    ui_configWidget->m_colorChooserButton->setPalette(palette);
}

void OverviewMap::writeSettings()
{
    if (!m_configDialog) {
        return;
    }

    m_settings.insert(QStringLiteral("width"), contentRect().width());
    m_settings.insert(QStringLiteral("height"), contentRect().height());

    QStringList const planets = PlanetFactory::planetList();
    for (const QString &planet : planets) {
        m_settings.insert(QLatin1StringView("path_") + planet, m_svgPaths[planet]);
    }

    m_settings.insert(QStringLiteral("posColor"), m_posColor.name());

    Q_EMIT settingsChanged(nameId());
}

void OverviewMap::updateSettings()
{
    QStringList const planets = PlanetFactory::planetList();
    for (const QString &planet : planets) {
        m_svgPaths.insert(planet, m_settings.value(QLatin1StringView("path_") + planet, QString()).toString());
    }

    m_posColor = QColor(m_settings.value(QStringLiteral("posColor")).toString());
    loadPlanetMaps();

    if (!m_configDialog) {
        return;
    }

    setCurrentWidget(m_svgWidgets[m_planetID[2]]);
    showCurrentPlanetPreview();
    setContentSize(QSizeF(ui_configWidget->m_widthBox->value(), ui_configWidget->m_heightBox->value()));
}

bool OverviewMap::eventFilter(QObject *object, QEvent *e)
{
    if (!enabled() || !visible()) {
        return false;
    }

    auto widget = dynamic_cast<MarbleWidget *>(object);
    if (!widget) {
        return AbstractFloatItem::eventFilter(object, e);
    }

    if (e->type() == QEvent::MouseButtonDblClick || e->type() == QEvent::MouseMove) {
        auto event = static_cast<QMouseEvent *>(e);
        QRectF floatItemRect = QRectF(positivePosition(), size());

        bool cursorAboveFloatItem(false);
        if (floatItemRect.contains(event->pos())) {
            cursorAboveFloatItem = true;

            // Double click triggers recentering the map at the specified position
            if (e->type() == QEvent::MouseButtonDblClick) {
                QRectF mapRect(contentRect());
                QPointF pos = event->pos() - floatItemRect.topLeft() - QPointF(padding(), padding());

                qreal lon = (pos.x() - mapRect.width() / 2.0) / mapRect.width() * 360.0;
                qreal lat = (mapRect.height() / 2.0 - pos.y()) / mapRect.height() * 180.0;
                widget->centerOn(lon, lat, true);

                return true;
            }
        }

        if (cursorAboveFloatItem && e->type() == QEvent::MouseMove && !(event->buttons() & Qt::LeftButton)) {
            // Cross hair cursor when moving above the float item without pressing a button
            widget->setCursor(QCursor(Qt::CrossCursor));
            return true;
        }
    }

    return AbstractFloatItem::eventFilter(object, e);
}

void OverviewMap::changeBackground(const QString &target)
{
    m_svgobj.load(m_svgPaths[target]);
    m_mapChanged = true;
}

QSvgWidget *OverviewMap::currentWidget() const
{
    return m_svgWidgets[m_planetID[ui_configWidget->m_planetComboBox->currentIndex()]];
}

void OverviewMap::setCurrentWidget(QSvgWidget *widget)
{
    m_svgWidgets[m_planetID[ui_configWidget->m_planetComboBox->currentIndex()]] = widget;
    if (m_target == m_planetID[ui_configWidget->m_planetComboBox->currentIndex()]) {
        changeBackground(m_target);
    }
}

void OverviewMap::loadPlanetMaps()
{
    for (const QString &planet : std::as_const(m_planetID)) {
        if (m_svgWidgets.contains(planet)) {
            m_svgWidgets[planet]->load(m_svgPaths[planet]);
        } else {
            m_svgWidgets[planet] = new QSvgWidget(m_svgPaths[planet]);
        }
    }
}

void OverviewMap::loadMapSuggestions()
{
    QStringList paths = QDir(MarbleDirs::pluginPath(QString())).entryList(QStringList(QStringLiteral("*.svg")), QDir::Files | QDir::NoDotAndDotDot);
    for (int i = 0; i < paths.size(); ++i) {
        paths[i] = MarbleDirs::pluginPath(QString()) + QLatin1Char('/') + paths[i];
    }
    paths << MarbleDirs::path(QStringLiteral("svg/worldmap.svg")) << MarbleDirs::path(QStringLiteral("svg/lunarmap.svg"));
    ui_configWidget->m_tableWidget->setRowCount(paths.size());
    for (int i = 0; i < paths.size(); ++i) {
        ui_configWidget->m_tableWidget->setCellWidget(i, 0, new QSvgWidget(paths[i]));
        ui_configWidget->m_tableWidget->setItem(i, 1, new QTableWidgetItem(paths[i]));
    }
}

void OverviewMap::chooseCustomMap()
{
    QString path = QFileDialog::getOpenFileName(m_configDialog, tr("Choose Overview Map"), QString(), QStringLiteral("SVG (*.svg)"));
    if (!path.isNull()) {
        ui_configWidget->m_fileChooserButton->layout()->removeWidget(currentWidget());
        delete currentWidget();
        auto widget = new QSvgWidget(path);
        setCurrentWidget(widget);
        ui_configWidget->m_fileChooserButton->layout()->addWidget(widget);
        m_svgPaths[m_planetID[ui_configWidget->m_planetComboBox->currentIndex()]] = path;
    }
}

void OverviewMap::synchronizeSpinboxes()
{
    if (sender() == ui_configWidget->m_widthBox) {
        ui_configWidget->m_heightBox->setValue(ui_configWidget->m_widthBox->value() / 2);
    } else if (sender() == ui_configWidget->m_heightBox) {
        ui_configWidget->m_widthBox->setValue(ui_configWidget->m_heightBox->value() * 2);
    }
}

void OverviewMap::showCurrentPlanetPreview() const
{
    delete ui_configWidget->m_fileChooserButton->layout();
    ui_configWidget->m_fileChooserButton->setLayout(new QHBoxLayout());
    ui_configWidget->m_fileChooserButton->layout()->addWidget(currentWidget());
}

void OverviewMap::choosePositionIndicatorColor()
{
    QColor c = QColorDialog::getColor(m_posColor, nullptr, tr("Please choose the color for the position indicator"), QColorDialog::ShowAlphaChannel);
    if (c.isValid()) {
        m_posColor = c;
        QPalette palette = ui_configWidget->m_colorChooserButton->palette();
        palette.setColor(QPalette::Button, m_posColor);
        ui_configWidget->m_colorChooserButton->setPalette(palette);
    }
}

void OverviewMap::useMapSuggestion(int index)
{
    QString path = ui_configWidget->m_tableWidget->item(index, 1)->text();
    m_svgPaths[m_planetID[ui_configWidget->m_planetComboBox->currentIndex()]] = path;
    delete currentWidget();
    auto widget = new QSvgWidget(path);
    setCurrentWidget(widget);
    showCurrentPlanetPreview();
}

}

#include "moc_OverviewMap.cpp"
