/*
 * Copyright (c) 2004 David Faure <faure@kde.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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 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.
 *
 *  In addition, as a special exception, the copyright holders give
 *  permission to link the code of this program with any edition of
 *  the Qt library by Trolltech AS, Norway (or with modified versions
 *  of Qt that use the same license as Qt), and distribute linked
 *  combinations including the two.  You must obey the GNU General
 *  Public License in all respects for all of the code used other than
 *  Qt.  If you modify this file, you may extend this exception to
 *  your version of the file, but you are not obligated to do so.  If
 *  you do not wish to do so, delete this exception statement from
 *  your version.
 */

#ifndef MAILCOMMON_JOBSCHEDULER_H
#define MAILCOMMON_JOBSCHEDULER_H

#include "mailcommon_export.h"

#include <QObject>

#include <QTimer>
#include <QVector>

#include "folderjob.h"
#include <collection.h>
// If this define is set, JobScheduler will show debug output, and related kmkernel timers will be shortened
// This is for debugging purposes only, don't commit with it.
//#define DEBUG_SCHEDULER

namespace MailCommon {
class FolderJob;
class ScheduledJob;

/**
 * A scheduled task is some information about a folder job that should be run later.
 * As long as it's not running, it's called a "task", i.e. something that needs to be done.
 * Tasks are held in the JobScheduler.
 */
class MAILCOMMON_EXPORT ScheduledTask
{
public:
    /// Create a scheduled task for a given folder
    /// If @p immediate is true, the scheduler will run this task as soon
    /// as possible (but won't interrupt a currently running job for it)
    ScheduledTask(const Akonadi::Collection &folder, bool immediate);
    virtual ~ScheduledTask();

    /// Run this task, i.e. create a job for it.
    /// Important: the job's execute() method must either call open() on the
    /// folder or storage immediately, or abort (deleting itself).
    /// Usually, that job should also be cancellable.
    /// Otherwise (if the open() is delayed) an unrelated open() could happen first
    /// and mess things up.
    /// If for some reason (e.g. folder deleted) nothing should be done, return 0.
    virtual ScheduledJob *run() = 0;

    /// An identifier for the type of task (a bit like QListViewItem::rtti)
    /// This allows to automatically prevent two identical tasks from being scheduled
    /// for the same folder. To circumvent this feature and make every task
    /// unique, return 0 here.
    virtual int taskTypeId() const = 0;

    /// The folder which this task is about, 0 if it was deleted meanwhile.
    Q_REQUIRED_RESULT Akonadi::Collection folder() const
    {
        return mCurrentFolder;
    }

    Q_REQUIRED_RESULT bool isImmediate() const
    {
        return mImmediate;
    }

private:
    Akonadi::Collection mCurrentFolder;
    bool mImmediate;
};

/**
 * The unique JobScheduler instance (owned by kmkernel) implements "background processing"
 * of folder operations (like expiration and compaction). Tasks (things to be done)
 * are registered with the JobScheduler, and it will execute them one at a time,
 * separated with a 1-minute timer. The jobs themselves should use timers to avoid
 * using too much CPU for too long. Tasks for opened folders are not executed until
 * the folder is closed.
 */
class MAILCOMMON_EXPORT JobScheduler : public QObject
{
    Q_OBJECT
public:
    explicit JobScheduler(QObject *parent);
    ~JobScheduler();

    /// Register a task to be done for a given folder
    /// The ownership of the task is transferred to the JobScheduler
    void registerTask(ScheduledTask *task);

    // D-Bus calls, called from KMKernel
    void pause();
    void resume();

private:
    /// Called by a timer to run the next job
    void slotRunNextJob();

    /// Called when the current job terminates
    void slotJobFinished();
    void restartTimer();
    void interruptCurrentTask();
    void runTaskNow(ScheduledTask *task);
    typedef QVector<ScheduledTask *> TaskList;
    void removeTask(TaskList::Iterator &it);
private:
    TaskList mTaskList; // FIFO of tasks to be run

    QTimer mTimer;
    int mPendingImmediateTasks = 0;

    /// Information about the currently running job, if any
    ScheduledTask *mCurrentTask = nullptr;
    ScheduledJob *mCurrentJob = nullptr;
};

/**
 * Base class for scheduled jobs
 */
class MAILCOMMON_EXPORT ScheduledJob : public FolderJob
{
public:
    ScheduledJob(const Akonadi::Collection &folder, bool immediate);
    ~ScheduledJob();

protected:
    bool mImmediate;
};
}

#endif
