/*
 *   SPDX-FileCopyrightText: 2007 Tobias Koenig <tokoe@kde.org>
 *   SPDX-FileCopyrightText: 2022 Alexander Lohnau <alexander.lohnau@gmx.de>
 *
 *   SPDX-License-Identifier: LGPL-2.0-only
 */

#include "comic.h"

#include <QDate>
#include <QDebug>
#include <QFileInfo>
#include <QImage>
#include <QNetworkInformation>
#include <QSettings>
#include <QStandardPaths>
#include <QTimer>

#include <KPackage/PackageLoader>
#include <QLoggingCategory>
#include <qtdeprecationdefinitions.h>

#include "cachedprovider.h"
#include "comic_debug.h"
#include "comicprovider.h"
#include "comicproviderkross.h"
#include "types.h"

ComicEngine::ComicEngine(QObject *parent)
    : QObject(parent)
    , mEmptySuffix(false)
{
    if (const auto instance = QNetworkInformation::instance())
        instance->loadBackendByFeatures(QNetworkInformation::Feature::Reachability);
    loadProviders();
}

QList<ComicProviderInfo> ComicEngine::loadProviders()
{
    mProviders.clear();
    const auto comics = KPackage::PackageLoader::self()->listKPackages(QStringLiteral("Plasma/Comic"));
    QList<ComicProviderInfo> providers;
    for (const auto &comicPackage : comics) {
        const KPluginMetaData comic = comicPackage.metadata();
        qCDebug(PLASMA_COMIC) << "ComicEngine::loadProviders()  service name=" << comic.name();
        ComicProviderInfo data;
        data.pluginId = comic.pluginId();
        data.name = comic.name();
        QFileInfo file(comic.iconName());
        if (file.isRelative()) {
            data.icon =
                QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString::fromLatin1("plasma/comics/%1/%2").arg(comic.pluginId(), comic.iconName()));
        } else {
            data.icon = comic.iconName();
        }
        mProviders << comic.pluginId();
        providers << data;
    }
    std::sort(providers.begin(), providers.end(), [](ComicProviderInfo a, ComicProviderInfo b) {
        return QString::localeAwareCompare(a.name, b.name) < 1;
    });
    return providers;
}

void ComicEngine::setMaxComicLimit(int maxComicLimit)
{
    CachedProvider::setMaxComicLimit(maxComicLimit);
}

bool ComicEngine::requestSource(const QString &identifier, RequestReason reason)
{
    if (m_jobs.contains(identifier)) {
        QTimer::singleShot(250, this, [this, identifier, reason]() {
            requestSource(identifier, reason);
        });
        return true;
    }

    const QStringList parts = identifier.split(QLatin1Char(':'), Qt::KeepEmptyParts);

    bool isCurrentComic = parts[1].isEmpty();

    // check whether it is cached, make sure second part present
    bool lookInCache = parts.count() > 1 && !(isCurrentComic && reason == RequestReason::Check) && CachedProvider::isCached(identifier);

    if (lookInCache || !isOnline()) {
        ComicProvider *provider = new CachedProvider(this, KPluginMetaData{}, reason, IdentifierType::StringIdentifier, identifier);
        provider->setIsCurrent(isCurrentComic);
        m_jobs[identifier] = provider;
        connect(provider, &ComicProvider::finished, this, &ComicEngine::finished);
        connect(provider, &ComicProvider::error, this, &ComicEngine::error);
        return true;
    }
    // Struct with just error=true is fine!
    QT_WARNING_PUSH
    QT_WARNING_DISABLE_GCC("-Wmissing-designated-field-initializers");
    QT_WARNING_DISABLE_CLANG("-Wmissing-designated-field-initializers");
    // ... start a new query otherwise
    if (parts.count() < 2) {
        Q_EMIT requestFinished(ComicMetaData{.reason = reason, .error = true});
        qCWarning(PLASMA_COMIC) << "Less than two arguments specified.";
        return false;
    }
    if (!mProviders.contains(parts[0])) {
        // User might have installed more from GHNS
        loadProviders();
        if (!mProviders.contains(parts[0])) {
            Q_EMIT requestFinished(ComicMetaData{.reason = reason, .error = true});
            qCWarning(PLASMA_COMIC) << identifier << "comic plugin does not seem to be installed.";
            return false;
        }
    }
    QT_WARNING_POP

    // check if there is a connection
    if (!isOnline()) {
        qCDebug(PLASMA_COMIC) << "Currently offline, requested identifier was" << mIdentifierError;
        mIdentifierError = identifier;
        ComicMetaData data;
        data.error = true;
        data.errorAutomaticallyFixable = true;
        data.identifier = identifier;
        data.previousIdentifier = lastCachedIdentifier(identifier);
        Q_EMIT requestFinished(data);
        qCDebug(PLASMA_COMIC) << "No internet connection, using cached data";
        return true;
    }

    KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Comic"), parts[0]);

    ComicProvider *provider = nullptr;

    QVariant data;
    const IdentifierType identifierType = stringToIdentifierType(pkg.metadata().value(u"X-KDE-PlasmaComicProvider-SuffixType"));
    if (identifierType == IdentifierType::DateIdentifier) {
        QDate date = QDate::fromString(parts[1], Qt::ISODate);
        if (!date.isValid()) {
            date = QDate::currentDate();
        }

        data = date;
    } else if (identifierType == IdentifierType::NumberIdentifier) {
        data = parts[1].toInt();
    } else if (identifierType == IdentifierType::StringIdentifier) {
        data = parts[1];
    }
    provider = new ComicProviderKross(this, pkg.metadata(), reason, identifierType, data);
    provider->setIsCurrent(isCurrentComic);

    m_jobs[identifier] = provider;

    connect(provider, &ComicProvider::finished, this, &ComicEngine::finished);
    connect(provider, &ComicProvider::error, this, &ComicEngine::error);
    return true;
}

void ComicEngine::finished(ComicProvider *provider)
{
    // sets the data
    if (provider->image().isNull()) {
        qCWarning(PLASMA_COMIC) << "Provider returned null image" << provider->name();
        error(provider);
        return;
    }

    ComicMetaData data = metaDataFromProvider(provider);

    // different comic -- with no error yet -- has been chosen, old error is invalidated
    QString temp = mIdentifierError.left(mIdentifierError.indexOf(QLatin1Char(':')) + 1);
    if (!mIdentifierError.isEmpty() && provider->identifier().indexOf(temp) == -1) {
        mIdentifierError.clear();
    }
    // comic strip with error worked now
    if (!mIdentifierError.isEmpty() && (mIdentifierError == provider->identifier())) {
        mIdentifierError.clear();
    }

    if (provider->isCurrent()) {
        data.lastCachedStripIdentifier = provider->identifier().mid(provider->identifier().indexOf(QLatin1Char(':')) + 1);
    } else {
        data.lastCachedStripIdentifier = lastCachedIdentifier(provider->identifier());
    }

    // store in cache if it's not the response of a CachedProvider,
    if (!provider->inherits("CachedProvider") && !provider->image().isNull()) {
        CachedProvider::storeInCache(provider->identifier(), provider->image(), data);
        if (provider->isCurrent()) {
            QString currentIdentifier = provider->identifier().left(provider->identifier().indexOf(QLatin1Char(':')) + 1);
            CachedProvider::storeInCache(currentIdentifier, provider->image(), data);
        }
    }

    if (provider->inherits("CachedProvider")) {
        // but if it is, we need to re-read the identifierType, or stripselector picks the wrong dialog
        QString providerName = data.identifier.mid(0, data.identifier.indexOf(QLatin1Char(':')));
        KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Comic"), providerName);
        data.identifierType = stringToIdentifierType(pkg.metadata().value(u"X-KDE-PlasmaComicProvider-SuffixType"));
    }
    provider->deleteLater();

    const QString key = m_jobs.key(provider);
    if (!key.isEmpty()) {
        m_jobs.remove(key);
    }
    Q_EMIT requestFinished(data);
}

void ComicEngine::error(ComicProvider *provider)
{
    QString identifier(provider->identifier());
    mIdentifierError = identifier;

    qCWarning(PLASMA_COMIC) << identifier << "plugin reported an error.";

    ComicMetaData data = metaDataFromProvider(provider);
    data.error = true;

    // if there was an error loading the last cached comic strip, do not return its id anymore
    const QString lastCachedId = lastCachedIdentifier(identifier);
    bool hasPreviousIdentifier = data.previousIdentifier.length() > 0;
    if (!hasPreviousIdentifier && lastCachedId != provider->identifier().mid(provider->identifier().indexOf(QLatin1Char(':')) + 1)) {
        // sets the previousIdentifier to the identifier of a strip that has been cached before
        data.previousIdentifier = lastCachedId;
    }

    const QString key = m_jobs.key(provider);
    if (!key.isEmpty()) {
        m_jobs.remove(key);
    }

    provider->deleteLater();
    Q_EMIT requestFinished(data);
}

ComicMetaData ComicEngine::metaDataFromProvider(ComicProvider *provider)
{
    QString identifier(provider->identifier());

    /**
     * Requests for the current day have no suffix (date or id)
     * set initially, so we have to remove the 'faked' suffix
     * here again to not confuse the applet.
     */
    if (provider->isCurrent()) {
        identifier = identifier.left(identifier.indexOf(QLatin1Char(':')) + 1);
    }

    ComicMetaData data;
    data.imageUrl = provider->imageUrl();
    data.image = provider->image();
    data.websiteUrl = provider->websiteUrl();
    data.shopUrl = provider->shopUrl();
    data.nextIdentifier = provider->nextIdentifier();
    data.previousIdentifier = provider->previousIdentifier();
    data.comicAuthor = provider->comicAuthor();
    data.additionalText = provider->additionalText();
    data.stripTitle = provider->stripTitle();
    data.firstStripIdentifier = provider->firstStripIdentifier();
    data.identifier = identifier;
    data.providerName = provider->name();
    data.identifierType = provider->identifierType();
    data.isLeftToRight = provider->isLeftToRight();
    data.isTopToBottom = provider->isTopToBottom();
    data.reason = provider->reason();

    return data;
}

QString ComicEngine::lastCachedIdentifier(const QString &identifier) const
{
    const QString id = identifier.left(identifier.indexOf(QLatin1Char(':')));
    QString data = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/plasma_engine_comic/");
    data += QString::fromLatin1(QUrl::toPercentEncoding(id));
    QSettings settings(data + QLatin1String(".conf"), QSettings::IniFormat);
    QString previousIdentifier = settings.value(QLatin1String("lastCachedStripIdentifier"), QString()).toString();

    return previousIdentifier;
}

bool ComicEngine::isOnline() const
{
    const auto instance = QNetworkInformation::instance();
    return (instance && instance->reachability() == QNetworkInformation::Reachability::Online);
}
