// This may look like C code, but it's really -*- C++ -*-
/*
 * Copyright (C) 2008 Emweb bvba, Kessel-Lo, Belgium.
 *
 * See the LICENSE file for terms of use.
 */
#ifndef WT_DBO_SESSION_H_
#define WT_DBO_SESSION_H_

#include <map>
#include <set>
#include <string>
#include <typeinfo>

#include <Wt/Dbo/ptr>
#include <Wt/Dbo/collection>
#include <Wt/Dbo/Query>
#include <Wt/Dbo/Transaction>

namespace Wt {
  namespace Dbo {
    namespace Impl {
      extern WTDBO_API std::string quoteSchemaDot(const std::string& table);
    }

struct NullType {
  static NullType null_;
};

class Call;
class SqlConnection;
class SqlConnectionPool;
class SqlStatement;
template <typename Result, typename BindStrategy> class Query;
struct DirectBinding;
struct DynamicBinding;

/*! \class Session Wt/Dbo/Session Wt/DboSession
 *  \brief A database session.
 *
 * A database session manages meta data about the mapping of C++
 * classes to database tables, and keeps track of a collection of
 * in-memory objects.
 *
 * It also manages transactions that you need to create when accessing
 * database objects.
 *
 * You can provide the session with a dedicated database connection
 * using setConnection(), or with a connection pool (from which it
 * will take a connection while processing a transaction) using
 * setConnectionPool(). In either case, the session does not take
 * ownership of the connection or connection pool.
 *
 * A session will typically be a long-lived object in your
 * application.
 *
 * \ingroup dbo
 */
class WTDBO_API Session
{
public:
  /*! \brief Creates a database session.
   */
  Session();

  /*! \brief Destructor.
   *
   * A session must survive all database objects that have been loaded
   * through it, and will warning during this destructor if there are
   * still database objects that are being referenced from a ptr.
   */
  ~Session();

  /*! \brief Sets a dedicated connection.
   *
   * The connection will be used exclusively by this session.
   *
   * \sa setConnectionPool()
   */
  void setConnection(SqlConnection& connection);

  /*! \brief Sets a connection pool.
   *
   * The connection pool is typically shared with other sessions.
   *
   * \sa setConnection()
   */
  void setConnectionPool(SqlConnectionPool& pool);

  /*! \brief Maps a class to a database table.
   *
   * The class \p C is mapped to table with name \p tableName. You
   * need to map classes to tables.
   *
   * You may provide a schema-qualified table name, if the underlying
   * database supports this, eg. <tt>"myschema.users"</tt>.
   */
  template <class C> void mapClass(const char *tableName);

  /*! \brief Returns the mapped table name for a class.
   *
   * \sa mapClass()
   */
  template <class C> const char *tableName() const;

  /*! \brief Persists a transient object.
   *
   * The transient object pointed to by \p ptr is added to the
   * session, and will be persisted when the session is flushed.
   *
   * A transient object is usually a newly created object which want
   * to add to the database.
   *
   * The method returns \p ptr.
   */
  template <class C> ptr<C> add(ptr<C>& ptr);

  /*! \brief Persists a transient object.
   *
   * This is an overloaded method for convenience, and is implemented as:
   * \code
   * return add(ptr<C>(obj));
   * \endcode
   *
   * The method returns a database pointer to the object.
   */
  template <class C> ptr<C> add(C *obj);

  /*! \brief Loads a persisted object.
   *
   * This method returns a database object with the given object
   * id. If the object was already loaded in the session, the loaded
   * object is returned, otherwise the object is loaded from the
   * database.
   *
   * Throws an ObjectNotFoundException when the object was not found.
   *
   * \sa ptr::id()
   */
  template <class C> ptr<C> load(long long id);

#ifndef DOXYGEN_ONLY
  template <class C>
    Query< ptr<C> > find(const std::string& condition = std::string()) {
    // implemented in-line because otherwise it crashes gcc 4.0.1
    return find<C, DynamicBinding>(condition);
  }
#endif // DOXYGEN_ONLY

  /*! \brief Finds database objects.
   *
   * This method creates a query for finding objects of type \p C.
   *
   * When passing an empty \p condition parameter, it will return all
   * objects of type \p C. Otherwise, it will add the condition, by
   * generating an SQL <i>where</i> clause.
   *
   * The \p BindStrategy specifies how you want to bind parameters to
   * your query (if any).
   *
   * When using \p DynamicBinding (which is the default), you will
   * defer the binding until the query is run. This has the advantage
   * that you can compose the query definition using helper methods
   * provided in the query object, you can keep the query around and
   * run the query multiple times, perhaps with different parameter
   * values or to scroll through the query results.
   *
   * When using \p DirectBinding, the query must be specified entirely
   * using the \p condition, and can be run only once. This method
   * does have the benefit of binding parameters directly to the
   * underlying prepared statement.
   *
   * This method is convenient when you are querying only results from a
   * single table. For more generic query support, see query().
   *
   * Usage example:
   * \code
   * // Bart is missing, let's find him.
   * Wt::Dbo::ptr<User> bart = session.find<User>().where("name = ?").bind("Bart");
   *
   * // Find all users, order by name
   * typedef Wt::Dbo::collection< Wt::Dbo::ptr<User> > Users;
   * Users users = session.find<User>().orderBy("name");
   * \endcode
   *
   * \sa query()
   */
#ifdef DOXYGEN_ONLY
  template <class C, typename BindStrategy = DynamicBinding>
#else
  template <class C, typename BindStrategy>
#endif
    Query< ptr<C>, BindStrategy>
    find(const std::string& condition = std::string());

#ifndef DOXYGEN_ONLY
  template <class Result> Query<Result> query(const std::string& sql);
#endif // DOXYGEN_ONLY

  /*! \brief Creates a query.
   *
   * The sql statement should be a complete SQL statement, starting
   * with a "select ". The items listed in the "select" must match the
   * \p Result type. An item that corresponds to a database object
   * (ptr) is substituted with the selection of all the fields in the
   * dbo.
   *
   * For example, the following query (class User is mapped onto table 'user'):
   * \code
   * session.query< ptr<User> >("select u from user u").where("u.name = ?").bind("Bart");
   * \endcode
   * is the more general version of:
   * \code
   * session.find<User>().where("name = ?").bind("Bart");
   * \endcode
   *
   * Note that "u" in this query will be expanded to select the fields of the
   * user table (u.id, u.version, u.name, ...). The same expansion happens when
   * using an alias in Query::groupBy().
   *
   * The additional flexibility offered by %query() over find() is
   * however that it may support other result types.
   *
   * Thus, it may return plain values:
   * \code
   * session.query<int>("select count(1) from ...");
   * \endcode
   *
   * Or Boost.Tuple for an arbitrary combination of result values:
   *
   * \code
   * session.query< boost::tuple<int, int> >("select A.id, B.id from table_a A, table_b B").where("...");
   * \endcode
   *
   * A tuple may combine any kind of object that is supported as a result,
   * including database objects (see also ptr_tuple):
   * \code
   * session.query< boost::tuple<ptr<A>, ptr<B> >("select A, B from table_a A, table_b B").where("...");
   * \endcode
   *
   * The \p BindStrategy specifies how you want to bind parameters to
   * your query (if any).
   *
   * When using \p DynamicBinding (which is the default), you will
   * defer the binding until the query is run. This has the advantage
   * that you can compose the query using helper methods provided in
   * the Query object, you can keep the query around and run the query
   * multiple times, perhaps with different parameter values or to
   * scroll through the query results.
   *
   * When using \p DirectBinding, the query must be specified entirely
   * using the \p sql, and can be run only once. This method does have
   * the benefit of binding parameters directly to the underlying
   * prepared statement.
   *
   * This method uses query_result_traits to unmarshal the query result
   * into the \p Result type.
   */
#ifdef DOXYGEN_ONLY
  template <class Result, typename BindStrategy = DynamicBinding>
#else
  template <class Result, typename BindStrategy>
#endif
    Query<Result, BindStrategy> query(const std::string& sql);

  /*! \brief Executs an Sql command.
   *
   * This executs an Sql command. It differs from query() in that no
   * result is expected from the call.
   *
   * Usage example:
   * \code
   * session.execute("update user set name = ? where name = ?").bind("Bart").bind("Sarah");
   * \endcode
   */
  Call execute(const std::string& sql);

  /*! \brief Creates the database schema.
   *
   * This will create the database schema of the mapped tables. Schema
   * creation will fail if one or more tables already existed.
   *
   * \sa mapClass(), dropTables()
   */
  void createTables();

  /*! \brief Drops the database schema.
   *
   * This will drop the database schema. Dropping the schema will fail
   * if one or more tables did not exist.
   *
   * \sa createTables()
   */
  void dropTables();

  /*! \brief Flushes the session.
   *
   * This flushes all modified objects to the database. This does not
   * commit the transaction.
   *
   * Normally, you need not to call this method as the session is
   * flushed automatically before committing a transaction, or before
   * running a query (to be sure to take into account pending
   * modifications).
   */
  void flush();

  void getFields(const char *tableName, std::vector<FieldInfo>& result);
  
private:
  Session(const Session& s);

  struct DboKey {
    const char *table;
    long long id;

    bool operator<(const DboKey& other) const {
      return id < other.id ? true
	: (id > other.id ? false : table < other.table);
    }

    DboKey(const char *aTable, long long anId)
      : table(aTable),
	id(anId)
    { }

    DboKey()
      : table(0), id(0)
    { }
  };

  typedef std::map<DboKey, MetaDboBase *> Registry;
  typedef std::set<MetaDboBase *> MetaDboBaseSet;

  enum {SqlInsert = 0,
	SqlUpdate = 1,
	SqlDelete = 2,
	SqlDeleteVersioned = 3,
	SqlSelectById = 4,
	FirstSqlSelectSet = 5};

  struct WTDBO_API ClassMappingInfo {
    const char *tableName;
    std::vector<FieldInfo> fields;
    std::vector<std::string> statements;

    virtual ~ClassMappingInfo();
    virtual void createTable(Session& session,
			     std::set<std::string>& tablesCreated) = 0;
    virtual void dropTable(Session& session,
			   std::set<std::string>& tablesDropped) = 0;
    virtual void prepareStatements(Session& session) = 0;
  };

  template <class C>
  struct ClassMapping : public ClassMappingInfo
  {
    virtual void createTable(Session& session,
			     std::set<std::string>& tablesCreated);
    virtual void dropTable(Session& session,
			   std::set<std::string>& tablesDropped);
    virtual void prepareStatements(Session& session);
  };

  typedef std::map<const std::type_info *, ClassMappingInfo *> ClassRegistry;
  typedef std::map<std::string, ClassMappingInfo *> TableRegistry;
  
  ClassRegistry      classRegistry_;
  TableRegistry      tableRegistry_;
  Registry           registry_;
  MetaDboBaseSet     dirtyObjects_;
  SqlConnection     *connection_;
  SqlConnectionPool *connectionPool_;
  Transaction::Impl *transaction_;

  void needsFlush(MetaDboBase *dbo);

  template <class C> ptr<C> load(long long id, SqlStatement *statement,
				 int& column);

  void prune(MetaDboBase *obj);
  template <class C> void prune(MetaDbo<C> *obj);

  template<class C> void implSave(MetaDbo<C>& dbo);
  template<class C> void implDelete(MetaDbo<C>& dbo);
  template<class C> void implTransactionDone(MetaDbo<C>& dbo, bool success);
  template<class C> C *implLoad(MetaDboBase& dbo, SqlStatement *statement,
				int& column);

  static std::string statementId(const char *table, int statementIdx);

  template <class C> SqlStatement *getStatement(int statementIdx);
  SqlStatement *getStatement(const std::string& id);
  SqlStatement *getStatement(const char *tableName, int statementIdx);
  const std::string *getStatementSql(const char *tableName, int statementIdx);

  SqlStatement *prepareStatement(const std::string& id,
				 const std::string& sql);
  SqlStatement *getOrPrepareStatement(const std::string& sql);

  template <class C> void prepareStatements();
  template <class C> std::string manyToManyJoinId(const std::string& joinName,
						  const std::string& notId);

  void doDelete(SqlStatement *statement, long long id, bool withVersion,
		int version);

  SqlConnection *useConnection();
  void returnConnection(SqlConnection *connection);
  SqlConnection *connection();

  template <class C> friend class MetaDbo;
  template <class C> friend class collection;
  template <class C, typename S> friend class Query;
  template <typename V> friend class FieldRef;
  template <class C> friend struct query_result_traits;

  friend class Call;
  friend class Transaction;
  friend struct Transaction::Impl;
  friend class PrepareStatements;
  friend class SaveDbAction;
  friend class LoadDbAction;
  friend class CreateSchema;
  friend class DropSchema;
  friend class MetaDboBase;
};

  }
}

#endif // WT_SESSION_H_
