// Copyright (C) 2015 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

#include "qgeotilefetcher_nokia.h"
#include "qgeomapreply_nokia.h"
#include "qgeotiledmap_nokia.h"
#include "qgeotiledmappingmanagerengine_nokia.h"
#include "qgeonetworkaccessmanager.h"
#include "qgeouriprovider.h"
#include "uri_constants.h"

#include <QtLocation/private/qgeotilespec_p.h>

#include <QDebug>
#include <QSize>
#include <QDir>
#include <QUrl>
#include <QTime>

#include <map>

QT_BEGIN_NAMESPACE

namespace
{
    QString sizeToStr(int size)
    {
        if (size > 256)
            return QStringLiteral("512");
        else if (size > 128)
            return QStringLiteral("256");
        else
            return QStringLiteral("128");   // 128 pixel tiles are deprecated.
    }

    bool isAerialType(const QString mapScheme)
    {
        return mapScheme.startsWith("satellite") || mapScheme.startsWith("hybrid") || mapScheme.startsWith("terrain");
    }
}
QGeoTileFetcherNokia::QGeoTileFetcherNokia(const QVariantMap &parameters,
                                           QGeoNetworkAccessManager *networkManager,
                                           QGeoTiledMappingManagerEngineNokia *engine,
                                           const QSize &tileSize,
                                           int ppi)
:   QGeoTileFetcher(engine), m_engineNokia(engine), m_networkManager(networkManager), m_ppi(ppi), m_copyrightsReply(nullptr),
    m_baseUriProvider(new QGeoUriProvider(this, parameters, QStringLiteral("here.mapping.host"), MAP_TILES_HOST)),
    m_aerialUriProvider(new QGeoUriProvider(this, parameters, QStringLiteral("here.mapping.host.aerial"), MAP_TILES_HOST_AERIAL))
{
    Q_ASSERT(networkManager);
    m_tileSize = qMax(tileSize.width(), tileSize.height());
    m_networkManager->setParent(this);

    m_applicationId = parameters.value(QStringLiteral("here.app_id")).toString();
    m_token = parameters.value(QStringLiteral("here.token")).toString();
}

QGeoTileFetcherNokia::~QGeoTileFetcherNokia()
{
}

QGeoTiledMapReply *QGeoTileFetcherNokia::getTileImage(const QGeoTileSpec &spec)
{
    // TODO add error detection for if request.connectivityMode() != QGraphicsGeoMap::OnlineMode
    int ppi = m_ppi;
    if ((spec.mapId() == 2) || (spec.mapId() == 12) || (spec.mapId() == 21)) {
        ppi = 72;  // HiDpi apparently not supported for these maps
    } else if ((spec.mapId() >= 7 && spec.mapId() <= 11)
            || (spec.mapId() == 14)
            || (spec.mapId() == 16)
            || (spec.mapId() == 18)
            || (spec.mapId() == 20)) {
        ppi = 250; // LoDpi apparently not supported for these maps
    }

    QString rawRequest = getRequestString(spec, ppi);
    if (rawRequest.isEmpty()) {
        return new QGeoTiledMapReply(QGeoTiledMapReply::UnknownError,
                                     tr("Mapping manager no longer exists"), this);
    }

    QNetworkRequest netRequest((QUrl(rawRequest))); // The extra pair of parens disambiguates this from a function declaration
    netRequest.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);

    QNetworkReply *netReply = m_networkManager->get(netRequest);

    QGeoTiledMapReply *mapReply = new QGeoMapReplyNokia(netReply, spec);

    return mapReply;
}

QString QGeoTileFetcherNokia::getRequestString(const QGeoTileSpec &spec, int ppi) const
{
    if (!m_engineNokia)
        return QString();

    static const QString http("http://");
    static const QString path("/maptile/2.1/maptile/newest/");
    static const QChar slash('/');

    QString requestString = http;

    const QString mapScheme = m_engineNokia->getScheme(spec.mapId());
    if (isAerialType(mapScheme))
        requestString += m_aerialUriProvider->getCurrentHost();
    else
        requestString += m_baseUriProvider->getCurrentHost();

    requestString += path;
    requestString += mapScheme;
    requestString += slash;
    requestString += QString::number(spec.zoom());
    requestString += slash;
    requestString += QString::number(spec.x());
    requestString += slash;
    requestString += QString::number(spec.y());
    requestString += slash;
    requestString += ((ppi > 72)) ? sizeToStr(m_tileSize * 2) : sizeToStr(m_tileSize);
    static const QString slashpng("/png8");
    requestString += slashpng;

    if (!m_token.isEmpty() && !m_applicationId.isEmpty()) { // TODO: remove the if
        requestString += "?token=";
        requestString += m_token;

        requestString += "&app_id=";
        requestString += m_applicationId;
    }

    requestString += "&ppi=" + QString::number(ppi);

    requestString += "&lg=";
    requestString += getLanguageString();
    return requestString;
}

QString QGeoTileFetcherNokia::getLanguageString() const
{
    if (!m_engineNokia)
        return QStringLiteral("ENG");

    QLocale locale = m_engineNokia.data()->locale();

    // English is the default, where no ln is specified. We hardcode the languages
    // here even though the entire list is updated automagically from the server.
    // The current languages are Arabic, Chinese, Simplified Chinese, English
    // French, German, Italian, Polish, Russian and Spanish. The default is English.
    // These are actually available from the same host under the URL: /maptiler/v2/info

    switch (locale.language()) {
    case QLocale::Arabic:
        return QStringLiteral("ARA");
    case QLocale::Chinese:
        if (locale.script() == QLocale::TraditionalChineseScript)
            return QStringLiteral("CHI");
        else
            return QStringLiteral("CHT");
    case QLocale::Dutch:
        return QStringLiteral("DUT");
    case QLocale::French:
        return QStringLiteral("FRE");
    case QLocale::German:
        return QStringLiteral("GER");
    case QLocale::Gaelic:
        return QStringLiteral("GLE");
    case QLocale::Greek:
        return QStringLiteral("GRE");
    case QLocale::Hebrew:
        return QStringLiteral("HEB");
    case QLocale::Hindi:
        return QStringLiteral("HIN");
    case QLocale::Indonesian:
        return QStringLiteral("IND");
    case QLocale::Italian:
        return QStringLiteral("ITA");
    case QLocale::Persian:
        return QStringLiteral("PER");
    case QLocale::Polish:
        return QStringLiteral("POL");
    case QLocale::Portuguese:
        return QStringLiteral("POR");
    case QLocale::Russian:
        return QStringLiteral("RUS");
    case QLocale::Sinhala:
        return QStringLiteral("SIN");
    case QLocale::Spanish:
        return QStringLiteral("SPA");
    case QLocale::Thai:
        return QStringLiteral("THA");
    case QLocale::Turkish:
        return QStringLiteral("TUR");
    case QLocale::Ukrainian:
        return QStringLiteral("UKR");
    case QLocale::Urdu:
        return QStringLiteral("URD");
    case QLocale::Vietnamese:
        return QStringLiteral("VIE");

    default:
        return QStringLiteral("ENG");
    }
    // No "lg" param means that we want English.
}

QString QGeoTileFetcherNokia::token() const
{
    return m_token;
}

QString QGeoTileFetcherNokia::applicationId() const
{
    return m_applicationId;
}

void QGeoTileFetcherNokia::copyrightsFetched()
{
    if (m_engineNokia && m_copyrightsReply->error() == QNetworkReply::NoError) {
        QMetaObject::invokeMethod(m_engineNokia.data(),
                                  "loadCopyrightsDescriptorsFromJson",
                                  Qt::QueuedConnection,
                                  Q_ARG(QByteArray, m_copyrightsReply->readAll()));
    }

    m_copyrightsReply->deleteLater();
}

void QGeoTileFetcherNokia::versionFetched()
{
    if (m_engineNokia && m_versionReply->error() == QNetworkReply::NoError) {
        QMetaObject::invokeMethod(m_engineNokia.data(),
                                  "parseNewVersionInfo",
                                  Qt::QueuedConnection,
                                  Q_ARG(QByteArray, m_versionReply->readAll()));
    }

    m_versionReply->deleteLater();
}

void QGeoTileFetcherNokia::fetchCopyrightsData()
{
    QString copyrightUrl = QStringLiteral("http://");

    copyrightUrl += m_baseUriProvider->getCurrentHost();
    copyrightUrl += QStringLiteral("/maptile/2.1/copyright/newest?output=json");

    if (!token().isEmpty()) {
        copyrightUrl += QStringLiteral("&token=");
        copyrightUrl += token();
    }

    if (!applicationId().isEmpty()) {
        copyrightUrl += QStringLiteral("&app_id=");
        copyrightUrl += applicationId();
    }

    QNetworkRequest netRequest((QUrl(copyrightUrl)));
    m_copyrightsReply = m_networkManager->get(netRequest);
    if (m_copyrightsReply->error() != QNetworkReply::NoError) {
        qWarning() << __FUNCTION__ << m_copyrightsReply->errorString();
        m_copyrightsReply->deleteLater();
        return;
    }

    if (m_copyrightsReply->isFinished()) {
        copyrightsFetched();
    } else {
        connect(m_copyrightsReply, &QNetworkReply::finished,
                this, &QGeoTileFetcherNokia::copyrightsFetched);
    }
}

void QGeoTileFetcherNokia::fetchVersionData()
{
    QString versionUrl = QStringLiteral("http://");

    versionUrl += m_baseUriProvider->getCurrentHost();
    versionUrl += QStringLiteral("/maptile/2.1/version");

    if (!token().isEmpty()) {
        versionUrl += QStringLiteral("?token=");
        versionUrl += token();
    }

    if (!applicationId().isEmpty()) {
        versionUrl += QStringLiteral("&app_id=");
        versionUrl += applicationId();
    }

    QNetworkRequest netRequest((QUrl(versionUrl)));
    m_versionReply = m_networkManager->get(netRequest);

    if (m_versionReply->error() != QNetworkReply::NoError) {
        qWarning() << __FUNCTION__ << m_versionReply->errorString();
        m_versionReply->deleteLater();
        return;
    }

    if (m_versionReply->isFinished())
        versionFetched();
    else
        connect(m_versionReply, &QNetworkReply::finished,
                this, &QGeoTileFetcherNokia::versionFetched);
}

QT_END_NAMESPACE
