/* This file is part of the Chakra project

   Copyright (C) 2010 - 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 AKABEICLIENTTRANSACTIONHANDLER_H
#define AKABEICLIENTTRANSACTIONHANDLER_H

#include <QObject>
#include <QTime>
#include "akabeicore_global.h"
#include "akabeiclientqueue.h"
#include <akabeierror.h>

namespace AkabeiClient {
class TransactionHandlerPrivate;
class TransactionPrivate;
/**
 * \class TransactionQuestion  akabeiclienttransactionhandler.h "akabeiclienttransactionhandler.h"
 *
 * \brief This class represents a question which needs to get presented to the user.
 *
 * Sometimes during a transaction, the user has to choose between different options. For this
 * case we have to have a question framework inside AkabeiClient. This is the main class for this.
 *
 * During a transaction, the client knows the Transaction object which knows what questions need
 * to get asked. Between each step in the transaction, the client needs to check whether there are
 * questions pending, otherwise Akabei cannot continue the transaction (it is marked as invalid).
 *
 * So what does a client do to answer these questions?
 *
 * Basically, once each step finished, we check if there are any new question. E.g. once the validation
 * finished, we're going to do something like this:
 *
 * \code
 * if (!transaction->questions().isEmpty()) {
 *     foreach (AkabeiClient::TransactionQuestion * q, transaction->questions()) {
 *         int result = 0;
 *         if (q->possibleAnswers() & AkabeiClient::TransactionQuestion::Yes) {
 *             if (q->possibleAnswers() & AkabeiClient::TransactionQuestion::No) {
 *                 result = QMessageBox::questionYesNo(0, q->question());
 *             } else {
 *                 result = QMessageBox::information(0, q->question());//This case should never happen ;)
 *             }
 *         } else {
 *             ...
 *         }
 *         AkabeiClient::TransactionQuestion::Answer answer;
 *         if (result == QMessageBox::Yes || result == QMessageBox::No)
 *             answer = AkabeiClient::TransactionQuestion::Yes;
 *         else
 *             answer = AkabeiClient::TransactionQuestion::No;
 *         q->setAnswer(answer);
 *     }
 * }
 * \endcode
 *
 * This class is not thread-safe.
 */
struct AKABEICLIENTSHARED_EXPORT TransactionAnswer
{
    typedef QList<TransactionAnswer> List;
    
    TransactionAnswer(QString l, const QString &mes) //TODO: Makes the impl in the cpp file
      : letter(l), message(mes)
    {}
    TransactionAnswer() {}
    
    bool operator==(const TransactionAnswer &a) {
        return a.letter == this->letter && a.message == this->message;
    }
    
    QString letter;
    QString message;
};

class AKABEICLIENTSHARED_EXPORT TransactionQuestion
{
    public:
       ~TransactionQuestion();

        /**
         * The question which gets represented to the user.
         * This string should be translated!
         * @returns the actual question
         */
        QString question() const;

        /**
         * @returns a number of possible answers to this question
         */
        QList<TransactionAnswer> possibleAnswers() const;

        /**
         * @returns the default answer for this question
         */
        TransactionAnswer suggestedAnswer() const;

        /**
         * @returns the answer given by the user, per default this is None
         */
        TransactionAnswer answer() const;

        /**
         * This method is used to set the answer.
         * @param answer the answer given by the user
         */
        void setAnswer(const QString &answer);
private:
        TransactionQuestion(const QString &desc,
                            const QString &question,
                            TransactionAnswer::List possibleAnswers,
                            TransactionAnswer::List correctAnswers,
                            TransactionAnswer suggestedAnswer);
        TransactionQuestion(const QString &desc,
                            const QString &question);

        friend class TransactionHandler;
        friend class TransactionHandlerPrivate;
        friend class Transaction;
        TransactionAnswer::List correctAnswers() const;
        
        void setData(void * d);
        void * data();

        /**
         * A string which describes the question asked. This is not shown in the user interface,
         * but only exists to internally know what question was asked.
         *
         * The description should be unique.
         *
         * @returns a description of the TransactionQuestion
         */
        QString description() const;

        class Private;
        Private *d;
};

/**
 * \class Transaction akabeiclienttransactionhandler.h "akabeiclienttransactionhandler.h"
 *
 * \brief This class represents a transaction which gets processed by akabei.
 *
 * Once the TransactionHandler creates a new transaction, it emits it via transactionCreated.
 * Another way to get the current transaction object is to call TransactionHandler::currentTransaction.
 *
 * This class contains all the necessary information which is needed to represent a transaction to the
 * user. Once it gets created it should be shown to the user for review.
 * If there are any possible errors or questions in the transaction, this class knows it.
 *
 * Questions and errors should be checked for at each step during the transaction, otherwise
 * akabei won't be able to continue. If there are blocking actions in the transaction who
 * need to get worked off first, it is marked as invalid.
 *
 * \code
 * if (!transaction->errors.isEmpty()) {
 *     //Show errors and return
 * }
 * if (transaction->questions().isEmpty() {
 *     //Show questions and set answers
 * }
 * if (transaction->isValid()) {
 *     AkabeiClient::Backend::instance()->transactionHandler()->validate();
 * }
 * \endcode
 *
 * Something like this should be done at every step of the transaction
 *
 * This class is not thread-safe.
 */
class AKABEICLIENTSHARED_EXPORT Transaction
{
    public:
        ~Transaction();

        /**
         * @returns the packages which are going to be installed
         */
        QSet<Akabei::Package*> toBeInstalled() const;
        /**
         * @returns the packages which are going to be reinstalled
         */
        QSet<Akabei::Package*> toBeReinstalled() const;
        /**
         * @returns the packages which are going to be upgraded
         * The key represents the local package, the value is the updated package.
         */
        QMap<Akabei::Package*, Akabei::Package*> toBeUpgraded() const;
        /**
         * @returns the packages which are going to be removed
         */
        QSet<Akabei::Package*> toBeRemoved() const;
        /**
         * @returns the packages which are going to be replaced
         */
        QMap<Akabei::Package*, QList<Akabei::Package*> > toBeReplaced() const;

        /**
         * @returns a list of errors which occurred during transaction
         */
        Akabei::Error::List errors() const;

        /**
         * @returns a list of questions which need to get answered
         */
        QList<AkabeiClient::TransactionQuestion*> questions() const;

        /**
         * @returns the processing options which are going to be used when processing this transaction
         */
        Akabei::ProcessingOptions processingOptions() const;

        /**
         * @returns true when the transaction is able to continue with the next step
         */
        bool isValid() const;

    private:
        Transaction();
        friend class TransactionHandler;
        friend class TransactionHandlerPrivate;
        TransactionPrivate *d;
};

class TransactionHandler;

/**
 * \class DownloadInformation akabeiclienttransactionhandler.h "akabeiclienttransactionhandler.h"
 *
 * \brief This class is used to store information about running downloads.
 *
 * DownloadInformations are implicitly shared.
 *
 * This class is not thread-safe.
 */
class AKABEICLIENTSHARED_EXPORT DownloadInformation //TODO: Probably store package in here as well?
{
    public:
        /**
         * Never use this constructor! It's just there for Qt's sake! Remove once we store package in here!
         */
        DownloadInformation();
        DownloadInformation(const DownloadInformation &other);
        ~DownloadInformation();

        /**
         * @returns the number of downloaded bytes
         */
        qint64 downloadedBytes() const;
        /**
         * @returns the total bytes of the running download
         */
        qint64 totalBytes() const;
        /**
         * @returns the total download speed
         */
        qint64 downloadSpeed() const;

        DownloadInformation &operator=(const DownloadInformation &other);

    private:
        DownloadInformation(qint64 total);
        class Private;
        QSharedDataPointer<Private> d;

        friend class TransactionProgress;
};

/**
 * \class TransactionProgress akabeiclienttransactionhandler.h "akabeiclienttransactionhandler.h"
 *
 * \brief This class is used to show progress information about the current transaction.
 *
 * If the client wants to show up to date information to the user, connect to the signals
 * in this class and get all updates.
 *
 * This class is not thread-safe.
 */
class AKABEICLIENTSHARED_EXPORT TransactionProgress : public QObject
{
    Q_OBJECT
    public:
        enum Phase {
            Validating = 0,
            RunningPreHooks = 1,
            Downloading = 2,
            Processing = 3,
            RunningScriptlets = 4,
            RunningPostHooks = 5,
            Cleaning = 6,
            Finished = 7
        };
        ~TransactionProgress();

        /**
         * @returns the phase the transaction is currently in
         */
        Phase phase() const;

        /**
         * @returns the overall progress within the current phase
         * Gets reset whenever the TransactionHandler enters a new phase.
         */
        int progress() const;

        /**
         * @returns a map of the packages which are current processed
         * The value of the map represents the percentage of the processing of the specific package in the current phase.
         */
        QMap<Akabei::Package*, int> currentPackages() const;

        /**
         * @returns the estimated time which is needed for this transaction phase to finish
         */
        QTime estimatedTime() const;

        /**
         * @returns information about the packages which current get downloaded
         * The map will be empty in all phases except Downloading.
         */
        QMap<Akabei::Package*, DownloadInformation> downloadInformation() const;

    Q_SIGNALS:
        /**
         * The current phase changed.
         * @param phase the current phase
         * @see phase
         */
        void phaseChanged(AkabeiClient::TransactionProgress::Phase phase);

        /**
         * The overall progress changed.
         * @param percent the current progress
         * @see progress
         */
        void progressChanged(int percent);

        /**
         * A package started processing.
         * @param pkg the package which got started
         */
        void packageStarted(Akabei::Package * pkg);

        /**
         * A package finished processing.
         * @param pkg the package which finished
         */
        void packageEnded(Akabei::Package * pkg);

        /**
         * The progress of a package changed.
         * @param pkg the package
         * @param progress the percentage of execution in the current phase
         */
        void packageProgressChanged(Akabei::Package *pkg, int progress);

        /**
         * The estimated time changed.
         * @param eta the new estimated time
         * @see estimatedTime
         */
        void estimatedTimeChanged(QTime eta);

        /**
         * A DownloadInformation got changed or added.
         */
        void downloadInformationChanged(Akabei::Package * pkg, const AkabeiClient::DownloadInformation &information);

    private:
        TransactionProgress(TransactionHandler * parent = 0);

        friend class TransactionHandler;
        friend class TransactionHandlerPrivate;

        class Private;
        Private *d;

        Q_PRIVATE_SLOT(d, void __k__packageStarted(Akabei::Package *))
        Q_PRIVATE_SLOT(d, void __k__packageEnded(Akabei::Package *))
        Q_PRIVATE_SLOT(d, void __k__packageProgressChanged(Akabei::Package *, int))
        Q_PRIVATE_SLOT(d, void __k__setDownloadedBytes(Akabei::Package *, qint64))
        Q_PRIVATE_SLOT(d, void __k__setTotalBytes(Akabei::Package *, qint64))
        Q_PRIVATE_SLOT(d, void __k__setDownloadSpeed(Akabei::Package *, qint64))
        Q_PRIVATE_SLOT(d, void __k__phaseChanged(Akabei::Operation::Phase))
};

/**
 * \class TransactionHandler akabeiclienttransactionhandler.h "akabeiclienttransactionhandler.h"
 *
 * \brief This class represents a transaction which gets processed by akabei.
 *
 * For each transaction which the client wants to process, this is the class you should
 * start with!
 *
 * The way it works is as follows: //TODO: This is wrong by now, update it!!!
 * 1. fill an AkabeiClient::Queue with packages you want to process
 * 2. connect to transactionCreated and call start
 * 3. once the Transaction got created, present packages/questions/errors to the user
 * 4. if the Transaction is valid, connect to validationFinished and call validate
 * 5. if the validation finished successfully, connect to finished and call process
 * 6. the transaction is complete
 *
 * For additional progress information about the Transaction there is the transactionProgress
 * method, the current transaction can be retrieved via currentTransaction.
 *
 * This class is not thread-safe.
 */
class AKABEICLIENTSHARED_EXPORT TransactionHandler : public QObject
{
    Q_OBJECT
    Q_DISABLE_COPY(TransactionHandler)
    Q_DECLARE_PRIVATE(TransactionHandler)

    public:
        virtual ~TransactionHandler();

        /**
         * Takes AkabeiClient::Backend::queue() and starts to transform it to a valid queue.
         * When this step is finished transactionCreated gets emitted with all the information
         * to show to the user.
         * Once the user has confirmed the transaction and answered all questions, use validate()
         * to continue.
         *
         * @param processingOptions the processing options to use
         *
         * @see transactionCreated
         * @see validate
         */
        void start(Akabei::ProcessingOptions processingOptions);

        /**
         * Transfers the Transaction object we created in the step before to Operations and adds those to the
         * OperationRunner.
         * After that we try to validate all Operations, possible errors get added to the Transaction.
         * The client will receive validationFinished as signal that the validation is finished.
         * If the validation was valid, the client can call process to process the operations.
         *
         * @see start
         * @see process
         * @see validationFinished
         */
        void validate();

        /**
         * Processes the operations which got added in the previous step to the OperationRunner.
         * This is the last step in a transaction, we will emit finished() once it is finished.
         *
         * @see finished
         * @see validate
         * @see validationFinished
         * @see start
         */
        void process();

        /**
         * @returns the transaction which currently gets processed, can be 0
         */
        Transaction * currentTransaction() const;

        /**
         * @returns an object which reports the transaction progress
         */
        TransactionProgress * transactionProgress() const;

    Q_SIGNALS:
        void transactionCreated(AkabeiClient::Transaction * t);
        void validationFinished(bool success);
        void finished(bool success);

        void newTransactionMessage(const QString &message);

    private:
        TransactionHandler(Queue * queue, QObject * parent);
        TransactionHandlerPrivate *d_ptr;

        friend class Backend;

        Q_PRIVATE_SLOT(d_func(), void __k__phaseStarted(Akabei::Operation::Phase))
        Q_PRIVATE_SLOT(d_func(), void __k__phaseFinished(Akabei::Operation::Phase))
        Q_PRIVATE_SLOT(d_func(), void __k__operationProgress(Akabei::Operation *, int))
        Q_PRIVATE_SLOT(d_func(), void __k__operationEtaChanged(Akabei::Operation *, int))
        Q_PRIVATE_SLOT(d_func(), void __k__operationMessage(Akabei::Operation *, QString))
        Q_PRIVATE_SLOT(d_func(), void __k__operationStarted(Akabei::Operation*))
        Q_PRIVATE_SLOT(d_func(), void __k__operationFinished(Akabei::Operation*))
        Q_PRIVATE_SLOT(d_func(), void __k__errorsOccurred(Akabei::Error::List))
        Q_PRIVATE_SLOT(d_func(), void __k__finished(bool))
        Q_PRIVATE_SLOT(d_func(), void __k__validationFinished(bool))
        Q_PRIVATE_SLOT(d_func(), void __k__startFinished())
        Q_PRIVATE_SLOT(d_func(), void __k__transformQueueFinished())
};
}

#endif
