/* This file is part of the Chakra project

   Copyright (C) 2010 Dario Freddi <drf@chakra-project.org>
   Copyright (C) 2011 Lukas Appelhans <l.appelhans@gmx.de>

   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.
*/

#ifndef AKABEIOPERATION_H
#define AKABEIOPERATION_H

#include <QObject>

#include <akabeicore_global.h>
#include <akabeierror.h>

class QUuid;
class QStringList;

//FIXME: Do we need to make this thread-safe? Can it ever get access from 2 threads at the same time?
namespace Akabei {
class OperationPrivate;
/**
 * \class Operation akabeioperation.h "akabeioperation.h"
 *
 * \brief Base class for all operations.
 *
 * An \c Operation describes a single action we run during processing a queue or package.
 * This can e.g. be the download, installation or signature-checking of a package.
 * The \c Operation class is an abstract class which all operations need to base on. Those
 * custom operations can then be added to the \c OperationRunner and be run with OperationRunner::run().
 * Every operation belongs to a certain \c Phase, which basically means the point of execution. Each phase
 * is processed after each other.
 * The \c run and \c validate methods are the ones a developer needs to reimplement. The \c validate methods
 * checks whether the operation is ready to be run and possibly adds filesystemadditions etc. The \c run method
 * then does all the processing.
 *
 * \code
 * TestOperation::TestOperation(Akabei::Package *package)
 *   : Akabei::Operation(package->name())
 * {
 *   setPhase(Phase3);
 *   setPriority(50);
 *   setCanBeConcurrent(false);
 *   setTargetVersion(package->version().toByteArray());
 *   setDescription(QObject::tr("Testing %1...").arg(package->name()));
 * }
 *
 * void TestOperation::run()
 * {
 *    //Do stuff
 *    setFinished(true);
 * }
 *
 * void TestOperation::validate()
 * {
 *    //Test stuff
 *    setValidationFinished(true);
 * }
 * \endcode
 *
 * When you setFinished or setValidationFinished to false the processing of the whole
 * queue of operations will stop. Also note that there is a possibility to give further
 * information about the error via \c setErrors.
 *
 * Another thing which needs to be thought of when implementing an operation are the
 * \c processingOptions which are some flags set by the user (or the program used by the user).
 * This can e.g. contain the flag SkipDependencies, which means that the operation should not
 * care about possible target dependencies and thus should just not set them during the validation.
 *
 * @see setFinished
 * @see setValidationFinished
 * @see run
 * @see validate
 */
class Operation : public QObject
{
    friend class ArchiveHandler;

    Q_OBJECT
    Q_DISABLE_COPY(Operation)
    Q_DECLARE_PRIVATE(Operation)
public:
    /**
     * \class Operation::List Akabei/Operation/List
     * @typedef List
     * @brief   List of operations.
     */
    typedef QList<Operation*> List;

    /**
     * This enum describes certain phases an operation can be processed in.
     * The phase of the operation should be set in the constructor.
     * @see setPhase
     */
    enum Phase {
        NoPhase = 0,
        Phase1 = 1, /// Early preparation - download, stuff
        Phase2 = 2, /// Pre transaction stuff
        Phase3 = 3, /// Transaction stuff
        Phase4 = 4, /// Post transaction stuff
        Phase5 = 5  /// Late cleaning - clean cache, stuff.
    };

    /**
     * This enum describes the status the operation is current in.
     * The developer does not set the status directly, but just uses
     * \c setFinished and \c setValidationFinished.
     */
    enum Status {
        StatusIdle = 0, /// Ready to be validated
        StatusValidating = 1, /// The operation is being validated
        StatusReady = 2, /// The operation is ready to be run
        StatusNotReady = 3, /// Validation failed, the operation is not ready
        StatusRunning = 4, /// The operation is running
        StatusPerformed = 5, /// The operation has been performed
        StatusError = 6 /// There was an error while performing the operation
    };

    /**
     * The reason why an operation gets processed.
     */
    enum Reason {
        ReasonExplicit = 0, ///Operation explicitely needed, e.g. the user wants to install a certain package
        ReasonDependency = 1 ///Operation needed, as it's a dependency to an explicit operation, e.g. a package which gets installed as dependency
    };

    virtual ~Operation();

    /**
     * @returns a \c QUuid to identify the operation
     */
    QUuid uuid() const;

    /**
     * @returns the name of the package or target what is going to be processed
     */
    QString targetName() const;
    /**
     * @returns the version of the package or target which is going to be processed
     */
    QByteArray targetVersion() const;

    /**
     * @returns true when the operation can be executed concurrent
     */
    bool canBeConcurrent() const;

    /**
     * @returns a list of targets which the operation conflicts against.
     */
    QStringList conflictingTargets() const;

    /**
     * @returns the phase the operation will be executed in
     */
    Phase phase() const;
    /**
     * @returns the priority of the operation
     */
    int priority() const;

    /**
     * @returns the status of the operation
     */
    Status status() const;

    /**
     * @returns a list of files the operation is going to add to the filesystem
     */
    QStringList fileSystemAdditions() const;
    /**
     * @returns a list of files the operation is going to remove from the filesystem
     */
    QStringList fileSystemRemovals() const;

    /**
     * @returns a list of targets the operation is going to add
     */
    QStringList targetAdditions() const;
    /**
     * @returns a list of targets the operation is going to remove
     */
    QStringList targetRemovals() const;

    /**
     * @returns a list of targets the operation depends on
     */
    QStringList targetDependencies() const;

    /**
     * @returns a list of operations which are going to be executed right before the main operation
     */
    List preOperations() const;
    /**
     * @returns a list of operations which are going to be executed right after the main operation
     */
    List postOperations() const;

    /**
     * @returns a list of errors which occurred during the processing or validating
     */
    Error::List errors() const;

    /**
     * @returns a brief description of the operation
     */
    QString description() const;

    /**
     * @returns a list of processing options the operation needs to take care of
     */
    ProcessingOptions processingOptions() const;

    /**
     * @returns the estimated time left for the operation to complete processing
     * This will be zero if the operation is in a finished state.
     */
    int eta() const;

protected:
    /**
     * Constructs an operation
     * When processing a package one should use the Package::name() as targetName.
     * @param targetName the name of the target which is going to be processed
     */
    explicit Operation(const QString &targetName);

    /**
     * This method needs to be reimplemented by every operation.
     * It gets called when there is a need to validate the queue. In this operation
     * a number of things need to happen:
     * First of all you need to check whether the operation is ready to run. Then a number
     * of properties is needed.
     * If the operations is not ready to process set \c setValidationFinished to false, otherwise
     * true.
     * If there are any post- or pre-operations which need to be processed, they have to be created
     * and added in this method.
     *
     * @see setValidationFinished
     * @see setFileSystemAdditions
     * @see setFileSystemRemovals
     * @see setTargetAdditions
     * @see setTargetRemovals
     * @see setTargetDependencies
     * @see setConflictingTargets
     * @see setErrors
     * @see setPreOperations
     * @see setPostOperations
     */
    virtual void validate() = 0;

    /**
     * This method needs to be reimplemented by every operation.
     * It gets called when processing the queue of operations.
     * The \c run method is there to apply the certain operation you want
     * to do to the target. Use \c setFinished to report about success or fail
     * of it and \c setErrors to add more description to possible errors.
     *
     * @see setFinished
     * @see setErrors
     * @see addMessage
     * @see setProgress
     */
    virtual void run() = 0;

    /**
     * Sets the version of the target which is going to be processed.
     * In most cases this is simply the Package::version().
     * @param version the version of the target
     */
    void setTargetVersion(const QByteArray &version);

    /**
     * Sets if the operation can be processed concurrently.
     * @param concurrent when true, the operation is going be processed
     * concurrently
     */
    void setCanBeConcurrent(bool concurrent);

    /**
     * Sets the phase the operation is going to be processed in.
     * @param phase the phase the operation is going to be processed in
     */
    void setPhase(Phase phase);
    /**
     * Sets the priority of the operation
     * @param priority the priority of the operation (can be any integer value)
     */
    void setPriority(int priority);

    /**
     * Sets the list of files the operation is going to add to the filesystem
     * @param targets the files which are going to be added
     */
    void setFileSystemAdditions(const QStringList &targets);
    /**
     * Sets the list of files the operation is going to remove from the filesystem
     * @param targets the files which are going to be deleted
     */
    void setFileSystemRemovals(const QStringList &targets);

    /**
     * Sets the list of targets which are going to be added.
     * This is usually the Package::name(), but also the name of all providers.
     * @param targets the targets which are going to be added
     */
    void setTargetAdditions(const QStringList &targets);
    /**
     * Sets the list of targets which are going to be removed by the operation.
     * This is usually Package::name(), but also the name of all providers.
     * @param targets the targets which are going to be removed.
     */
    void setTargetRemovals(const QStringList &targets);

    /**
     * Sets a list of dependencies of the target we are going to process.
     * @param targets the dependencies of our target
     */
    void setTargetDependencies(const QStringList &targets);

    /**
     * Sets a list of targets which conflict to the target we are going to process.
     * @param targets the targets we conflict against
     */
    void setConflictingTargets(const QStringList &targets);
    /**
     * Sets a list of errors which occurred during validation or processing.
     * @param errors the list of errors which occurred
     */

    void setErrors(Error::List const& errors);

    /**
     * This method can be used to indicate at what point of processing we current are.
     * Should be a value between 0 and 100.
     * @param percentage the percentage the operation already is done
     */
    void setProgress(int percentage);
    /**
     * This method can be used to show a message to the user.
     * @param message the message to show
     */
    void addMessage(const QString &message);

    /**
     * Sets a list of operations which are going to be processed right before
     * this one.
     * @param operations a list of pre operations
     */
    void setPreOperations(const List &operations);
    /**
     * Sets a list of operations which are going to be processed right after this one.
     * @param operations a list of post operations
     */
    void setPostOperations(const List &operations);

    /**
     * Marks the validation as finished.
     * @param result use this to indicate whether the validation was successfull or not
     */
    void setValidationFinished(bool result);
    /**
     * Marks the operation as finished.
     * @param result use this to indicate whether the processing was successfull or not
     */
    void setFinished(bool result);

    /**
     * Sets a brief description to the operation
     * @param string the description of the operation
     */
    void setDescription(const QString &string);

    /**
     * Sets the estimated time left for the operation to complete processing
     * @param eta the estimated time left in seconds
     */
    void setEta(int eta);

private:
    OperationPrivate * const d_ptr;

    friend class OperationRunner;
    friend class RunnerWorker;
    friend class ValidatorWorker;

    /* This is needed by the libarchive interface to perform its tasks */
    friend class InstallFunctor;
    friend class PolKitInstallFunctor;
    friend class ReinstallUpgradeFunctor;
    friend class PolKitReinstallUpgradeFunctor;
};

}

#endif // AKABEIOPERATION_H
