// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2007 Andrew Manson <g.real.ate@gmail.com>
// SPDX-FileCopyrightText: 2009 Eckhart Wörner <ewoerner@kde.org>
// SPDX-FileCopyrightText: 2010 Thibaut Gridel <tgridel@free.fr>
// SPDX-FileCopyrightText: 2010 Daniel Marth <danielmarth@gmx.at>
//

#include "PositionMarker.h"

#include "MarbleDebug.h"
#include <QColorDialog>
#include <QFileDialog>
#include <QPushButton>
#include <QRect>
#include <QTransform>
#include <qmath.h>

#include <cmath>

#include "GeoDataAccuracy.h"
#include "GeoPainter.h"
#include "MarbleColors.h"
#include "MarbleDirs.h"
#include "MarbleModel.h"
#include "Planet.h"
#include "PositionTracking.h"
#include "ViewportParams.h"
#include "ui_PositionMarkerConfigWidget.h"

namespace Marble
{

const int PositionMarker::sm_defaultSizeStep = 2;
const float PositionMarker::sm_resizeSteps[] = {0.25, 0.5, 1.0, 2.0, 4.0};
const int PositionMarker::sm_numResizeSteps = sizeof(sm_resizeSteps) / sizeof(sm_resizeSteps[0]);

PositionMarker::PositionMarker(const MarbleModel *marbleModel)
    : RenderPlugin(marbleModel)
    , m_marbleModel(marbleModel)
    , m_isInitialized(false)
    , m_useCustomCursor(false)
    , m_defaultCursorPath(MarbleDirs::path(QStringLiteral("svg/track_turtle.svg")))
    , m_lastBoundingBox()
    , m_cursorPath(m_defaultCursorPath)
    , m_cursorSize(1.0)
    , m_accuracyColor(Oxygen::brickRed4)
    , m_trailColor(0, 0, 255)
    , m_heading(0.0)
    , m_showTrail(false)
{
    const bool smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
    m_accuracyColor.setAlpha(smallScreen ? 80 : 40);
}

PositionMarker::~PositionMarker()
{
    delete ui_configWidget;
    delete m_configDialog;
}

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

QString PositionMarker::renderPolicy() const
{
    return QStringLiteral("ALWAYS");
}

QStringList PositionMarker::backendTypes() const
{
    return QStringList(QStringLiteral("positionmarker"));
}

QString PositionMarker::name() const
{
    return tr("Position Marker");
}

QString PositionMarker::guiString() const
{
    return tr("&Position Marker");
}

QString PositionMarker::nameId() const
{
    return QStringLiteral("positionMarker");
}

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

QString PositionMarker::description() const
{
    return tr("draws a marker at the current position");
}

QString PositionMarker::copyrightYears() const
{
    return QStringLiteral("2009, 2010");
}

QList<PluginAuthor> PositionMarker::pluginAuthors() const
{
    return QList<PluginAuthor>() << PluginAuthor(QStringLiteral("Andrew Manson"), QStringLiteral("g.real.ate@gmail.com"))
                                 << PluginAuthor(QStringLiteral("Eckhart Woerner"), QStringLiteral("ewoerner@kde.org"))
                                 << PluginAuthor(QStringLiteral("Thibaut Gridel"), QStringLiteral("tgridel@free.fr"))
                                 << PluginAuthor(QStringLiteral("Daniel Marth"), QStringLiteral("danielmarth@gmx.at"));
}

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

QDialog *PositionMarker::configDialog()
{
    if (!m_configDialog) {
        // Initializing configuration dialog
        m_configDialog = new QDialog();
        ui_configWidget = new Ui::PositionMarkerConfigWidget;
        ui_configWidget->setupUi(m_configDialog);
        ui_configWidget->m_resizeSlider->setMaximum(sm_numResizeSteps - 1);
        readSettings();
        connect(ui_configWidget->m_buttonBox, &QDialogButtonBox::accepted, this, &PositionMarker::writeSettings);
        connect(ui_configWidget->m_buttonBox, &QDialogButtonBox::rejected, this, &PositionMarker::readSettings);
        connect(ui_configWidget->m_buttonBox->button(QDialogButtonBox::RestoreDefaults),
                &QAbstractButton::clicked,
                this,
                &RenderPlugin::restoreDefaultSettings);
        QPushButton *applyButton = ui_configWidget->m_buttonBox->button(QDialogButtonBox::Apply);
        connect(applyButton, &QAbstractButton::clicked, this, &PositionMarker::writeSettings);
        connect(ui_configWidget->m_fileChooserButton, &QAbstractButton::clicked, this, &PositionMarker::chooseCustomCursor);
        connect(ui_configWidget->m_resizeSlider, &QAbstractSlider::valueChanged, this, &PositionMarker::resizeCursor);
        connect(ui_configWidget->m_acColorChooserButton, &QAbstractButton::clicked, this, &PositionMarker::chooseColor);
        connect(ui_configWidget->m_trailColorChooserButton, &QAbstractButton::clicked, this, &PositionMarker::chooseColor);
    }
    return m_configDialog;
}

void PositionMarker::initialize()
{
    if (marbleModel()) {
        connect(marbleModel()->positionTracking(), &PositionTracking::gpsLocation, this, &PositionMarker::setPosition);
        connect(marbleModel()->positionTracking(), SIGNAL(statusChanged(PositionProviderStatus)), this, SIGNAL(repaintNeeded()));
        m_isInitialized = true;
    }
    loadDefaultCursor();
}

bool PositionMarker::isInitialized() const
{
    return m_isInitialized;
}

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

    bool const gpsActive = marbleModel()->positionTracking()->positionProviderPlugin() != nullptr;
    bool const positionAvailable = marbleModel()->positionTracking()->status() == PositionProviderStatusAvailable;
    bool const positionValid = m_currentPosition.isValid();
    if (gpsActive && positionAvailable && positionValid) {
        m_lastBoundingBox = viewport->viewLatLonAltBox();

        qreal screenPositionX, screenPositionY;
        if (!viewport->screenCoordinates(m_currentPosition, screenPositionX, screenPositionY)) {
            return true;
        }
        const GeoDataCoordinates top(m_currentPosition.longitude(), m_currentPosition.latitude() + 0.1);
        qreal screenTopX, screenTopY;
        if (!viewport->screenCoordinates(top, screenTopX, screenTopY)) {
            return true;
        }
        qreal const correction = -90.0 + RAD2DEG * atan2(screenPositionY - screenTopY, screenPositionX - screenTopX);
        const qreal rotation = m_heading + correction;

        if (m_useCustomCursor) {
            QTransform transform;
            transform.rotate(rotation);
            bool const highQuality = painter->mapQuality() == HighQuality || painter->mapQuality() == PrintQuality;
            Qt::TransformationMode const mode = highQuality ? Qt::SmoothTransformation : Qt::FastTransformation;
            m_customCursorTransformed = m_customCursor.transformed(transform, mode);
        } else {
            // Calculate the scaled arrow shape
            const QPointF baseX(m_cursorSize, 0.0);
            const QPointF baseY(0.0, m_cursorSize);
            const QPointF relativeLeft = -(baseX * 9) + (baseY * 9);
            const QPointF relativeRight = (baseX * 9) + (baseY * 9);
            const QPointF relativeTip = -(baseY * 19.0);
            m_arrow = QPolygonF() << QPointF(0.0, 0.0) << relativeLeft << relativeTip << relativeRight;

            // Rotate the shape according to the current direction and move it to the screen center
            QTransform transformation;
            transformation.translate(screenPositionX, screenPositionY);
            transformation.rotate(rotation);
            m_arrow = m_arrow * transformation;

            m_dirtyRegion = QRegion();
            m_dirtyRegion += (m_arrow.boundingRect().toRect());
            m_dirtyRegion += (m_previousArrow.boundingRect().toRect());
        }

        painter->save();

        GeoDataAccuracy accuracy = marbleModel()->positionTracking()->accuracy();
        if (accuracy.horizontal > 0 && accuracy.horizontal < 1000) {
            // Paint a circle indicating the position accuracy
            painter->setPen(Qt::transparent);
            qreal planetRadius = m_marbleModel->planet()->radius();
            int width = qRound(accuracy.horizontal * viewport->radius() / planetRadius);
            if (MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen) {
                int arrowSize = qMax<int>(m_arrow.boundingRect().width(), m_arrow.boundingRect().height());
                width = qMax<int>(width, arrowSize + 10);
            }

            painter->setBrush(m_accuracyColor);
            painter->drawEllipse(m_currentPosition, width, width);
        }

        // Draw trail if requested.
        if (m_showTrail) {
            painter->save();

            // Use selected color to draw trail.
            painter->setBrush(m_trailColor);
            painter->setPen(m_trailColor);

            // we don't draw m_trail[0] which is current position
            for (int i = 1; i < m_trail.size(); ++i) {
                // Get screen coordinates from coordinates on the map.
                qreal trailPointX, trailPointY;
                viewport->screenCoordinates(m_trail[i], trailPointX, trailPointY);

                const int size = (sm_numTrailPoints - i) * 3;
                QRectF trailRect;
                trailRect.setX(trailPointX - size / 2.0);
                trailRect.setY(trailPointY - size / 2.0);
                trailRect.setWidth(size);
                trailRect.setHeight(size);

                const qreal opacity = 1.0 - 0.15 * (i - 1);
                painter->setOpacity(opacity);
                painter->drawEllipse(trailRect);
            }

            painter->restore();
        }

        if (m_useCustomCursor) {
            painter->drawPixmap(m_currentPosition, m_customCursorTransformed);
        } else {
            painter->setPen(Qt::black);
            painter->setBrush(Qt::white);
            painter->drawPolygon(m_arrow);
        }

        painter->restore();
        m_previousArrow = m_arrow;
    }
    return true;
}

QHash<QString, QVariant> PositionMarker::settings() const
{
    QHash<QString, QVariant> settings = RenderPlugin::settings();

    settings.insert(QStringLiteral("useCustomCursor"), m_useCustomCursor);
    settings.insert(QStringLiteral("cursorPath"), m_cursorPath);
    settings.insert(QStringLiteral("cursorSize"), m_cursorSize);
    settings.insert(QStringLiteral("acColor"), m_accuracyColor);
    settings.insert(QStringLiteral("trailColor"), m_trailColor);
    settings.insert(QStringLiteral("showTrail"), m_showTrail);

    return settings;
}

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

    const bool smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
    QColor defaultColor = Oxygen::brickRed4;
    defaultColor.setAlpha(smallScreen ? 80 : 40);

    m_useCustomCursor = settings.value(QStringLiteral("useCustomCursor"), false).toBool();
    m_cursorPath = settings.value(QStringLiteral("cursorPath"), m_defaultCursorPath).toString();
    m_cursorSize = settings.value(QStringLiteral("cursorSize"), 1.0).toFloat();
    loadCustomCursor(m_cursorPath, m_useCustomCursor);

    m_accuracyColor = settings.value(QStringLiteral("acColor"), defaultColor).value<QColor>();
    m_trailColor = settings.value(QStringLiteral("trailColor"), QColor(0, 0, 255)).value<QColor>();
    m_showTrail = settings.value(QStringLiteral("showTrail"), false).toBool();

    readSettings();
}

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

    if (m_useCustomCursor)
        ui_configWidget->m_customCursor->click();
    else
        ui_configWidget->m_originalCursor->click();

    bool found = false;
    float cursorSize = m_cursorSize;
    for (int i = 0; i < sm_numResizeSteps && !found; i++) {
        if (sm_resizeSteps[i] == cursorSize) {
            ui_configWidget->m_resizeSlider->setValue(i);
            found = true;
        }
    }
    if (!found) {
        ui_configWidget->m_resizeSlider->setValue(sm_defaultSizeStep);
        cursorSize = sm_resizeSteps[sm_defaultSizeStep];
    }

    ui_configWidget->m_sizeLabel->setText(tr("Cursor Size: %1").arg(cursorSize));
    QPalette palette = ui_configWidget->m_acColorChooserButton->palette();
    palette.setColor(QPalette::Button, m_accuracyColor);
    ui_configWidget->m_acColorChooserButton->setPalette(palette);
    palette = ui_configWidget->m_trailColorChooserButton->palette();
    palette.setColor(QPalette::Button, m_trailColor);
    ui_configWidget->m_trailColorChooserButton->setPalette(palette);
    ui_configWidget->m_trailCheckBox->setChecked(m_showTrail);
}

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

    m_useCustomCursor = ui_configWidget->m_customCursor->isChecked();
    m_cursorSize = sm_resizeSteps[ui_configWidget->m_resizeSlider->value()];
    m_showTrail = ui_configWidget->m_trailCheckBox->isChecked();

    Q_EMIT settingsChanged(nameId());
}

void PositionMarker::setPosition(const GeoDataCoordinates &position)
{
    m_previousPosition = m_currentPosition;
    m_currentPosition = position;
    m_heading = marbleModel()->positionTracking()->direction();
    // Update the trail
    m_trail.push_front(m_currentPosition);
    for (int i = sm_numTrailPoints + 1; i < m_trail.size(); ++i) {
        m_trail.pop_back();
    }
    if (m_lastBoundingBox.contains(m_currentPosition)) {
        Q_EMIT repaintNeeded(m_dirtyRegion);
    }
}

void PositionMarker::chooseCustomCursor()
{
    QString filename = QFileDialog::getOpenFileName(nullptr, tr("Choose Custom Cursor"));
    if (!filename.isEmpty())
        loadCustomCursor(filename, true);
}

void PositionMarker::loadCustomCursor(const QString &filename, bool useCursor)
{
    m_customCursor = QPixmap(filename).scaled(22 * m_cursorSize, 22 * m_cursorSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
    if (!m_customCursor.isNull()) {
        if (m_configDialog) {
            if (useCursor)
                ui_configWidget->m_customCursor->click();
            ui_configWidget->m_fileChooserButton->setIconSize(QSize(m_customCursor.width(), m_customCursor.height()));
            ui_configWidget->m_fileChooserButton->setIcon(QIcon(m_customCursor));
        }
        m_cursorPath = filename;
    } else {
        mDebug() << "Unable to load custom cursor from " << filename << ". "
                 << "The default cursor will be used instead";
        if (m_configDialog)
            ui_configWidget->m_fileChooserButton->setIcon(QIcon(m_defaultCursor));
        m_customCursor = m_defaultCursor;
        m_cursorPath = m_defaultCursorPath;
    }
}

void PositionMarker::loadDefaultCursor()
{
    m_defaultCursor = QPixmap(m_defaultCursorPath).scaled(22 * m_cursorSize, 22 * m_cursorSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}

void PositionMarker::chooseColor()
{
    QColor initialColor;
    if (sender() == ui_configWidget->m_acColorChooserButton) {
        initialColor = m_accuracyColor;
    } else if (sender() == ui_configWidget->m_trailColorChooserButton) {
        initialColor = m_trailColor;
    }
    QColor color = QColorDialog::getColor(initialColor, nullptr, tr("Please choose a color"), QColorDialog::ShowAlphaChannel);
    if (color.isValid()) {
        QPalette palette;
        if (sender() == ui_configWidget->m_acColorChooserButton) {
            m_accuracyColor = color;
            palette = ui_configWidget->m_acColorChooserButton->palette();
            palette.setColor(QPalette::Button, m_accuracyColor);
            ui_configWidget->m_acColorChooserButton->setPalette(palette);
        } else if (sender() == ui_configWidget->m_trailColorChooserButton) {
            m_trailColor = color;
            palette = ui_configWidget->m_trailColorChooserButton->palette();
            palette.setColor(QPalette::Button, m_trailColor);
            ui_configWidget->m_trailColorChooserButton->setPalette(palette);
        }
    }
}

void PositionMarker::resizeCursor(int step)
{
    m_cursorSize = sm_resizeSteps[step];
    float newSize = 22 * m_cursorSize;
    m_customCursor = QPixmap(m_cursorPath).scaled(newSize, newSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
    ui_configWidget->m_sizeLabel->setText(tr("Cursor Size: %1").arg(m_cursorSize));
    if (!m_customCursor.isNull()) {
        ui_configWidget->m_fileChooserButton->setIconSize(QSize(m_customCursor.width(), m_customCursor.height()));
        ui_configWidget->m_fileChooserButton->setIcon(QIcon(m_customCursor));
    }
    loadDefaultCursor();
}

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

}

#include "moc_PositionMarker.cpp"
