// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default

#include "qgeopositioninfosource_wasm_p.h"
#include "qgeopositioninfosourcedispatcher_wasm_p.h"

#include <QtCore/QDebug>
#include <QtCore/QPermission>
#include <QtCore/QCoreApplication>

#include <emscripten/val.h>
#include <emscripten/bind.h>


// QGeoPositionInfoSource implemented using the Web Geolocation API

QT_BEGIN_NAMESPACE

typedef emscripten::val GeolocationCoordinates;

QGeoPositionInfoSourceWasm::QGeoPositionInfoSourceWasm(QObject *parent)
    : QGeoPositionInfoSource(parent)
{
    // QGeoPositionInfoSourceWasm should not be constructed if
    // geolocation support is missing.
    Q_ASSERT(!geolocation().isUndefined());

    m_dispatcher = QGeoPositionInfoDispatcherWasm::instance();
    m_instanceId = m_dispatcher->registerInfoSourceInstance(this);

    successCallback = val::module_property("qtGeolocationPositionSuccess").call<val>("bind", val::undefined(), m_instanceId);
    errorCallback = val::module_property("qtGeolocationPositionError").call<val>("bind", val::undefined(), m_instanceId);

}

Geolocation QGeoPositionInfoSourceWasm::geolocation()
{
    return val::global("navigator")["geolocation"];
}

QGeoPositionInfoSourceWasm::~QGeoPositionInfoSourceWasm()
{
    stopUpdates();
    m_dispatcher->unregisterInfoSourceInstance(m_instanceId);
}

void QGeoPositionInfoSourceWasm::setUpdateInterval(int msec)
{
    // If msec is 0 we send updates as data becomes available, otherwise we force msec to be equal
    // to or larger than the minimum update interval.
    if (msec != 0 && msec < minimumUpdateInterval())
        msec = minimumUpdateInterval();

    QGeoPositionInfoSource::setUpdateInterval(msec);
}

void QGeoPositionInfoSourceWasm::startUpdates()
{
    if (m_updates_running) {
        qDebug() << "startUpdates() is called, but an update is already running.";
        return;
    }

    if (preferredPositioningMethods() == 0) {
        m_lastError = QGeoPositionInfoSource::UnknownSourceError;
        return;
    }

    m_updates_running = true;
    m_lastError = QGeoPositionInfoSource::NoError;

    val options = val::object();
    options.set("maximumAge", 0);

    if (preferredPositioningMethods() & PositioningMethod::SatellitePositioningMethods)
        options.set("enableHighAccuracy", true);

    m_handlerId = geolocation().call<val>("watchPosition", successCallback, errorCallback, options);
}

void QGeoPositionInfoSourceWasm::stopUpdates()
{
    geolocation().call<void>("clearWatch", m_handlerId);
    m_updates_running = false;
}

void QGeoPositionInfoSourceWasm::requestUpdate(int timeout)
{
    if (timeout < minimumUpdateInterval()) {
        emit errorOccurred(UpdateTimeoutError);
        return;
    }

    if (m_singleUpdatePending) {
        qDebug() << "requestUpdate() called while a previous update is still pending. Ignoring.";
        return;
    }

    m_singleUpdatePending = true;
    m_lastError = QGeoPositionInfoSource::NoError;

    val options = val::object();
    options.set("maximumAge", 0); // maximum cache age: 0: don't use cache
    options.set("timeout", timeout);

    if (preferredPositioningMethods() & PositioningMethod::SatellitePositioningMethods)
        options.set("enableHighAccuracy", true);

    geolocation().call<void>("getCurrentPosition", successCallback, errorCallback, options);
}

QGeoPositionInfoSource::PositioningMethods QGeoPositionInfoSourceWasm::supportedPositioningMethods() const
{
    return QGeoPositionInfoSource::AllPositioningMethods;
}

void QGeoPositionInfoSourceWasm::setPreferredPositioningMethods(PositioningMethods methods)
{
    PositioningMethods previousPreferredPositioningMethods = preferredPositioningMethods();
    QGeoPositionInfoSource::setPreferredPositioningMethods(methods);
    if (previousPreferredPositioningMethods == preferredPositioningMethods())
        return;

    if (m_updates_running)
        reconfigureRunningSystem();
}


int QGeoPositionInfoSourceWasm::minimumUpdateInterval() const
{
    return 1000;
}

QGeoPositionInfo QGeoPositionInfoSourceWasm::lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const
{
    Q_UNUSED(fromSatellitePositioningMethodsOnly);
    return m_lastKnownPosition;
}

QGeoPositionInfoSource::Error QGeoPositionInfoSourceWasm::error() const
{
    qDebug() << "QGeoPositionInfoSourceWasm::error";
    return m_lastError;
}

void QGeoPositionInfoSourceWasm::geolocationSuccess(GeolocationPosition position)
{
    // Populate a QGeoPositionInfo from the native GeolocationPosition. Some
    // attributes are optional and may be null.
    QGeoPositionInfo info;
    info.setTimestamp(QDateTime::fromMSecsSinceEpoch(position["timestamp"].as<double>()));

    GeolocationCoordinates coords = position["coords"];
    val latitude = coords["latitude"];
    val longitude = coords["longitude"];
    val altitude = coords["altitude"];
    info.setCoordinate(altitude.isNull() ?
        QGeoCoordinate(latitude.as<double>(), longitude.as<double>()) :
        QGeoCoordinate(latitude.as<double>(), longitude.as<double>(), altitude.as<double>())
    );

    if (val speed = coords["speed"]; !speed.isNull())
        info.setAttribute(QGeoPositionInfo::GroundSpeed, speed.as<double>());
    if (val heading = coords["heading"]; !heading.isNull())
        info.setAttribute(QGeoPositionInfo::Direction, heading.as<double>());

    info.setAttribute(QGeoPositionInfo::HorizontalAccuracy, coords["accuracy"].as<double>());
    if (val altitudeAccuracy = coords["altitudeAccuracy"]; !altitudeAccuracy.isNull())
        info.setAttribute(QGeoPositionInfo::VerticalAccuracy, altitudeAccuracy.as<double>());

    m_lastKnownPosition = info;
    m_singleUpdatePending = false;

    emit positionUpdated(info);
}

void QGeoPositionInfoSourceWasm::geolocationError(GeolocationPositionError error)
{
    qDebug() << "geolocationError";
    qDebug() << "Handler ID:" << m_handlerId.as<int>();

    int code = error["code"].as<int>();
    QString message = error["message"].as<QString>();

    QGeoPositionInfoSource::Error qtError = QGeoPositionInfoSource::UnknownSourceError;

    switch (code) {
        case 1:
            qtError = QGeoPositionInfoSource::AccessError; // -> PERMISSION_DENIED
            break;
        case 2:
            qtError = QGeoPositionInfoSource::UnknownSourceError; // -> POSITION_UNAVAILABLE
            break;
        case 3:
            qtError = QGeoPositionInfoSource::UpdateTimeoutError; // -> TIMEOUT
            break;
    }

    qDebug() << "Message: " << message;

    m_lastError = qtError;
    m_singleUpdatePending = false;

    emit errorOccurred(qtError);
}

void QGeoPositionInfoSourceWasm::reconfigureRunningSystem()
{
    if (!m_updates_running)
        return;

    stopUpdates();
    startUpdates();
}


QT_END_NAMESPACE

#include "moc_qgeopositioninfosource_wasm_p.cpp"
