/*
    SPDX-FileCopyrightText: 2008 Torsten Rahn <rahn@kde.org>
    SPDX-FileCopyrightText: 2008 Jens-Michael Hoffmann <jensmh@gmx.de>
    SPDX-FileCopyrightText: 2012 Ander Pijoan <ander.pijoan@deusto.es>

    SPDX-License-Identifier: LGPL-2.1-or-later
*/

#include "GeoSceneTileDataset.h"
#include "GeoSceneEquirectTileProjection.h"
#include "GeoSceneMercatorTileProjection.h"
#include "GeoSceneTypes.h"

#include "DownloadPolicy.h"
#include "MarbleDebug.h"
#include "MarbleDirs.h"
#include "ServerLayout.h"
#include "TileId.h"

#include <QImage>
#include <QUrl>

namespace Marble
{

GeoSceneTileDataset::GeoSceneTileDataset(const QString &name)
    : GeoSceneAbstractDataset(name)
    , m_sourceDir()
    , m_installMap()
    , m_storageLayoutMode(Marble)
    , m_serverLayout(new MarbleServerLayout(this))
    , m_levelZeroColumns(defaultLevelZeroColumns)
    , m_levelZeroRows(defaultLevelZeroRows)
    , m_minimumTileLevel(0)
    , m_maximumTileLevel(-1)
    , m_tileProjection(new GeoSceneEquirectTileProjection())
    , m_blending()
    , m_downloadUrls()
    , m_nextUrl(m_downloadUrls.constEnd())
{
    m_tileProjection->setLevelZeroColumns(m_levelZeroColumns);
    m_tileProjection->setLevelZeroRows(m_levelZeroRows);
}

GeoSceneTileDataset::~GeoSceneTileDataset()
{
    qDeleteAll(m_downloadPolicies);
    delete m_serverLayout;
    delete m_tileProjection;
}

const char *GeoSceneTileDataset::nodeType() const
{
    return GeoSceneTypes::GeoSceneTileDatasetType;
}

QString GeoSceneTileDataset::sourceDir() const
{
    return m_sourceDir;
}

void GeoSceneTileDataset::setSourceDir(const QString &sourceDir)
{
    m_sourceDir = sourceDir;
}

QString GeoSceneTileDataset::installMap() const
{
    return m_installMap;
}

void GeoSceneTileDataset::setInstallMap(const QString &installMap)
{
    m_installMap = installMap;
}

GeoSceneTileDataset::StorageLayout GeoSceneTileDataset::storageLayout() const
{
    return m_storageLayoutMode;
}

void GeoSceneTileDataset::setStorageLayout(const StorageLayout layout)
{
    m_storageLayoutMode = layout;
}

void GeoSceneTileDataset::setServerLayout(const ServerLayout *layout)
{
    delete m_serverLayout;
    m_serverLayout = layout;
}

const ServerLayout *GeoSceneTileDataset::serverLayout() const
{
    return m_serverLayout;
}

int GeoSceneTileDataset::levelZeroColumns() const
{
    return m_levelZeroColumns;
}

void GeoSceneTileDataset::setLevelZeroColumns(const int columns)
{
    m_levelZeroColumns = columns;
    m_tileProjection->setLevelZeroColumns(m_levelZeroColumns);
}

int GeoSceneTileDataset::levelZeroRows() const
{
    return m_levelZeroRows;
}

void GeoSceneTileDataset::setLevelZeroRows(const int rows)
{
    m_levelZeroRows = rows;
    m_tileProjection->setLevelZeroRows(m_levelZeroRows);
}

int GeoSceneTileDataset::maximumTileLevel() const
{
    return m_maximumTileLevel;
}

void GeoSceneTileDataset::setMaximumTileLevel(const int maximumTileLevel)
{
    m_maximumTileLevel = maximumTileLevel;
}

int GeoSceneTileDataset::minimumTileLevel() const
{
    return m_minimumTileLevel;
}

void GeoSceneTileDataset::setMinimumTileLevel(int level)
{
    m_minimumTileLevel = level;
}

void GeoSceneTileDataset::setTileLevels(const QString &tileLevels)
{
    if (tileLevels.isEmpty()) {
        m_tileLevels.clear();
        return;
    }

    const QStringList values = tileLevels.split(QLatin1Char(','));
    for (const QString &value : values) {
        bool canParse(false);
        int const tileLevel = value.trimmed().toInt(&canParse);
        if (canParse && tileLevel >= 0 && tileLevel < 100) {
            m_tileLevels << tileLevel;
        } else {
            mDebug() << "Cannot parse tile level part " << value << " in " << tileLevels << ", ignoring it.";
        }
    }

    if (!m_tileLevels.isEmpty()) {
        std::sort(m_tileLevels.begin(), m_tileLevels.end());
        m_minimumTileLevel = m_tileLevels.first();
        m_maximumTileLevel = m_tileLevels.last();
    }
}

QList<int> GeoSceneTileDataset::tileLevels() const
{
    return m_tileLevels;
}

QList<QUrl> GeoSceneTileDataset::downloadUrls() const
{
    return m_downloadUrls;
}

const QSize GeoSceneTileDataset::tileSize() const
{
    if (m_tileSize.isEmpty()) {
        const TileId id(0, 0, 0, 0);
        QString const fileName = relativeTileFileName(id);
        QFileInfo const dirInfo(fileName);
        QString const path = dirInfo.isAbsolute() ? fileName : MarbleDirs::path(fileName);

        QImage testTile(path);

        if (testTile.isNull()) {
            mDebug() << "Tile size is missing in dgml and no base tile found in " << themeStr();
            mDebug() << "Using default tile size " << c_defaultTileSize;
            m_tileSize = QSize(c_defaultTileSize, c_defaultTileSize);
        } else {
            m_tileSize = testTile.size();
        }

        if (m_tileSize.isEmpty()) {
            mDebug() << "Tile width or height cannot be 0. Falling back to default tile size.";
            m_tileSize = QSize(c_defaultTileSize, c_defaultTileSize);
        }
    }

    Q_ASSERT(!m_tileSize.isEmpty());
    return m_tileSize;
}

GeoDataLatLonBox GeoSceneTileDataset::latLonBox() const
{
    return m_latLonBox;
}

void GeoSceneTileDataset::setLatLonBox(const GeoDataLatLonBox &box)
{
    m_latLonBox = box;
}

void GeoSceneTileDataset::setTileSize(const QSize &tileSize)
{
    if (tileSize.isEmpty()) {
        mDebug() << "Ignoring invalid tile size " << tileSize;
    } else {
        m_tileSize = tileSize;
    }
}

void GeoSceneTileDataset::setTileProjection(GeoSceneAbstractTileProjection::Type projectionType)
{
    if (m_tileProjection->type() == projectionType) {
        return;
    }

    delete m_tileProjection;
    if (projectionType == GeoSceneAbstractTileProjection::Mercator) {
        m_tileProjection = new GeoSceneMercatorTileProjection();
    } else {
        m_tileProjection = new GeoSceneEquirectTileProjection();
    }

    m_tileProjection->setLevelZeroColumns(m_levelZeroColumns);
    m_tileProjection->setLevelZeroRows(m_levelZeroRows);
}

const GeoSceneAbstractTileProjection *GeoSceneTileDataset::tileProjection() const
{
    return m_tileProjection;
}

GeoSceneAbstractTileProjection::Type GeoSceneTileDataset::tileProjectionType() const
{
    return m_tileProjection->type();
}

// Even though this method changes the internal state, it may be const
// because the compiler is forced to invoke this method for different TileIds.
QUrl GeoSceneTileDataset::downloadUrl(const TileId &id) const
{
    // default download url
    if (m_downloadUrls.empty()) {
        QUrl const defaultUrl = QUrl(QLatin1StringView("https://maps.kde.org/") + m_serverLayout->sourceDir());
        mDebug() << "No download URL specified for tiles stored in " << m_sourceDir << ", falling back to " << defaultUrl.toString();
        return m_serverLayout->downloadUrl(defaultUrl, id);
    } else if (m_downloadUrls.size() == 1) {
        return m_serverLayout->downloadUrl(*m_nextUrl, id);
    } else {
        if (m_nextUrl == m_downloadUrls.constEnd()) {
            m_nextUrl = m_downloadUrls.constBegin();
        }
        const QUrl url = m_serverLayout->downloadUrl(*m_nextUrl, id);
        ++m_nextUrl;
        return url;
    }
}

void GeoSceneTileDataset::addDownloadUrl(const QUrl &url)
{
    m_downloadUrls.append(url);
    // FIXME: this could be done only once
    m_nextUrl = m_downloadUrls.constBegin();
}

QString GeoSceneTileDataset::relativeTileFileName(const TileId &id) const
{
    const QString suffix = fileFormat().toLower();

    QString relFileName;

    switch (m_storageLayoutMode) {
    case GeoSceneTileDataset::Marble:
        relFileName = QStringLiteral("%1/%2/%3/%3_%4.%5")
                          .arg(themeStr())
                          .arg(id.zoomLevel())
                          .arg(id.y(), tileDigits, 10, QLatin1Char('0'))
                          .arg(id.x(), tileDigits, 10, QLatin1Char('0'))
                          .arg(suffix);
        break;
    case GeoSceneTileDataset::OpenStreetMap:
        relFileName = QStringLiteral("%1/%2/%3/%4.%5").arg(themeStr()).arg(id.zoomLevel()).arg(id.x()).arg(id.y()).arg(suffix);
        break;
    case GeoSceneTileDataset::TileMapService:
        relFileName = QStringLiteral("%1/%2/%3/%4.%5")
                          .arg(themeStr())
                          .arg(id.zoomLevel())
                          .arg(id.x())
                          .arg((1 << id.zoomLevel()) - id.y() - 1) // Y coord in TMS runs from bottom to top
                          .arg(suffix);
        break;
    }

    return relFileName;
}

QString GeoSceneTileDataset::themeStr() const
{
    QFileInfo const dirInfo(sourceDir());
    return dirInfo.isAbsolute() ? sourceDir() : QLatin1StringView("maps/") + sourceDir();
}

QList<const DownloadPolicy *> GeoSceneTileDataset::downloadPolicies() const
{
    return m_downloadPolicies;
}

void GeoSceneTileDataset::addDownloadPolicy(const DownloadUsage usage, const int maximumConnections)
{
    auto const policy = new DownloadPolicy(DownloadPolicyKey(hostNames(), usage));
    policy->setMaximumConnections(maximumConnections);
    m_downloadPolicies.append(policy);
    mDebug() << "added download policy" << hostNames() << usage << maximumConnections;
}

QStringList GeoSceneTileDataset::hostNames() const
{
    QStringList result;
    result.reserve(m_downloadUrls.size());

    QList<QUrl>::const_iterator pos = m_downloadUrls.constBegin();
    QList<QUrl>::const_iterator const end = m_downloadUrls.constEnd();
    for (; pos != end; ++pos)
        result.append((*pos).host());
    return result;
}

}
