/*
    This file is part of the KDE libraries
    SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
    SPDX-FileCopyrightText: 2000-2009 David Faure <faure@kde.org>
    SPDX-FileCopyrightText: 2000-2009 Waldo Bastian <bastian@kde.org>

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

#include "job.h"
#include "job_p.h"

#include <time.h>

#include <KLocalizedString>
#include <KStringHandler>

#include "worker_p.h"
#include <kio/jobuidelegateextension.h>

using namespace Qt::StringLiterals;
using namespace KIO;

Job::Job()
    : KCompositeJob(nullptr)
    , d_ptr(new JobPrivate)
{
    d_ptr->q_ptr = this;
    setCapabilities(KJob::Killable | KJob::Suspendable);
}

Job::Job(JobPrivate &dd)
    : KCompositeJob(nullptr)
    , d_ptr(&dd)
{
    d_ptr->q_ptr = this;
    setCapabilities(KJob::Killable | KJob::Suspendable);
}

Job::~Job()
{
    delete d_ptr;
}

JobUiDelegateExtension *Job::uiDelegateExtension() const
{
    Q_D(const Job);
    return d->m_uiDelegateExtension;
}

void Job::setUiDelegateExtension(JobUiDelegateExtension *extension)
{
    Q_D(Job);
    d->m_uiDelegateExtension = extension;
}

bool Job::addSubjob(KJob *jobBase)
{
    // qDebug() << "addSubjob(" << jobBase << ") this=" << this;

    bool ok = KCompositeJob::addSubjob(jobBase);
    KIO::Job *job = qobject_cast<KIO::Job *>(jobBase);
    if (ok && job) {
        // Copy metadata into subjob (e.g. window-id, user-timestamp etc.)
        Q_D(Job);
        job->mergeMetaData(d->m_outgoingMetaData);

        // Forward information from that subjob.
        connect(job, &KJob::speed, this, [this](KJob *job, ulong speed) {
            Q_UNUSED(job);
            emitSpeed(speed);
        });
        // Forwarding invalid properties is not free. For 1 million files we'd be paying about 100ms. Only forward
        // when actually useful.
        const auto dynamicProperties = dynamicPropertyNames();
        for (const auto &name : {"widget"_ba, "window"_ba, "userTimestamp"_ba}) { // see KJobWidgets
            if (dynamicProperties.contains(name) || metaObject()->indexOfProperty(name.constData()) != -1) {
                // known property, forward it
                job->setProperty(name.constData(), property(name.constData()));
            }
        }
        job->setUiDelegateExtension(d->m_uiDelegateExtension);
    }
    return ok;
}

bool Job::removeSubjob(KJob *jobBase)
{
    // qDebug() << "removeSubjob(" << jobBase << ") this=" << this << "subjobs=" << subjobs().count();
    return KCompositeJob::removeSubjob(jobBase);
}

static QString url_description_string(const QUrl &url)
{
    return url.scheme() == QLatin1String("data") ? QStringLiteral("data:[...]") : KStringHandler::csqueeze(url.toDisplayString(QUrl::PreferLocalFile), 100);
}

KIO::JobPrivate::~JobPrivate()
{
}

void JobPrivate::emitMoving(KIO::Job *job, const QUrl &src, const QUrl &dest)
{
    static const QString s_title = i18nc("@title job", "Moving");
    static const QString s_source = i18nc("The source of a file operation", "Source");
    static const QString s_destination = i18nc("The destination of a file operation", "Destination");
    Q_EMIT job->description(job, s_title, qMakePair(s_source, url_description_string(src)), qMakePair(s_destination, url_description_string(dest)));
}

void JobPrivate::emitRenaming(KIO::Job *job, const QUrl &src, const QUrl &dest)
{
    static const QString s_title = i18nc("@title job", "Renaming");
    static const QString s_source = i18nc("The source of a file operation", "Source");
    static const QString s_destination = i18nc("The destination of a file operation", "Destination");
    Q_EMIT job->description(job, s_title, {s_source, url_description_string(src)}, {s_destination, url_description_string(dest)});
}

void JobPrivate::emitCopying(KIO::Job *job, const QUrl &src, const QUrl &dest)
{
    static const QString s_title = i18nc("@title job", "Copying");
    static const QString s_source = i18nc("The source of a file operation", "Source");
    static const QString s_destination = i18nc("The destination of a file operation", "Destination");
    Q_EMIT job->description(job, s_title, qMakePair(s_source, url_description_string(src)), qMakePair(s_destination, url_description_string(dest)));
}

void JobPrivate::emitCreatingDir(KIO::Job *job, const QUrl &dir)
{
    static const QString s_title = i18nc("@title job", "Creating directory");
    static const QString s_directory = i18n("Directory");
    Q_EMIT job->description(job, s_title, qMakePair(s_directory, url_description_string(dir)));
}

void JobPrivate::emitDeleting(KIO::Job *job, const QUrl &url)
{
    static const QString s_title = i18nc("@title job", "Deleting");
    static const QString s_file = i18n("File");
    Q_EMIT job->description(job, s_title, qMakePair(s_file, url_description_string(url)));
}

void JobPrivate::emitStating(KIO::Job *job, const QUrl &url)
{
    static const QString s_title = i18nc("@title job", "Examining");
    static const QString s_file = i18n("File");
    Q_EMIT job->description(job, s_title, qMakePair(s_file, url_description_string(url)));
}

void JobPrivate::emitTransferring(KIO::Job *job, const QUrl &url)
{
    static const QString s_title = i18nc("@title job", "Transferring");
    static const QString s_source = i18nc("The source of a file operation", "Source");
    Q_EMIT job->description(job, s_title, qMakePair(s_source, url_description_string(url)));
}

void JobPrivate::emitMounting(KIO::Job *job, const QString &dev, const QString &point)
{
    Q_EMIT job->description(job, i18nc("@title job", "Mounting"), qMakePair(i18n("Device"), dev), qMakePair(i18n("Mountpoint"), point));
}

void JobPrivate::emitUnmounting(KIO::Job *job, const QString &point)
{
    Q_EMIT job->description(job, i18nc("@title job", "Unmounting"), qMakePair(i18n("Mountpoint"), point));
}

bool Job::doKill()
{
    // kill all subjobs, without triggering their result slot
    for (KJob *job : subjobs()) {
        job->kill(KJob::Quietly);
    }
    clearSubjobs();

    return true;
}

bool Job::doSuspend()
{
    for (KJob *job : subjobs()) {
        if (!job->suspend()) {
            return false;
        }
    }

    return true;
}

bool Job::doResume()
{
    for (KJob *job : subjobs()) {
        if (!job->resume()) {
            return false;
        }
    }

    return true;
}

// Job::errorString is implemented in job_error.cpp

void Job::setParentJob(Job *job)
{
    Q_D(Job);
    Q_ASSERT(d->m_parentJob == nullptr);
    Q_ASSERT(job);
    d->m_parentJob = job;
}

Job *Job::parentJob() const
{
    return d_func()->m_parentJob;
}

MetaData Job::metaData() const
{
    return d_func()->m_incomingMetaData;
}

QString Job::queryMetaData(const QString &key)
{
    return d_func()->m_incomingMetaData.value(key, QString());
}

void Job::setMetaData(const KIO::MetaData &_metaData)
{
    Q_D(Job);
    d->m_outgoingMetaData = _metaData;
}

void Job::addMetaData(const QString &key, const QString &value)
{
    d_func()->m_outgoingMetaData.insert(key, value);
}

void Job::addMetaData(const QMap<QString, QString> &values)
{
    Q_D(Job);
    for (auto it = values.cbegin(); it != values.cend(); ++it) {
        d->m_outgoingMetaData.insert(it.key(), it.value());
    }
}

void Job::mergeMetaData(const QMap<QString, QString> &values)
{
    Q_D(Job);
    for (const auto &[key, value] : values.asKeyValueRange()) {
        // there's probably a faster way
        //   There is not. QMap lacks try_emplace API in Qt 6.11
        if (!d->m_outgoingMetaData.contains(key)) {
            d->m_outgoingMetaData.insert(key, value);
        }
    }
}

MetaData Job::outgoingMetaData() const
{
    return d_func()->m_outgoingMetaData;
}

//////////////////////////

class KIO::DirectCopyJobPrivate : public KIO::SimpleJobPrivate
{
public:
    DirectCopyJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs)
        : SimpleJobPrivate(url, command, packedArgs)
    {
    }

    /*!
     * \internal
     * Called by the scheduler when a @p worker gets to
     * work on this job.
     * \a worker the worker that starts working on this job
     */
    void start(Worker *worker) override;

    Q_DECLARE_PUBLIC(DirectCopyJob)
};

DirectCopyJob::DirectCopyJob(const QUrl &url, const QByteArray &packedArgs)
    : SimpleJob(*new DirectCopyJobPrivate(url, CMD_COPY, packedArgs))
{
    setUiDelegate(KIO::createDefaultJobUiDelegate());
}

DirectCopyJob::~DirectCopyJob()
{
}

void DirectCopyJobPrivate::start(Worker *worker)
{
    Q_Q(DirectCopyJob);
    q->connect(worker, &WorkerInterface::canResume, q, &DirectCopyJob::slotCanResume);
    SimpleJobPrivate::start(worker);
}

void DirectCopyJob::slotCanResume(KIO::filesize_t offset)
{
    Q_EMIT canResume(this, offset);
}

//////////////////////////

SimpleJob *KIO::file_delete(const QUrl &src, JobFlags flags)
{
    KIO_ARGS << src << qint8(true); // isFile
    SimpleJob *job = SimpleJobPrivate::newJob(src, CMD_DEL, packedArgs, flags);
    if (job->uiDelegateExtension()) {
        job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::RemoveContent);
    }
    return job;
}

//////////
////

#include "moc_job_base.cpp"
#include "moc_job_p.cpp"
