// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2012 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
//

#include "PositionTracking.h"
#include "GeoDataAccuracy.h"
#include "GeoDataTreeModel.h"
#include "PositionProviderPlugin.h"
#include "TestUtils.h"

#include <QSignalSpy>

class FakeProvider : public Marble::PositionProviderPlugin
{
public:
    FakeProvider()
        : m_status(Marble::PositionProviderStatusUnavailable)
        , m_position()
        , m_accuracy()
        , m_speed(0.0)
        , m_direction(0.0)
        , m_timestamp()
    {
    }

    QString name() const override
    {
        return "fake plugin";
    }
    QString guiString() const override
    {
        return "fake";
    }
    QString nameId() const override
    {
        return "fakeplugin";
    }
    QString version() const override
    {
        return "1.0";
    }
    QString description() const override
    {
        return "plugin for testing";
    }
    QIcon icon() const override
    {
        return {};
    }
    QString copyrightYears() const override
    {
        return "2012";
    }
    QList<Marble::PluginAuthor> pluginAuthors() const override
    {
        return {};
    }
    void initialize() override
    {
    }
    bool isInitialized() const override
    {
        return true;
    }

    Marble::PositionProviderStatus status() const override
    {
        return m_status;
    }
    Marble::GeoDataCoordinates position() const override
    {
        return m_position;
    }
    Marble::GeoDataAccuracy accuracy() const override
    {
        return m_accuracy;
    }
    qreal speed() const override
    {
        return m_speed;
    }
    qreal direction() const override
    {
        return m_direction;
    }
    QDateTime timestamp() const override
    {
        return m_timestamp;
    }

    Marble::PositionProviderPlugin *newInstance() const override
    {
        return nullptr;
    }

    void setStatus(Marble::PositionProviderStatus status);
    void
    setPosition(const Marble::GeoDataCoordinates &position, const Marble::GeoDataAccuracy &accuracy, qreal speed, qreal direction, const QDateTime &timestamp);

private:
    Marble::PositionProviderStatus m_status;
    Marble::GeoDataCoordinates m_position;
    Marble::GeoDataAccuracy m_accuracy;
    qreal m_speed;
    qreal m_direction;
    QDateTime m_timestamp;
};

void FakeProvider::setStatus(Marble::PositionProviderStatus status)
{
    const Marble::PositionProviderStatus oldStatus = m_status;

    m_status = status;

    if (oldStatus != m_status) {
        Q_EMIT statusChanged(m_status);
    }
}

void FakeProvider::setPosition(const Marble::GeoDataCoordinates &position,
                               const Marble::GeoDataAccuracy &accuracy,
                               qreal speed,
                               qreal direction,
                               const QDateTime &timestamp)
{
    m_position = position;
    m_accuracy = accuracy;
    m_speed = speed;
    m_direction = direction;
    m_timestamp = timestamp;

    Q_EMIT positionChanged(m_position, m_accuracy);
}

namespace Marble
{

class PositionTrackingTest : public QObject
{
    Q_OBJECT

public:
    PositionTrackingTest();

private Q_SLOTS:
    void construct();

    void statusChanged_data();
    void statusChanged();

    void setPositionProviderPlugin();

    void clearTrack();
};

PositionTrackingTest::PositionTrackingTest()
{
    qRegisterMetaType<GeoDataCoordinates>("GeoDataCoordinates");
    qRegisterMetaType<PositionProviderStatus>("PositionProviderStatus");
}

void PositionTrackingTest::construct()
{
    GeoDataTreeModel treeModel;
    const PositionTracking tracking(&treeModel);

    QCOMPARE(const_cast<PositionTracking &>(tracking).positionProviderPlugin(), static_cast<PositionProviderPlugin *>(nullptr));
    QCOMPARE(tracking.speed(), qreal(0));
    QCOMPARE(tracking.direction(), qreal(0));
    QCOMPARE(tracking.timestamp(), QDateTime());
    QCOMPARE(tracking.accuracy(), GeoDataAccuracy());
    QCOMPARE(tracking.trackVisible(), true);
    QCOMPARE(tracking.currentLocation(), GeoDataCoordinates());
    QCOMPARE(tracking.status(), PositionProviderStatusUnavailable);
    QCOMPARE(tracking.isTrackEmpty(), true);

    QCOMPARE(treeModel.rowCount(), 1);
    const QModelIndex indexPositionTracking = treeModel.index(0, 0);
    QCOMPARE(treeModel.data(indexPositionTracking, Qt::DisplayRole).toString(), QString("Position Tracking"));
    QCOMPARE(treeModel.rowCount(indexPositionTracking), 2);
    const QModelIndex indexCurrentPosition = treeModel.index(0, 0, indexPositionTracking);
    QCOMPARE(treeModel.data(indexCurrentPosition, Qt::DisplayRole).toString(), QString("Current Position"));
    const QModelIndex indexCurrentTrack = treeModel.index(1, 0, indexPositionTracking);
    QCOMPARE(treeModel.data(indexCurrentTrack, Qt::DisplayRole).toString(), QString("Current Track"));
}

void PositionTrackingTest::statusChanged_data()
{
    QTest::addColumn<PositionProviderStatus>("finalStatus");

    addRow() << PositionProviderStatusError;
    addRow() << PositionProviderStatusUnavailable;
    addRow() << PositionProviderStatusAcquiring;
    addRow() << PositionProviderStatusAvailable;
}

void PositionTrackingTest::statusChanged()
{
    QFETCH(PositionProviderStatus, finalStatus);
    const int expectedStatusChangedCount = (finalStatus == PositionProviderStatusUnavailable) ? 0 : 1;

    GeoDataTreeModel treeModel;
    PositionTracking tracking(&treeModel);

    QSignalSpy statusChangedSpy(&tracking, SIGNAL(statusChanged(PositionProviderStatus)));

    FakeProvider provider;
    provider.setStatus(finalStatus);

    tracking.setPositionProviderPlugin(&provider);

    QCOMPARE(tracking.status(), finalStatus);
    QCOMPARE(statusChangedSpy.count(), expectedStatusChangedCount);
}

void PositionTrackingTest::setPositionProviderPlugin()
{
    const GeoDataCoordinates coordinates(1.2, 0.9);
    const GeoDataAccuracy accuracy(GeoDataAccuracy::Detailed, 10.0, 22.0);
    const qreal speed = 32.8;
    const qreal direction = 49.7;
    const QDateTime timestamp(QDate(1, 3, 1994), QTime());

    GeoDataTreeModel treeModel;
    PositionTracking tracking(&treeModel);

    QSignalSpy gpsLocationSpy(&tracking, SIGNAL(gpsLocation(GeoDataCoordinates, qreal)));

    QPointer<FakeProvider> provider(new FakeProvider);
    provider->setStatus(PositionProviderStatusAvailable);
    provider->setPosition(coordinates, accuracy, speed, direction, timestamp);

    tracking.setPositionProviderPlugin(provider);

    QCOMPARE(tracking.currentLocation(), coordinates);
    QCOMPARE(tracking.accuracy(), accuracy);
    QCOMPARE(tracking.speed(), speed);
    QCOMPARE(tracking.direction(), direction);
    QCOMPARE(tracking.timestamp(), timestamp);
    QCOMPARE(gpsLocationSpy.count(), 1);

    tracking.setPositionProviderPlugin(nullptr);

    QVERIFY(provider.isNull());
}

void PositionTrackingTest::clearTrack()
{
    const GeoDataCoordinates position(2.1, 0.8);
    const GeoDataAccuracy accuracy(GeoDataAccuracy::Detailed, 10.0, 22.0);
    const qreal speed = 32.8;
    const qreal direction = 49.7;
    const QDateTime timestamp(QDate(1, 3, 1994), QTime());

    GeoDataTreeModel treeModel;
    PositionTracking tracking(&treeModel);

    FakeProvider provider;
    tracking.setPositionProviderPlugin(&provider);

    tracking.clearTrack();

    QVERIFY(tracking.isTrackEmpty());

    provider.setStatus(PositionProviderStatusAvailable);

    provider.setPosition(position, accuracy, speed, direction, timestamp);

    QVERIFY(!tracking.isTrackEmpty());

    tracking.clearTrack();

    QVERIFY(tracking.isTrackEmpty());
}

}

QTEST_MAIN(Marble::PositionTrackingTest)

#include "PositionTrackingTest.moc"
