/*
    SPDX-FileCopyrightText: 2006 Volker Krause <vkrause@kde.org>

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

#include "akonadislave.h"

#include <Akonadi/Collection>
#include <Akonadi/CollectionDeleteJob>
#include <Akonadi/CollectionFetchJob>
#include <Akonadi/EntityDisplayAttribute>
#include <Akonadi/ItemDeleteJob>
#include <Akonadi/ItemFetchJob>
#include <Akonadi/ItemFetchScope>

#include "akonadislave_debug.h"

#include <KAboutData>
#include <KLocalizedString>
#include <QApplication>
#include <QCommandLineOption>
#include <QCommandLineParser>

#ifdef Q_OS_WIN
// see kio/core/src/kioglobal_p.h
#define S_IRUSR 0400
#define S_IRGRP 0040
#define S_IROTH 0004
#endif

// Pseudo plugin class to embed meta data
class KIOPluginForMetaData : public QObject
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.kde.kio.slave.akonadi" FILE "akonadi.json")
};

extern "C" {
int Q_DECL_EXPORT kdemain(int argc, char **argv);
}

int kdemain(int argc, char **argv)
{
    QApplication app(argc, argv);
    KAboutData aboutData(QStringLiteral("kio_akonadi"), QString(), QStringLiteral("0"));
    QCommandLineParser parser;
    KAboutData::setApplicationData(aboutData);
    parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("+protocol"), i18n("Protocol name")));
    parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("+pool"), i18n("Socket name")));
    parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("+app"), i18n("Socket name")));

    aboutData.setupCommandLine(&parser);
    parser.process(app);
    aboutData.processCommandLine(&parser);

    AkonadiSlave slave(parser.positionalArguments().at(1).toLocal8Bit(), parser.positionalArguments().at(2).toLocal8Bit());
    slave.dispatchLoop();

    return 0;
}

using namespace Akonadi;

AkonadiSlave::AkonadiSlave(const QByteArray &pool_socket, const QByteArray &app_socket)
    : KIO::SlaveBase("akonadi", pool_socket, app_socket)
{
    qCDebug(AKONADISLAVE_LOG) << "kio_akonadi starting up";
}

AkonadiSlave::~AkonadiSlave()
{
    qCDebug(AKONADISLAVE_LOG) << "kio_akonadi shutting down";
}

void AkonadiSlave::get(const QUrl &url)
{
    const Item item = Item::fromUrl(url);
    auto job = new ItemFetchJob(item);
    job->fetchScope().fetchFullPayload();

    if (!job->exec()) {
        error(KIO::ERR_INTERNAL, job->errorString());
        return;
    }

    if (job->items().count() != 1) {
        error(KIO::ERR_DOES_NOT_EXIST, i18n("No such item."));
    } else {
        const Item itemJob = job->items().at(0);
        const QByteArray tmp = itemJob.payloadData();
        data(tmp);
        data(QByteArray());
        finished();
    }
}

void AkonadiSlave::stat(const QUrl &url)
{
    qCDebug(AKONADISLAVE_LOG) << url;

    // Stats for a collection
    if (Collection::fromUrl(url).isValid()) {
        Collection collection = Collection::fromUrl(url);

        if (collection != Collection::root()) {
            // Check that the collection exists.
            auto job = new CollectionFetchJob(collection, CollectionFetchJob::Base);
            if (!job->exec()) {
                error(KIO::ERR_INTERNAL, job->errorString());
                return;
            }

            if (job->collections().count() != 1) {
                error(KIO::ERR_DOES_NOT_EXIST, i18n("No such item."));
                return;
            }

            collection = job->collections().at(0);
        }

        statEntry(entryForCollection(collection));
        finished();
    }
    // Stats for an item
    else if (Item::fromUrl(url).isValid()) {
        auto job = new ItemFetchJob(Item::fromUrl(url));

        if (!job->exec()) {
            error(KIO::ERR_INTERNAL, job->errorString());
            return;
        }

        if (job->items().count() != 1) {
            error(KIO::ERR_DOES_NOT_EXIST, i18n("No such item."));
            return;
        }

        const Item item = job->items().at(0);
        statEntry(entryForItem(item));
        finished();
    }
}

void AkonadiSlave::del(const QUrl &url, bool isFile)
{
    qCDebug(AKONADISLAVE_LOG) << url;

    if (!isFile) { // It's a directory
        Collection collection = Collection::fromUrl(url);
        auto job = new CollectionDeleteJob(collection);
        if (!job->exec()) {
            error(KIO::ERR_INTERNAL, job->errorString());
            return;
        }
        finished();
    } else { // It's a file
        auto job = new ItemDeleteJob(Item::fromUrl(url));
        if (!job->exec()) {
            error(KIO::ERR_INTERNAL, job->errorString());
            return;
        }
        finished();
    }
}

void AkonadiSlave::listDir(const QUrl &url)
{
    qCDebug(AKONADISLAVE_LOG) << url;

    if (!Collection::fromUrl(url).isValid()) {
        error(KIO::ERR_DOES_NOT_EXIST, i18n("No such collection."));
        return;
    }

    // Fetching collections
    Collection collection = Collection::fromUrl(url);
    if (!collection.isValid()) {
        error(KIO::ERR_DOES_NOT_EXIST, i18n("No such collection."));
        return;
    }
    auto job = new CollectionFetchJob(collection, CollectionFetchJob::FirstLevel);
    if (!job->exec()) {
        error(KIO::ERR_CANNOT_ENTER_DIRECTORY, job->errorString());
        return;
    }

    const Collection::List collections = job->collections();
    for (const Collection &col : collections) {
        listEntry(entryForCollection(col));
    }

    // Fetching items
    if (collection != Collection::root()) {
        auto fjob = new ItemFetchJob(collection);
        if (!fjob->exec()) {
            error(KIO::ERR_INTERNAL, job->errorString());
            return;
        }
        const Item::List items = fjob->items();
        totalSize(collections.count() + items.count());
        for (const Item &item : items) {
            listEntry(entryForItem(item));
        }
    }

    finished();
}

KIO::UDSEntry AkonadiSlave::entryForItem(const Akonadi::Item &item)
{
    KIO::UDSEntry entry;
    entry.reserve(7);
    entry.fastInsert(KIO::UDSEntry::UDS_NAME, QString::number(item.id()));
    entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, item.mimeType());
    entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
    entry.fastInsert(KIO::UDSEntry::UDS_URL, item.url().url());
    entry.fastInsert(KIO::UDSEntry::UDS_SIZE, item.size());
    entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH);
    entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, item.modificationTime().toSecsSinceEpoch());
    return entry;
}

KIO::UDSEntry AkonadiSlave::entryForCollection(const Akonadi::Collection &collection)
{
    KIO::UDSEntry entry;
    entry.reserve(7);
    entry.fastInsert(KIO::UDSEntry::UDS_NAME, collection.name());
    entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, Collection::mimeType());
    entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
    entry.fastInsert(KIO::UDSEntry::UDS_URL, collection.url().url());
    entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH);
    if (const auto attr = collection.attribute<EntityDisplayAttribute>()) {
        if (!attr->iconName().isEmpty()) {
            entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, attr->iconName());
        }
        if (!attr->displayName().isEmpty()) {
            entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, attr->displayName());
        }
    }
    return entry;
}

#include "akonadislave.moc"
