/*
    SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de>

    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/

#include "jobsmodel.h"
#include "jobsmodel_p.h"

#include "utils_p.h"

#include <QDebug>

#include <KJob>
#include <KLocalizedString>

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

using namespace NotificationManager;

JobsModel::JobsModel()
    : QAbstractListModel(nullptr)
    , d(new JobsModelPrivate(this))
{
    connect(d, &JobsModelPrivate::jobViewAboutToBeAdded, this, [this](int row, Job *job) {
        Q_UNUSED(job);
        beginInsertRows(QModelIndex(), row, row);
    });
    connect(d, &JobsModelPrivate::jobViewAdded, this, [this](int row) {
        Q_UNUSED(row);
        endInsertRows();
    });

    connect(d, &JobsModelPrivate::jobViewAboutToBeRemoved, this, [this](int row) {
        beginRemoveRows(QModelIndex(), row, row);
    });
    connect(d, &JobsModelPrivate::jobViewRemoved, this, [this](int row) {
        Q_UNUSED(row);
        endRemoveRows();
    });

    connect(d, &JobsModelPrivate::jobViewChanged, this, [this](int row, Job *job, const QList<int> &roles) {
        Q_UNUSED(job);
        const QModelIndex idx = index(row, 0);
        Q_EMIT dataChanged(idx, idx, roles);
    });

    connect(d, &JobsModelPrivate::serviceOwnershipLost, this, &JobsModel::serviceOwnershipLost);
}

JobsModel::~JobsModel() = default;

JobsModel::Ptr JobsModel::createJobsModel()
{
    static std::weak_ptr<JobsModel> s_instance;
    if (s_instance.expired()) {
        std::shared_ptr<JobsModel> ptr(new JobsModel());
        s_instance = ptr;
        return ptr;
    }
    return s_instance.lock();
}

bool JobsModel::init()
{
    return d->init();
}

bool JobsModel::isValid() const
{
    return d->m_valid;
}

QVariant JobsModel::data(const QModelIndex &index, int role) const
{
    if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
        return QVariant();
    }

    Job *job = d->m_jobViews.at(index.row());

    switch (role) {
    case Notifications::IdRole:
        return job->id();
    case Notifications::TypeRole:
        return Notifications::JobType;
    // basically when it started
    case Notifications::CreatedRole:
        if (job->created().isValid()) {
            return job->created();
        }
        break;
    // basically when it finished
    case Notifications::UpdatedRole:
        if (job->updated().isValid()) {
            return job->updated();
        }
        break;
    case Notifications::SummaryRole:
        return job->summary();
    case Notifications::BodyRole:
        return job->text();
    case Qt::AccessibleDescriptionRole:
        return i18nc("@info %1 notification body %2 job name", "%1 from %2", job->text(), job->applicationName());
    case Notifications::DesktopEntryRole:
        return job->desktopEntry();
    case Notifications::ApplicationNameRole:
        return job->applicationName();
    case Notifications::ApplicationIconNameRole:
        return job->applicationIconName();

    case Notifications::JobStateRole:
        return job->state();
    case Notifications::PercentageRole:
        return job->percentage();
    case Notifications::JobErrorRole:
        return job->error();
    case Notifications::SuspendableRole:
        return job->suspendable();
    case Notifications::KillableRole:
        return job->killable();
    case Notifications::JobDetailsRole:
        return QVariant::fromValue(job);

    // successfully finished jobs timeout like a regular notifiation
    // whereas running or error'd jobs are persistent
    case Notifications::TimeoutRole:
        return job->state() == Notifications::JobStateStopped && !job->error() ? -1 : 0;
    case Notifications::ClosableRole:
        return job->state() == Notifications::JobStateStopped;

    case Notifications::ConfigurableRole:
        return false;
    case Notifications::ExpiredRole:
        return job->expired();
    case Notifications::DismissedRole:
        return job->dismissed();

    // A job is usually either a long lasting operation you're aware about
    // or a quick job you don't care about.
    // When it's running, it's there, when it failed, it's persistent.
    // There's hardly a reason why it should show up as "unread".
    case Notifications::ReadRole:
        return true;
    }

    return QVariant();
}

bool JobsModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
        return false;
    }

    Job *job = d->m_jobViews.at(index.row());

    switch (role) {
    case Notifications::DismissedRole:
        if (value.toBool() != job->dismissed()) {
            job->setDismissed(value.toBool());
            return true;
        }
        break;
    }

    return false;
}

int JobsModel::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid()) {
        return 0;
    }

    return d->m_jobViews.count();
}

QHash<int, QByteArray> JobsModel::roleNames() const
{
    return Utils::roleNames();
}

void JobsModel::close(const QModelIndex &idx)
{
    if (checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
        d->removeAt(idx.row());
    }
}

void JobsModel::expire(const QModelIndex &idx)
{
    if (checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
        d->m_jobViews.at(idx.row())->setExpired(true);
    }
}

void JobsModel::suspend(const QModelIndex &idx)
{
    if (checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
        d->m_jobViews.at(idx.row())->suspend();
    }
}

void JobsModel::resume(const QModelIndex &idx)
{
    if (checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
        d->m_jobViews.at(idx.row())->resume();
    }
}

void JobsModel::kill(const QModelIndex &idx)
{
    if (checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
        d->m_jobViews.at(idx.row())->kill();
    }
}

void JobsModel::clear(Notifications::ClearFlags flags)
{
    if (d->m_jobViews.isEmpty()) {
        return;
    }

    for (int i = d->m_jobViews.count() - 1; i >= 0; --i) {
        Job *job = d->m_jobViews.at(i);

        bool clear = (flags.testFlag(Notifications::ClearExpired) && job->expired());

        // Compared to notifications, the number of jobs is typically small
        // so for simplicity we can just delete one item at a time
        if (clear) {
            d->removeAt(i);
        }
    }
}
