/* This file is part of the Chakra project

   Copyright (C) 2010 Dario Freddi <drf@chakra-project.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.
*/

#ifndef AKABEIBACKEND_H
#define AKABEIBACKEND_H

#include <QObject>
#include <QMetaType>

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

class QUuid;
namespace Akabei {
class Delta;
class OperationRunner;
class Group;
class Package;
class Database;
class Hook;

class BackendPrivate;
/**
 * \class Backend  akabeibackend.h "akabeibackend.h"
 *
 * \brief Base class of AkabeiCore.
 *
 * Backend is the base class for all operations going on in AkabeiCore. Before starting
 * any transaction or query it needs to be initialized.
 * Said queries are run asynchronously.
 *
 * \code
 * QUuid queryUuid;
 * void query()
 * {
 *     Akabei::Backend::instance()->initialize();
 *     connect(Akabei::Backend::instance(), SIGNAL(queryPackagesCompleted(QUuid, QList<Akabei::Package*>)), SLOT(queryCompleted(QUuid, QList<Akabei::Package*>)));
 *     queryUuid = Akabei::Backend::instance()->searchPackages(bla);
 * }
 *
 * void queryCompleted(QUuid uuid, QList<Akabei::Package*> packages)
 * {
 *     if (uuid == queryUuid) {
 *     //Do stuff
 *     }
 * }
 * \endcode
 *
 * If you want to do install/remove/upgrade a package without manually handling all the operations, you should
 * use \c AkabeiClient::Backend. This is recommended, as the user should always have the same operations run during
 * e.g. an install operation on a system.
 *
 * This class is thread-safe.
 */
class AKABEICORESHARED_EXPORT Backend : public QObject
{
    Q_OBJECT
    Q_DISABLE_COPY(Backend)
    Q_DECLARE_PRIVATE(Backend)
    Q_ENUMS(Status)
    public:
        /**
         * \enum Status
         * Describes the status the Backend current is in.
         */
        enum Status {
            StatusBare = 0,
            StatusInitializing = 1,
            StatusWaitingForLock = 2,
            StatusReady = 3,
            StatusOnTransaction = 4,
            StatusBroken = 5
        };

        /**
         * @returns the backend singleton
         */
        static Backend *instance();
        virtual ~Backend();

        /**
         * Sets the locale for AkabeiCore.
         */
        void setLocale(const QString &locale);

        /**
         * Take care of using this method:
         * If you set it to StatusOnTransaction it will try to acquire a lock and will only retire from that
         * after you set another Status again.
         * @param status the new status of the backend
         * @param cO this object gets called when the status change from WaitingForLock to OnTransaction
         * @param cS this function gets called when the status changes from WaitingForLock to OnTransaction
         */
        void setStatus(Backend::Status status, QObject * cO = nullptr, const char *cS = nullptr);

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

        /**
         * @returns the status of the backend in the form of string
         */
        QString statusToString(Backend::Status aElement);

        /**
         * @returns the local database, which is the database which contains all the installed packages.
         * To check whether a package is installed on the system, one needs to query this database whether
         * it contains the package.
         */
        Database *localDatabase();

        /**
         * @returns a list of remote databases which are current initialized
         */
        QList<Database*> databases();

        /**
         * Queries akabei for all packages and emits queryPackagesCompleted()
         * @returns the \c QUuid associated with the query
         * @see queryPackagesCompleted()
         */
        QUuid packages();

        /**
         * Searches all packages which contain the given token and emits queryPackagesCompleted()
         * @param token the string to be queried for
         * @param type describes what properties are taken into account
         * @see queryPackagesCompleted()
         * @returns the \c QUuid of the query
         */
        QUuid searchPackages(const QString &token, SearchType type = SearchNameAndDescriptionLike);

        /**
         * Searches all packages which contain one of the given tokens and emits queryPackagesCompleted()
         * @param packages a list of tokens
         * @param type describes what properties are taken into account
         * @see queryPackagesCompleted()
         * @returns the \c QUuid of the query
         */
        QUuid searchPackages(const QStringList &packages, SearchType type = SearchNameAndDescriptionLike);

        /**
         * Queries akabei for all groups and emits queryGroupsCompleted()
         * @returns the \c QUuid associated with the query
         * @see queryGroupsCompleted()
         */
        QUuid groups();

        /**
         * Searches all groups which contain the given token in either their description or name and emits queryGroupsCompleted()
         * @param token the string to be queried for
         * @see queryGroupsCompleted()
         */
        QUuid searchGroups(const QString &token);

        /**
         * Performs a simple SQL-Query on all databases and emits queryPackagesCompleted()
         * @returns the \c QUuid associated with the query
         * @see queryPackagesCompleted()
         */
        QUuid queryPackages(const QString &sql);

        /**
         * Computes the list of orphaned packages in the local database and emits queryOrphansCompleted()
         * @returns the \c QUuid associated with the operation.
         * @see queryOrphansCompleted()
         */
        QUuid orphanPackages();

        /**
         * Performs a simple SQL-Query on all databases and emits queryPackagesCompleted()
         * @returns the \c QUuid associated with the query
         * @see queryGroupsCompleted()
         */
        QUuid queryGroups(const QString &sql);

        /**
         * @returns the global OperationRunner used for all transactions
         */
        OperationRunner *operationRunner();

        /**
         * Loads a package from a ".pkg.tar.xz"-file.
         * @param path the path of the package-file
         */
        Package *loadPackageFromFile(const QString &path);
        /**
         * Loads a delta from a ".delta.tar.xz"-file.
         * @param path the path of the delta-file
         */
        Delta *loadDeltaFromFile(const QString &path);
        /**
         * Loads a group from a textfile.
         * @param path the path of the file containing the group information
         */
        Group *loadGroupFromFile(const QString &path);
        /**
         * Loads a hook from a textfile.
         * @param path the path of the file containing the hook information
         */
        Hook *loadHookFromFile(const QString &path, QList<Package*> pkgs);
        
        /**
         * This function contains all the operations that must be performed before exiting, either normally or
         * as a result of some error.
         */
        void deInit();

    public Q_SLOTS:
        /**
         * This method initializes the database. This needs to be run before any query or transaction
         * is performed.
         */
        void initialize();

    Q_SIGNALS:
        /**
         * The status of the backend changed.
         * @param status the new status
         */
        void statusChanged(Akabei::Backend::Status status);
        /**
         * A package query got completed.
         * @param id the \c QUuid associated with the query
         * @param result the result of the query, in this case a list of Akabei::Packages
         */
        void queryPackagesCompleted(const QUuid &id, const QList<Akabei::Package*> &result);
        
        /**
         * Computation of orphans packages completed.
         * @param id the \c QUuid associated with the operation
         * @param result the resulted Package list.
         */
        void queryOrphansCompleted(const QUuid &id, const QList<Akabei::Package*> &result);
        
        /**
         * A group query got completed.
         * @param id the \c QUuid associated with the query
         * @param result the result of the query, in this case a list of Akabei::Groups
         */
        void queryGroupsCompleted(const QUuid &id, const QList<Akabei::Group*> &result);

    private:
        Backend(QObject* parent = nullptr);

        BackendPrivate * const d_ptr;

        Q_PRIVATE_SLOT(d_func(), void __k__initializationFinished())
        Q_PRIVATE_SLOT(d_func(), void __k__lockGranted(qlonglong))
        Q_PRIVATE_SLOT(d_func(), void __k__lockGranted())

        friend class DatabasePrivate;
        friend class Config;
        friend class OperationRunner;
        friend class OperationRunnerPrivate;
        friend class BackendHelper;
};

}

Q_DECLARE_METATYPE(Akabei::Backend::Status)

#endif // AKABEIBACKEND_H
