/*
 *   Copyright © 2008 Rob Scheepmaker <r.scheepmaker@student.utwente.nl>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Library General Public License version 2 as
 *   published by the Free Software Foundation
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "jobviewadaptor.h"
#include "jobviewserveradaptor.h"
#include "kuiserverengine.h"
#include "jobcontrol.h"

#include <QDBusConnection>

#include <KJob>

#include <Plasma/DataEngine>


uint KuiserverEngine::s_jobId = 0;

static const int UPDATE_INTERVAL = 100;

JobView::JobView(QObject* parent)
    : Plasma::DataContainer(parent),
      m_capabilities(-1),
      m_percent(0),
      m_speed(0),
      m_totalBytes(0),
      m_processedBytes(0),
      m_state(UnknownState),
      m_bytesUnitId(-1),
      m_unitId(0)
{
    m_jobId = ++KuiserverEngine::s_jobId;
    setObjectName(QString("Job %1").arg(KuiserverEngine::s_jobId));

    new JobViewV2Adaptor(this);

    m_objectPath.setPath(QString("/DataEngine/applicationjobs/JobView_%1").arg(m_jobId));
    QDBusConnection::sessionBus().registerObject(m_objectPath.path(), this);

    setSuspended(false);
}

JobView::~JobView()
{
    QDBusConnection::sessionBus().unregisterObject(m_objectPath.path(), QDBusConnection::UnregisterTree);
}

uint JobView::jobId() const
{
    return m_jobId;
}

void JobView::scheduleUpdate()
{
    if (!m_updateTimer.isActive()) {
        m_updateTimer.start(UPDATE_INTERVAL, this);
    }
}

void JobView::timerEvent(QTimerEvent *event)
{
    if (event->timerId() == m_updateTimer.timerId()) {
        m_updateTimer.stop();
        checkForUpdate();

        if (m_state == Stopped) {
            emit becameUnused(objectName());
        }
    } else {
        Plasma::DataContainer::timerEvent(event);
    }
}

void JobView::terminate(const QString &errorMessage)
{
    setData("error", errorMessage);
    QTimer::singleShot(0, this, SLOT(finished()));
}

void JobView::finished()
{
    if (m_state != Stopped) {
        m_state = Stopped;
        setData("state", "stopped");
        setData("speed", QVariant());
        scheduleUpdate();
    }
}

JobView::State JobView::state()
{
    return m_state;
}

void JobView::setSuspended(bool suspended)
{
    if (suspended) {
        if (m_state != Suspended) {
            m_state = Suspended;
            setData("state", "suspended");
            setData("speed", QVariant());
            scheduleUpdate();
        }
    } else if (m_state != Running) {
        m_state = Running;
        setData("state", "running");
        setData("speed", speedString());
        scheduleUpdate();
    }
}

int JobView::unitId(const QString &unit)
{
    int id = 0;
    if (m_unitMap.contains(unit)) {
        id = m_unitMap.value(unit);
    } else {
        id = m_unitId;
        setData(QString("totalUnit%1").arg(id), unit);
        setData(QString("totalAmount%1").arg(id), 0);
        setData(QString("processedUnit%1").arg(id), unit);
        setData(QString("processedAmount%1").arg(id), 0);
        m_unitMap.insert(unit, m_unitId);

        if (unit == "bytes") {
            m_bytesUnitId = id;
        }

        ++m_unitId;
        scheduleUpdate();
    }

    return id;
}

void JobView::updateEta()
{
    if (m_speed < 1) {
        setData("eta", 0);
        return;
    }

    if (m_totalBytes < 1) {
        setData("eta", 0);
        return;
    }

    const qlonglong remaining = 1000 * (m_totalBytes - m_processedBytes);
    setData("eta", remaining / m_speed);
}

void JobView::setTotalAmount(qlonglong amount, const QString &unit)
{
    const int id = unitId(unit);
    const QString amountString = QString("totalAmount%1").arg(id);
    const qlonglong prevTotal = data().value(amountString).toLongLong();
    if (prevTotal != amount) {
        if (id == m_bytesUnitId) {
            m_totalBytes = amount;
            updateEta();
        }

        setData(amountString, amount);
        scheduleUpdate();
    }
}

void JobView::setProcessedAmount(qlonglong amount, const QString &unit)
{
    const int id = unitId(unit);
    const QString processedString = QString("processedAmount%1").arg(id);
    const qlonglong prevTotal = data().value(processedString).toLongLong();
    if (prevTotal != amount) {
        if (id == m_bytesUnitId) {
            m_processedBytes = amount;
            updateEta();
        }

        setData(processedString, amount);
        scheduleUpdate();
    }
}

void JobView::setDestUrl(const QDBusVariant & destUrl)
{
    Q_UNUSED(destUrl);
}

void JobView::setPercent(uint percent)
{
    if (m_percent != percent) {
        m_percent = percent;
        setData("percentage", m_percent);
        scheduleUpdate();
    }
}

void JobView::setSpeed(qlonglong bytesPerSecond)
{
    if (m_speed != bytesPerSecond) {
        m_speed = bytesPerSecond;
        setData("speed", speedString());

        if (m_bytesUnitId > -1) {
            updateEta();
        }

        scheduleUpdate();
    }
}

QString JobView::speedString() const
{
    return i18nc("Byes per second", "%1/s", KGlobal::locale()->formatByteSize(m_speed));
}

void JobView::setInfoMessage(const QString &infoMessage)
{
    if (data().value("infoMessage") != infoMessage) {
        setData("infoMessage", infoMessage);
        scheduleUpdate();
    }
}

bool JobView::setDescriptionField(uint number, const QString &name, const QString &value)
{
    const QString labelString = QString("label%1").arg(number);
    const QString labelNameString = QString("labelName%1").arg(number);

    if (!data().contains(labelNameString) || data().value(labelString) != value) {
        setData(labelNameString, name);
        setData(labelString, value);
        scheduleUpdate();
    }
    return true;
}

void JobView::clearDescriptionField(uint number)
{
    const QString labelString = QString("label%1").arg(number);
    const QString labelNameString = QString("labelName%1").arg(number);

    setData(labelNameString, QVariant());
    setData(labelString, QVariant());
    scheduleUpdate();
}

void JobView::setAppName(const QString &appName)
{
    // don't need to update, this is only set once at creation
    setData("appName", appName);
}

void JobView::setAppIconName(const QString &appIconName)
{
    // don't need to update, this is only set once at creation
    setData("appIconName", appIconName);
}

void JobView::setCapabilities(int capabilities)
{
    if (m_capabilities != uint(capabilities)) {
        m_capabilities = capabilities;
        setData("suspendable", m_capabilities & KJob::Suspendable);
        setData("killable", m_capabilities & KJob::Killable);
        scheduleUpdate();
    }
}

QDBusObjectPath JobView::objectPath() const
{
    return m_objectPath;
}

void JobView::requestStateChange(State state)
{
    switch (state) {
        case Running:
            emit resumeRequested();
            break;
        case Suspended:
            emit suspendRequested();
            break;
        case Stopped:
            emit cancelRequested();
            break;
        default:
            break;
    }
}

KuiserverEngine::KuiserverEngine(QObject* parent, const QVariantList& args)
    : Plasma::DataEngine(parent, args)
{
    new JobViewServerAdaptor(this);

    QDBusConnection bus = QDBusConnection::sessionBus();
    bus.registerObject(QLatin1String("/DataEngine/applicationjobs/JobWatcher"), this);

    setMinimumPollingInterval(500);

    m_pendingJobsTimer.setSingleShot(true);
    m_pendingJobsTimer.setInterval(500);
    connect(&m_pendingJobsTimer, SIGNAL(timeout()), this, SLOT(processPendingJobs()));
}

KuiserverEngine::~KuiserverEngine()
{
    QDBusConnection::sessionBus()
    .unregisterObject(QLatin1String("/DataEngine/applicationjobs/JobWatcher"), QDBusConnection::UnregisterTree);

    qDeleteAll(m_pendingJobs);
}

QDBusObjectPath KuiserverEngine::requestView(const QString &appName,
                                             const QString &appIconName, int capabilities)
{
    JobView *jobView = new JobView(this);
    jobView->setAppName(appName);
    jobView->setAppIconName(appIconName);
    jobView->setCapabilities(capabilities);
    connect(jobView, SIGNAL(becameUnused(QString)), this, SLOT(removeSource(QString)));

    m_pendingJobs << jobView;
    m_pendingJobsTimer.start();

    return jobView->objectPath();
}

void KuiserverEngine::processPendingJobs()
{
    foreach (JobView *jobView, m_pendingJobs) {
        if (jobView->state() == JobView::Stopped) {
            delete jobView;
        } else {
            addSource(jobView);
        }
    }

    m_pendingJobs.clear();
}

Plasma::Service* KuiserverEngine::serviceForSource(const QString& source)
{
    JobView *jobView = qobject_cast<JobView *>(containerForSource(source));
    if (jobView) {
        return new JobControl(this, jobView);
    } else {
        return DataEngine::serviceForSource(source);
    }
}

void KuiserverEngine::init()
{
    QDBusInterface interface("org.kde.kuiserver", "/JobViewServer"/* object to connect to */,
                              ""/* use the default interface */, QDBusConnection::sessionBus(), this);
    interface.asyncCall("registerService", QDBusConnection::sessionBus().baseService(), "/DataEngine/applicationjobs/JobWatcher");
}

K_EXPORT_PLASMA_DATAENGINE(kuiserver, KuiserverEngine)

#include "kuiserverengine.moc"
