/***************************************************************************
 *   Copyright (C) 2005 by the G System Team                               *
 *   http://www.g-system.at                                                *
 *                                                                         *
 *   Permission is hereby granted, free of charge, to any person obtaining *
 *   a copy of this software and associated documentation files (the       *
 *   "Software"), to deal in the Software without restriction, including   *
 *   without limitation the rights to use, copy, modify, merge, publish,   *
 *   distribute, sublicense, and/or sell copies of the Software, and to    *
 *   permit persons to whom the Software is furnished to do so, subject to *
 *   the following conditions:                                             *
 *                                                                         *
 *   The above copyright notice and this permission notice shall be        *
 *   included in all copies or substantial portions of the Software.       *
 *                                                                         *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*
 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR     *
 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
 *   OTHER DEALINGS IN THE SOFTWARE.                                       *
 ***************************************************************************/

#ifndef GWEGSTORAGE_H
#define GWEGSTORAGE_H

#include <qobject.h>
#include <qvaluelist.h>
#include <qmutex.h>

#include <exception>

class QSqlDatabase;
class QDomDocument;
class QStringList;

namespace GCS
{
  class GElement;
  class GElementID;
  class GAgent;
  class GEnergy;
  class GForm;
  class GVector3;
  class GWorldData;
}

namespace GWE
{

/**
 * \class GStorageException GStorage.h
 * \brief An exception that can is thrown from GStorage objects.
 * @author Raphael Langerhorst
 * 
 * @todo Improve MySQL support and avoid special case handling (driver)
 */
class GStorageException : public std::exception
{
  private:
    
    /**
     * The actual text.
     */
    QString Text;
  public:
    
    /**
     * Creates a storage exception with a descriptive error message.
     */
    GStorageException(QString error) : Text(error) {}
    
    /**
     * Virtual destructor.
     */
    virtual ~GStorageException() throw() {}
    
    /**
     * @return the error description.
     */
    QString toString() {return Text;}
};

/**
 * \class GStorage GStorage.he
 * \brief Provides persistent data storage, using a DBMS
 * @author Raphael Langerhorst
 * 
 * Usually this class is used like this:
 * 
 * @code
 * 
 * GStorage* storage = new GStorage();
 * storage->setDatabase("QSQLITE",":memory:","user","password");
 * storage->connectDatabase();  //returns false on failure
 * storage->initializeDatabase();  //returns false on failure
 * 
 * GXmlDataController* dc = new GXmlDataController(storage,network);
 * 
 * @endcode
 * 
 * Please consult the G System Architecture for the DB layout.
 * @note GStorage inherits QMutex, the application is responsible
 *       of serializing access to GStorage objects!
 */

class GStorage : public QObject, public QMutex
{
  Q_OBJECT
    
  protected:
    
    /**
     * The database itself.
     * @see connectDatabase()
     */
    QSqlDatabase* Database;
    
    /**
     * The database type or driver.
     * @see setDatabaseDriver()
     */
    QString DbDriver;
    
    /**
     * The database name.
     * @see setDatabaseName()
     */
    QString DbName;
    
    /**
     * The host where the DBMS is running.
     * If no host is given, a local connection is used.
     * @see setDatabaseHost()
     */
    QString DbHost;
    
    /**
     * The port on which the DBMS is listening.
     * If the port is set to 0 (the default) then the default
     * port of the database driver is used instead.
     * @see setDatabasePort()
     */
    int DbPort;
    
    /**
     * The database user.
     * @see setDatabaseUser()
     */
    QString DbUser;
    
    /**
     * The database password.
     * @see setDatabasePassword()
     */
    QString DbPassword;
    
  public:
    
    /**
     * Constructor.
     */
    GStorage(QObject *parent = 0, const char *name = 0);
    
    /**
     * Destructor.
     */
    virtual ~GStorage();
    
    /**
     * Sets the database type or driver to use.
     * You can get all available drivers with QSqlDatabase::drivers().
     * @see QSqlDatabase::drivers()
     * @see DbDriver
     */
    void setDatabaseDriver(QString driver);
    
    /**
     * Set the actual database connection string. The form of the string
     * usually differs depending on the database driver that is used.
     * @see DbName
     */
    void setDatabaseName(QString connection);
    
    /**
     * Sets the host where the DBMS is running.
     * Leave empty for local connections.
     * @see DbHost
     */
    void setDatabaseHost(QString host);
    
    /**
     * Sets the port on which the DBMS is listening.
     * Leave empty or set to 0 for the default port.
     * @see DbPort
     */
    void setDatabasePort(int port);
    
    /**
     * Set the user with which to connect to the database.
     * @see DbUser
     */
    void setDatabaseUser(QString user);
    
    /**
     * Set the user's password.
     * @see DbPassword
     */
    void setDatabasePassword(QString password);
    
    /**
     * Set all database parameters at once.
     * @note this does not connect to the database, use connectDatabase()
     *       for this purpose.
     */
    void setDatabase(QString driver, QString name, QString user, QString password, QString host="", int port=0);
    
    /**
     * Connects to database.
     * You must set all database parameters before connecting.
     * @see setDatabase()
     * @return true on success.
     */
    bool connectDatabase();
    
    /**
     * @return true if database is open.
     */
    bool isDatabaseConnected();
    
    /**
     * Creates all tables, ...
     * You must connect to the database before initializing.
     * @see connectDatabase()
     * @return true if it was possible to prepare the database for real usage.
     */
    bool initializeDatabase();
    
    /**
     * Disconnects from database.
     * @return true if connection was already closed or if closing was successfull.
     */
    bool disconnectDatabase();
    
    /**
     * @return the last SQL error.
     */
    QString getLastDatabaseError();
    
    //BEGIN Data management
    
  protected:
    
    GCS::GVector3 getVector3(unsigned long id) throw(GStorageException);
    
  public:
    
    //BEGIN GWE stuff
    
    // get
    
    QStringList getAllServers() throw(GStorageException);
    
    bool getServerExists(const QString& server) throw(GStorageException);
    
    QStringList getServersForElementChildren(const GCS::GElementID& parent) throw(GStorageException);

    QValueList<GCS::GElementID> getElementsForServer(const QString& server) throw(GStorageException);
    
    QString getManagingServerForElement(const GCS::GElementID& id) throw(GStorageException);
    
    QString getServerPresence(const QString& server) throw(GStorageException);
    
    QStringList getServersSubscribedToElementUpdates(const GCS::GElementID& id) throw(GStorageException);
    
    QStringList getServersSubscribedToDataUpdates(const GCS::GElementID& id) throw(GStorageException);
    
    QStringList getServersSubscribedToEnergyUpdates(const GCS::GElementID& id) throw(GStorageException);
    
    QStringList getServersSubscribedToFormUpdates(const GCS::GElementID& id) throw(GStorageException);
    
    // modifications:
    
    /**
     * A new server is added on registration.
     */
    void addServer(const QString& server, const QString& presence) throw(GStorageException);
    
    void updateServerPresence(const QString& server, const QString& presence) throw(GStorageException);
    
    /**
     * Also removes all associated subscriptions of course.
     * A server is generally removed when it unregisters.
     */
    void removeServer(const QString& server) throw(GStorageException);
    
    /**
     * @param type must be either element, data, energy or form
     */
    void setSubscription(const GCS::GElementID& element, const QString& server, const QString& type) throw(GStorageException);
    
    /**
     * @param type must be either element, data, energy or form
     */
    void clearSubscription(const GCS::GElementID& element, const QString& server, const QString& type) throw(GStorageException);
    
    void updateManagingServer(const GCS::GElementID& element, const QString& new_server) throw(GStorageException);
    
    
    //END GWE stuff
    
    //BEGIN Element stuff
    
    // NOTE the agent list has only one column, it might be extended in the future
    
    // get
    
    bool getElementExists(const GCS::GElementID& id) throw(GStorageException);
    
    QValueList<GCS::GElementID> getAllElementIDs() throw(GStorageException);
    
    QValueList<GCS::GElementID> getAllElementIDsByServer(const QString& managing_server) throw(GStorageException);
    
    /**
     * @todo use views if the database supports it to select a whole element with one select!
     * @param data pointer to the used data controller, like the GXmlDataController
     */
    GCS::GElement* getElement(const GCS::GElementID& id, const GCS::GWorldData* data) throw(GStorageException);
    
    QValueList<GCS::GElement*> getElements(QValueList<GCS::GElementID> id_list, const GCS::GWorldData* worlddata) throw(GStorageException);
    
    QDomDocument* getElementData(const GCS::GElementID& id) throw(GStorageException);
    
    GCS::GEnergy* getElementEnergy(const GCS::GElementID& id) throw(GStorageException);
    
    GCS::GForm* getElementForm(const GCS::GElementID& id) throw(GStorageException);
    
    QValueList<GCS::GElementID> getChildren(const GCS::GElementID& id) throw(GStorageException);
    
    GCS::GElementID getParent(const GCS::GElementID& id) throw(GStorageException);
    
    QStringList getElementAgents(const GCS::GElementID& id) throw(GStorageException);
    
    GCS::GElementID getConnection(const GCS::GElementID& id) throw(GStorageException);
    
    
    QStringList getAllAgents() throw(GStorageException);
    
    /**
     * @return empty string if agent does not exist
     */
    QString getAgent(const QString& agent) throw(GStorageException);
    
    // modifications
    
    void addElement(const GCS::GElement* element, const QString& managing_server) throw(GStorageException);
    
    void updateElement(const GCS::GElement* element) throw(GStorageException);
    
    void removeElement(const GCS::GElementID& id) throw(GStorageException);
    
    void removeElements(QValueList<GCS::GElementID> id_list) throw(GStorageException);
    
    void reparentElement(const GCS::GElementID& element, const GCS::GElementID& old_parent, const GCS::GElementID& new_parent) throw(GStorageException);
    
    void changeConnection(const GCS::GElementID& element, const GCS::GElementID& connection) throw(GStorageException);
    
    void addElementAgent(const GCS::GElementID& element, const QString& agent) throw(GStorageException);
    
    void removeElementAgent(const GCS::GElementID& element, const QString& agent) throw(GStorageException);
    
    /**
     * @return list of agents that were removed.
     */
    QStringList removeAllElementAgents(const GCS::GElementID& element) throw(GStorageException);
    
    /**
     * @return list of elements that had the agent included.
     */
    QValueList<GCS::GElementID> removeAgentFromAllElements(const QString& agent) throw(GStorageException);
    
    void updateData(const GCS::GElementID& element, const QDomDocument* data) throw(GStorageException);
    
    void updateEnergy(const GCS::GElementID& element, const GCS::GEnergy* energy) throw(GStorageException);
    
    void updateForm(const GCS::GElementID& element, const GCS::GForm* form) throw(GStorageException);
    
    
    void removeAllAgents() throw(GStorageException);
    
    void addAgent(const QString& agent) throw(GStorageException);
    
    /**
     * @note you should make sure that the removed agent is not used by
     *       any element, see removeAgentFromAllElements()
     */
    void removeAgent(const QString& agent) throw(GStorageException);
    
    //END Element stuff
    
    //END Data management
};

}

#endif
