// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2004-2007 Torsten Rahn <tackat@kde.org>
// SPDX-FileCopyrightText: 2007 Inge Wallin <ingwa@kde.org>
// SPDX-FileCopyrightText: 2007 Thomas Zander <zander@kde.org>
// SPDX-FileCopyrightText: 2010 Bastian Holst <bastianholst@gmx.de>
//

// Self
#include "CurrentLocationWidget.h"

// Marble
#include "GeoDataCoordinates.h"
#include "MarbleDebug.h"
#include "MarbleLocale.h"
#include "MarbleModel.h"
#include "MarbleWidget.h"
#include "MarbleWidgetPopupMenu.h"
#include "PluginManager.h"
#include "PositionProviderPlugin.h"
#include "PositionTracking.h"
#include "routing/RoutingManager.h"

using namespace Marble;
/* TRANSLATOR Marble::CurrentLocationWidget */

// Ui
#include "ui_CurrentLocationWidget.h"

#include <QDateTime>
#include <QFileDialog>
#include <QMessageBox>

namespace Marble
{

class CurrentLocationWidgetPrivate
{
public:
    CurrentLocationWidgetPrivate();

    Ui::CurrentLocationWidget m_currentLocationUi;
    MarbleWidget *m_widget;
    AutoNavigation *m_adjustNavigation;

    QList<const PositionProviderPlugin *> m_positionProviderPlugins;
    GeoDataCoordinates m_currentPosition;

    QString m_lastOpenPath;
    QString m_lastSavePath;

    void receiveGpsCoordinates(const GeoDataCoordinates &position, qreal speed);
    void adjustPositionTrackingStatus(PositionProviderStatus status);
    void centerOnCurrentLocation();
    void updateRecenterComboBox(AutoNavigation::CenterMode centerMode);
    void updateAutoZoomCheckBox(bool autoZoom);
    void updateActivePositionProvider(PositionProviderPlugin *);
    void updateGuidanceMode();
    void saveTrack();
    void openTrack();
    void clearTrack();
};

CurrentLocationWidgetPrivate::CurrentLocationWidgetPrivate()
    : m_widget(nullptr)
    , m_adjustNavigation(nullptr)
    , m_positionProviderPlugins()
    , m_currentPosition()
    , m_lastOpenPath()
    , m_lastSavePath()
{
}

CurrentLocationWidget::CurrentLocationWidget(QWidget *parent, Qt::WindowFlags f)
    : QWidget(parent, f)
    , d(new CurrentLocationWidgetPrivate())
{
    d->m_currentLocationUi.setupUi(this);
    layout()->setContentsMargins({});

    connect(d->m_currentLocationUi.recenterComboBox, &QComboBox::currentIndexChanged, this, &CurrentLocationWidget::setRecenterMode);

    connect(d->m_currentLocationUi.autoZoomCheckBox, &QAbstractButton::clicked, this, &CurrentLocationWidget::setAutoZoom);

    bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
    d->m_currentLocationUi.positionTrackingComboBox->setVisible(!smallScreen);
    d->m_currentLocationUi.locationLabel->setVisible(!smallScreen);
}

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

void CurrentLocationWidget::setMarbleWidget(MarbleWidget *widget)
{
    d->m_widget = widget;

    delete d->m_adjustNavigation;
    d->m_adjustNavigation = new AutoNavigation(widget->model(), widget->viewport(), this);

    const PluginManager *pluginManager = d->m_widget->model()->pluginManager();
    d->m_positionProviderPlugins = pluginManager->positionProviderPlugins();
    for (const PositionProviderPlugin *plugin : std::as_const(d->m_positionProviderPlugins)) {
        d->m_currentLocationUi.positionTrackingComboBox->addItem(plugin->guiString());
    }
    if (d->m_positionProviderPlugins.isEmpty()) {
        d->m_currentLocationUi.positionTrackingComboBox->setEnabled(false);
        QString html = tr("<p>No Position Tracking Plugin installed.</p>");
        d->m_currentLocationUi.locationLabel->setText(html);
        d->m_currentLocationUi.locationLabel->setEnabled(true);
        bool const hasTrack = !d->m_widget->model()->positionTracking()->isTrackEmpty();
        d->m_currentLocationUi.showTrackCheckBox->setEnabled(hasTrack);
        d->m_currentLocationUi.saveTrackButton->setEnabled(hasTrack);
        d->m_currentLocationUi.clearTrackButton->setEnabled(hasTrack);
    }

    // disconnect CurrentLocation Signals
    disconnect(d->m_widget->model()->positionTracking(),
               SIGNAL(gpsLocation(GeoDataCoordinates, qreal)),
               this,
               SLOT(receiveGpsCoordinates(GeoDataCoordinates, qreal)));
    disconnect(d->m_widget->model()->positionTracking(),
               SIGNAL(positionProviderPluginChanged(PositionProviderPlugin *)),
               this,
               SLOT(updateActivePositionProvider(PositionProviderPlugin *)));
    disconnect(d->m_currentLocationUi.positionTrackingComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(changePositionProvider(int)));
    disconnect(d->m_currentLocationUi.locationLabel, SIGNAL(linkActivated(QString)), this, SLOT(centerOnCurrentLocation()));
    disconnect(d->m_widget->model()->positionTracking(),
               SIGNAL(statusChanged(PositionProviderStatus)),
               this,
               SLOT(adjustPositionTrackingStatus(PositionProviderStatus)));

    disconnect(d->m_widget->model(), SIGNAL(trackedPlacemarkChanged(const GeoDataPlacemark *)), this, SLOT(trackPlacemark()));

    // connect CurrentLocation signals
    connect(d->m_widget->model()->positionTracking(),
            SIGNAL(gpsLocation(GeoDataCoordinates, qreal)),
            this,
            SLOT(receiveGpsCoordinates(GeoDataCoordinates, qreal)));
    connect(d->m_widget->model()->positionTracking(),
            SIGNAL(positionProviderPluginChanged(PositionProviderPlugin *)),
            this,
            SLOT(updateActivePositionProvider(PositionProviderPlugin *)));
    d->updateActivePositionProvider(d->m_widget->model()->positionTracking()->positionProviderPlugin());
    connect(d->m_currentLocationUi.positionTrackingComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(changePositionProvider(int)));
    connect(d->m_currentLocationUi.locationLabel, SIGNAL(linkActivated(QString)), this, SLOT(centerOnCurrentLocation()));
    connect(d->m_widget->model()->positionTracking(),
            SIGNAL(statusChanged(PositionProviderStatus)),
            this,
            SLOT(adjustPositionTrackingStatus(PositionProviderStatus)));

    connect(d->m_adjustNavigation, SIGNAL(recenterModeChanged(AutoNavigation::CenterMode)), this, SLOT(updateRecenterComboBox(AutoNavigation::CenterMode)));
    connect(d->m_adjustNavigation, SIGNAL(autoZoomToggled(bool)), this, SLOT(updateAutoZoomCheckBox(bool)));
    connect(d->m_adjustNavigation, SIGNAL(zoomIn(FlyToMode)), d->m_widget, SLOT(zoomIn(FlyToMode)));
    connect(d->m_adjustNavigation, SIGNAL(zoomOut(FlyToMode)), d->m_widget, SLOT(zoomOut(FlyToMode)));
    connect(d->m_adjustNavigation, SIGNAL(centerOn(GeoDataCoordinates, bool)), d->m_widget, SLOT(centerOn(GeoDataCoordinates, bool)));

    connect(d->m_widget, SIGNAL(visibleLatLonAltBoxChanged(GeoDataLatLonAltBox)), d->m_adjustNavigation, SLOT(inhibitAutoAdjustments()));
    connect(d->m_widget->model()->routingManager(), SIGNAL(guidanceModeEnabledChanged(bool)), this, SLOT(updateGuidanceMode()));

    connect(d->m_currentLocationUi.showTrackCheckBox, SIGNAL(clicked(bool)), d->m_widget->model()->positionTracking(), SLOT(setTrackVisible(bool)));
    connect(d->m_currentLocationUi.showTrackCheckBox, SIGNAL(clicked(bool)), d->m_widget, SLOT(update()));
    if (d->m_widget->model()->positionTracking()->trackVisible()) {
        d->m_currentLocationUi.showTrackCheckBox->setCheckState(Qt::Checked);
    }
    connect(d->m_currentLocationUi.saveTrackButton, SIGNAL(clicked(bool)), this, SLOT(saveTrack()));
    connect(d->m_currentLocationUi.openTrackButton, SIGNAL(clicked(bool)), this, SLOT(openTrack()));
    connect(d->m_currentLocationUi.clearTrackButton, SIGNAL(clicked(bool)), this, SLOT(clearTrack()));
    connect(d->m_widget->model(), SIGNAL(trackedPlacemarkChanged(const GeoDataPlacemark *)), this, SLOT(trackPlacemark()));
}

void CurrentLocationWidgetPrivate::adjustPositionTrackingStatus(PositionProviderStatus status)
{
    if (status == PositionProviderStatusAvailable) {
        return;
    }

    QString html = QStringLiteral("<html><body><p>");

    switch (status) {
    case PositionProviderStatusUnavailable:
        html += QObject::tr("No position available.");
        break;
    case PositionProviderStatusAcquiring:
        html += QObject::tr("Waiting for current location information...");
        break;
    case PositionProviderStatusAvailable:
        Q_ASSERT(false);
        break;
    case PositionProviderStatusError:
        html += QObject::tr("Error when determining current location: ");
        html += m_widget->model()->positionTracking()->error();
        break;
    }

    html += QLatin1StringView("</p></body></html>");
    m_currentLocationUi.locationLabel->setEnabled(true);
    m_currentLocationUi.locationLabel->setText(html);
}

void CurrentLocationWidgetPrivate::updateActivePositionProvider(PositionProviderPlugin *plugin)
{
    m_currentLocationUi.positionTrackingComboBox->blockSignals(true);
    if (!plugin) {
        m_currentLocationUi.positionTrackingComboBox->setCurrentIndex(0);
    } else {
        for (int i = 0; i < m_currentLocationUi.positionTrackingComboBox->count(); ++i) {
            if (m_currentLocationUi.positionTrackingComboBox->itemText(i) == plugin->guiString()) {
                m_currentLocationUi.positionTrackingComboBox->setCurrentIndex(i);
                break;
            }
        }
    }
    m_currentLocationUi.positionTrackingComboBox->blockSignals(false);
    m_currentLocationUi.recenterLabel->setEnabled(plugin);
    m_currentLocationUi.recenterComboBox->setEnabled(plugin);
    m_currentLocationUi.autoZoomCheckBox->setEnabled(plugin);
}

void CurrentLocationWidgetPrivate::updateGuidanceMode()
{
    const bool enabled = m_widget->model()->routingManager()->guidanceModeEnabled();

    m_adjustNavigation->setAutoZoom(enabled);
    m_adjustNavigation->setRecenter(enabled ? AutoNavigation::RecenterOnBorder : AutoNavigation::DontRecenter);
}

void CurrentLocationWidgetPrivate::receiveGpsCoordinates(const GeoDataCoordinates &position, qreal speed)
{
    m_currentPosition = position;
    QString unitString;
    QString altitudeUnitString;
    QString distanceUnitString;
    qreal unitSpeed = 0.0;
    qreal altitude = 0.0;
    qreal length = m_widget->model()->positionTracking()->length(m_widget->model()->planetRadius());

    QString html = QLatin1StringView(
        "<html><body>"
        "<table cellspacing=\"2\" cellpadding=\"2\">"
        "<tr><td>Longitude</td><td><a href=\"https://edu.kde.org/marble\">%1</a></td></tr>"
        "<tr><td>Latitude</td><td><a href=\"https://edu.kde.org/marble\">%2</a></td></tr>"
        "<tr><td>Altitude</td><td>%3</td></tr>"
        "<tr><td>Speed</td><td>%4</td></tr>"
        "<tr><td>Distance</td><td>%5</td></tr>"
        "</table>"
        "</body></html>");

    switch (MarbleGlobal::getInstance()->locale()->measurementSystem()) {
    case MarbleLocale::MetricSystem:
        // kilometers per hour
        unitString = QObject::tr("km/h");
        unitSpeed = speed * HOUR2SEC * METER2KM;
        altitudeUnitString = QObject::tr("m");
        distanceUnitString = QObject::tr("m");
        if (length > 1000.0) {
            length /= 1000.0;
            distanceUnitString = QObject::tr("km");
        }
        altitude = position.altitude();
        break;
    case MarbleLocale::ImperialSystem:
        // miles per hour
        unitString = QObject::tr("m/h");
        unitSpeed = speed * HOUR2SEC * METER2KM * KM2MI;
        altitudeUnitString = QObject::tr("ft");
        distanceUnitString = QObject::tr("ft");
        altitude = position.altitude() * M2FT;
        length *= M2FT;
        break;

    case MarbleLocale::NauticalSystem:
        // nautical miles
        unitString = QObject::tr("kt");
        unitSpeed = speed * HOUR2SEC * METER2KM * KM2NM;
        altitudeUnitString = QObject::tr("m");
        distanceUnitString = QObject::tr("nm");
        altitude = position.altitude();
        length *= METER2KM * KM2NM;
        break;
    }
    // TODO read this value from the incoming signal
    const QString speedString = QLocale::system().toString(unitSpeed, 'f', 1);
    const QString altitudeString = QStringLiteral("%1 %2").arg(altitude, 0, 'f', 1, QLatin1Char(' ')).arg(altitudeUnitString);
    const QString distanceString = QStringLiteral("%1 %2").arg(length, 0, 'f', 1, QLatin1Char(' ')).arg(distanceUnitString);

    html = html.arg(position.lonToString(), position.latToString());
    html = html.arg(altitudeString, speedString + QLatin1Char(' ') + unitString);
    html = html.arg(distanceString);
    m_currentLocationUi.locationLabel->setText(html);
    m_currentLocationUi.showTrackCheckBox->setEnabled(true);
    m_currentLocationUi.saveTrackButton->setEnabled(true);
    m_currentLocationUi.clearTrackButton->setEnabled(true);
}

void CurrentLocationWidget::changePositionProvider(int index)
{
    auto const combo = dynamic_cast<QComboBox *>(sender());

    if (!combo)
        return;

    QString provider = combo->itemText(index);

    changePositionProvider(provider);
}

void CurrentLocationWidget::changePositionProvider(const QString &provider)
{
    for (const PositionProviderPlugin *plugin : std::as_const(d->m_positionProviderPlugins)) {
        if (plugin->guiString() == provider) {
            d->m_currentLocationUi.locationLabel->setEnabled(true);
            PositionProviderPlugin *instance = plugin->newInstance();
            PositionTracking *tracking = d->m_widget->model()->positionTracking();
            tracking->setPositionProviderPlugin(instance);
            d->m_widget->update();
            return;
        }
    }

    // requested provider not found -> disable position tracking
    d->m_currentLocationUi.locationLabel->setEnabled(false);
    d->m_widget->model()->positionTracking()->setPositionProviderPlugin(nullptr);
    d->m_widget->update();
}

void CurrentLocationWidget::trackPlacemark()
{
    changePositionProvider(QObject::tr("Placemark"));
    d->m_adjustNavigation->setRecenter(AutoNavigation::AlwaysRecenter);
}

void CurrentLocationWidget::setRecenterMode(int mode)
{
    if (mode >= 0 && mode <= AutoNavigation::RecenterOnBorder) {
        auto centerMode = (AutoNavigation::CenterMode)mode;
        d->m_adjustNavigation->setRecenter(centerMode);
    }
}

void CurrentLocationWidget::setAutoZoom(bool autoZoom)
{
    d->m_adjustNavigation->setAutoZoom(autoZoom);
}

void CurrentLocationWidgetPrivate::updateAutoZoomCheckBox(bool autoZoom)
{
    m_currentLocationUi.autoZoomCheckBox->setChecked(autoZoom);
}

void CurrentLocationWidgetPrivate::updateRecenterComboBox(AutoNavigation::CenterMode centerMode)
{
    m_currentLocationUi.recenterComboBox->setCurrentIndex(centerMode);
}

void CurrentLocationWidgetPrivate::centerOnCurrentLocation()
{
    m_widget->centerOn(m_currentPosition, true);
}

void CurrentLocationWidgetPrivate::saveTrack()
{
    QString suggested = m_lastSavePath;
    QString fileName = QFileDialog::getSaveFileName(
        m_widget,
        QObject::tr("Save Track"), // krazy:exclude=qclasses
        suggested.append(QLatin1Char('/') + QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd_hhmmss")) + QLatin1StringView(".kml")),
        QObject::tr("KML File (*.kml)"));
    if (fileName.isEmpty()) {
        return;
    }
    if (!fileName.endsWith(QLatin1StringView(".kml"), Qt::CaseInsensitive)) {
        fileName += QLatin1StringView(".kml");
    }
    QFileInfo file(fileName);
    m_lastSavePath = file.absolutePath();
    m_widget->model()->positionTracking()->saveTrack(fileName);
}

void CurrentLocationWidgetPrivate::openTrack()
{
    QString suggested = m_lastOpenPath;
    QString fileName = QFileDialog::getOpenFileName(m_widget,
                                                    QObject::tr("Open Track"), // krazy:exclude=qclasses
                                                    suggested,
                                                    QObject::tr("KML File (*.kml)"));
    if (!fileName.isEmpty()) {
        QFileInfo file(fileName);
        m_lastOpenPath = file.absolutePath();
        m_widget->model()->addGeoDataFile(fileName);
    }
}

void CurrentLocationWidgetPrivate::clearTrack()
{
    const int result = QMessageBox::question(m_widget,
                                             QObject::tr("Clear current track"),
                                             QObject::tr("Are you sure you want to clear the current track?"),
                                             QMessageBox::Yes,
                                             QMessageBox::No);

    if (result == QMessageBox::Yes) {
        m_widget->model()->positionTracking()->clearTrack();
        m_widget->update();
        m_currentLocationUi.saveTrackButton->setEnabled(false);
        m_currentLocationUi.clearTrackButton->setEnabled(false);
    }
}

AutoNavigation::CenterMode CurrentLocationWidget::recenterMode() const
{
    return d->m_adjustNavigation->recenterMode();
}

bool CurrentLocationWidget::autoZoom() const
{
    return d->m_adjustNavigation->autoZoom();
}

bool CurrentLocationWidget::trackVisible() const
{
    return d->m_widget->model()->positionTracking()->trackVisible();
}

QString CurrentLocationWidget::lastOpenPath() const
{
    return d->m_lastOpenPath;
}

QString CurrentLocationWidget::lastSavePath() const
{
    return d->m_lastSavePath;
}

void CurrentLocationWidget::setTrackVisible(bool visible)
{
    d->m_currentLocationUi.showTrackCheckBox->setChecked(visible);
    d->m_widget->model()->positionTracking()->setTrackVisible(visible);
}

void CurrentLocationWidget::setLastOpenPath(const QString &path)
{
    d->m_lastOpenPath = path;
}

void CurrentLocationWidget::setLastSavePath(const QString &path)
{
    d->m_lastSavePath = path;
}

}

#include "moc_CurrentLocationWidget.cpp"
