///////////////////////////////////////////////////////////////////////////////
//                                                                             
//  Copyright (C) 2010-2011  Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>     
//                                                                             
//  Distributed under:
//
//                   the Boost Software License, Version 1.0.
//              (See accompanying file LICENSE_1_0.txt or copy at 
//                     http://www.boost.org/LICENSE_1_0.txt)
//
//  or (at your opinion) under:
//
//                               The MIT License
//                 (See accompanying file MIT.txt or a copy at
//              http://www.opensource.org/licenses/mit-license.php)
//
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPDB_BACKEND_H
#define CPPDB_BACKEND_H
#include <iosfwd>
#include <ctime>
#include <string>
#include <memory>
#include <map>
#include <typeinfo>
#include <cppdb/defs.h>
#include <cppdb/errors.h>
#include <cppdb/ref_ptr.h>
#include <cppdb/connection_specific.h>

// Borland errors about unknown pool-type without this include.
#ifdef __BORLANDC__
#include <cppdb/pool.h>
#endif

namespace cppdb {
	class connection_info;
	// Borland needs pool.h, but not this forward declaration.
	#ifndef __BORLANDC__
	class pool;
	#endif

	///
	/// \brief This namepace includes all classes required to implement a cppdb SQL backend.
	///
	namespace backend {	

		///
		/// \brief This class represents query result.
		///
		/// This object is created by statement::query call, backend developer may assume that this object
		/// will stay alive as long as statement that created it exits, i.e. statement would be destroyed after
		/// result.
		///
		class CPPDB_API result : public ref_counted {
		public:
			
			///
			/// The flag that defines the information about availability of the next row in result
			///
			typedef enum {
				last_row_reached, ///< No more rows exits, next() would return false
				next_row_exists,  ///< There are more rows, next() would return true
				next_row_unknown  ///< It is unknown, next() may return either true or false
			} next_row;

			///
			/// Check if the next row in the result exists. If the DB engine can't perform
			/// this check without loosing data for current row, it should return next_row_unknown.
			///
			virtual next_row has_next() = 0;
			///
			/// Move to next row. Should be called before first access to any of members. If no rows remain
			/// return false, otherwise return true
			///
			virtual bool next() = 0;
			///
			/// Fetch an integer value for column \a col starting from 0.
			///
			/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
			/// can't be converted to integer or its range is not supported by the integer type.
			///
			virtual bool fetch(int col,short &v) = 0;
			///
			/// Fetch an integer value for column \a col starting from 0.
			/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
			///
			/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
			/// can't be converted to integer or its range is not supported by the integer type.
			///
			virtual bool fetch(int col,unsigned short &v) = 0;
			///
			/// Fetch an integer value for column \a col starting from 0.
			/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
			///
			/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
			/// can't be converted to integer or its range is not supported by the integer type.
			///
			virtual bool fetch(int col,int &v) = 0;
			///
			/// Fetch an integer value for column \a col starting from 0.
			/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
			///
			/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
			/// can't be converted to integer or its range is not supported by the integer type.
			///
			virtual bool fetch(int col,unsigned &v) = 0;
			///
			/// Fetch an integer value for column \a col starting from 0.
			/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
			///
			/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
			/// can't be converted to integer or its range is not supported by the integer type.
			///
			virtual bool fetch(int col,long &v) = 0;
			///
			/// Fetch an integer value for column \a col starting from 0.
			/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
			///
			/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
			/// can't be converted to integer or its range is not supported by the integer type.
			///
			virtual bool fetch(int col,unsigned long &v) = 0;
			///
			/// Fetch an integer value for column \a col starting from 0.
			/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
			///
			/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
			/// can't be converted to integer or its range is not supported by the integer type.
			///
			virtual bool fetch(int col,long long &v) = 0;
			///
			/// Fetch an integer value for column \a col starting from 0.
			/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
			///
			/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
			/// can't be converted to integer or its range is not supported by the integer type.
			///
			virtual bool fetch(int col,unsigned long long &v) = 0;
			///
			/// Fetch a floating point value for column \a col starting from 0.
			/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
			///
			/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
			/// can't be converted to floating point value.
			///
			virtual bool fetch(int col,float &v) = 0;
			///
			/// Fetch a floating point value for column \a col starting from 0.
			/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
			///
			/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
			/// can't be converted to floating point value.
			///
			virtual bool fetch(int col,double &v) = 0;
			///
			/// Fetch a floating point value for column \a col starting from 0.
			/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
			///
			/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
			/// can't be converted to floating point value.
			///
			virtual bool fetch(int col,long double &v) = 0;
			///
			/// Fetch a string value for column \a col starting from 0.
			/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
			///
			/// Should throw invalid_column() \a col value is invalid, any data should be convertible to
			/// text value (as formatting integer, floating point value or date-time as string).
			///
			virtual bool fetch(int col,std::string &v) = 0;
			///
			/// Fetch a BLOB value for column \a col starting from 0.
			/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
			///
			/// Should throw invalid_column() \a col value is invalid, any data should be convertible to
			/// BLOB value as text (as formatting integer, floating point value or date-time as string).
			///
			virtual bool fetch(int col,std::ostream &v) = 0;
			///
			/// Fetch a date-time value for column \a col starting from 0.
			/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
			///
			/// Should throw invalid_column() \a col value is invalid. If the data can't be converted
			/// to date-time it should throw bad_value_cast()
			///
			virtual bool fetch(int col,std::tm &v) = 0;
			///
			/// Check if the column \a col is NULL starting from 0, should throw invalid_column() if the index out of range
			///
			virtual bool is_null(int col) = 0;
			///
			/// Return the number of columns in the result. Should be valid even without calling next() first time.
			///
			virtual int cols() = 0;
			///
			/// Return the number of columns by its name. Return -1 if the name is invalid
			/// Should be able to work even without calling next() first time.
			///
			virtual int name_to_column(std::string const &) = 0;
			///
			/// Return the column name for column index starting from 0.
			/// Should throw invalid_column() if the index out of range
			/// Should be able to work even without calling next() first time.
			///
			virtual std::string column_to_name(int) = 0;

			result();
			virtual ~result();
		private:
			struct data;
			std::auto_ptr<data> d;
		};

		class statements_cache;

		///
		/// \brief This class represents a statement that can be either executed or queried for result
		///
		class CPPDB_API statement : public ref_counted {
		public:
			// Begin of API

			///
			/// Reset the prepared statement to initial state as before the operation. It is
			/// called by front-end each time before new query() or exec() are called.
			///
			virtual void reset() = 0;
			///
			/// Get the query the statement works with. Return it as is, used as key for statement
			/// caching 
			///
			virtual std::string const &sql_query() = 0;

			///
			/// Bind a text value to column \a col (starting from 1). You may assume
			/// that the reference remains valid until real call of query() or exec()
			///
			/// Should throw invalid_placeholder() if the value of col is out of range. May
			/// ignore if it is impossible to know whether the placeholder exists without special
			/// support from back-end.
			///
			virtual void bind(int col,std::string const &) = 0;
			///
			/// Bind a text value to column \a col (starting from 1). You may assume
			/// that the reference remains valid until real call of query() or exec()
			///
			/// Should throw invalid_placeholder() if the value of col is out of range. May
			/// ignore if it is impossible to know whether the placeholder exists without special
			/// support from back-end.
			///
			virtual void bind(int col,char const *s) = 0;
			///
			/// Bind a text value to column \a col (starting from 1). You may assume
			/// that the reference remains valid until real call of query() or exec()
			///
			/// Should throw invalid_placeholder() if the value of col is out of range. May
			/// ignore if it is impossible to know whether the placeholder exists without special
			/// support from back-end.
			///
			virtual void bind(int col,char const *b,char const *e) = 0;
			///
			/// Bind a date-time value to column \a col (starting from 1).
			///
			/// Should throw invalid_placeholder() if the value of col is out of range. May
			/// ignore if it is impossible to know whether the placeholder exists without special
			/// support from back-end.
			///
			virtual void bind(int col,std::tm const &) = 0;
			///
			/// Bind a BLOB value to column \a col (starting from 1).
			///
			/// Should throw invalid_placeholder() if the value of col is out of range. May
			/// ignore if it is impossible to know whether the placeholder exists without special
			/// support from back-end.
			///
			virtual void bind(int col,std::istream &) = 0;
			///
			/// Bind an integer value to column \a col (starting from 1).
			///
			/// Should throw invalid_placeholder() if the value of col is out of range. May
			/// ignore if it is impossible to know whether the placeholder exists without special
			/// support from back-end.
			///
			virtual void bind(int col,int v) = 0;
			///
			/// Bind an integer value to column \a col (starting from 1).
			///
			/// Should throw invalid_placeholder() if the value of col is out of range. May
			/// ignore if it is impossible to know whether the placeholder exists without special
			/// support from back-end.
			///
			/// May throw bad_value_cast() if the value out of supported range by the DB. 
			///
			virtual void bind(int col,unsigned v) = 0;
			///
			/// Bind an integer value to column \a col (starting from 1).
			///
			/// Should throw invalid_placeholder() if the value of col is out of range. May
			/// ignore if it is impossible to know whether the placeholder exists without special
			/// support from back-end.
			///
			/// May throw bad_value_cast() if the value out of supported range by the DB. 
			///
			virtual void bind(int col,long v) = 0;
			///
			/// Bind an integer value to column \a col (starting from 1).
			///
			/// Should throw invalid_placeholder() if the value of col is out of range. May
			/// ignore if it is impossible to know whether the placeholder exists without special
			/// support from back-end.
			///
			/// May throw bad_value_cast() if the value out of supported range by the DB. 
			///
			virtual void bind(int col,unsigned long v) = 0;
			///
			/// Bind an integer value to column \a col (starting from 1).
			///
			/// Should throw invalid_placeholder() if the value of col is out of range. May
			/// ignore if it is impossible to know whether the placeholder exists without special
			/// support from back-end.
			///
			/// May throw bad_value_cast() if the value out of supported range by the DB. 
			///
			virtual void bind(int col,long long v) = 0;
			///
			/// Bind an integer value to column \a col (starting from 1).
			///
			/// Should throw invalid_placeholder() if the value of col is out of range. May
			/// ignore if it is impossible to know whether the placeholder exists without special
			/// support from back-end.
			///
			/// May throw bad_value_cast() if the value out of supported range by the DB. 
			///
			virtual void bind(int col,unsigned long long v) = 0;
			///
			/// Bind a floating point value to column \a col (starting from 1).
			///
			/// Should throw invalid_placeholder() if the value of col is out of range. May
			/// ignore if it is impossible to know whether the placeholder exists without special
			/// support from back-end.
			///
			virtual void bind(int col,double v) = 0;
			///
			/// Bind a floating point value to column \a col (starting from 1).
			///
			/// Should throw invalid_placeholder() if the value of col is out of range. May
			/// ignore if it is impossible to know whether the placeholder exists without special
			/// support from back-end.
			///
			virtual void bind(int col,long double v) = 0;
			///
			/// Bind a NULL value to column \a col (starting from 1).
			///
			/// Should throw invalid_placeholder() if the value of col is out of range. May
			/// ignore if it is impossible to know whether the placeholder exists without special
			/// support from back-end.
			///
			virtual void bind_null(int col) = 0;
			///
			/// Fetch the last sequence generated for last inserted row. May use sequence as parameter
			/// if the database uses sequences, should ignore the parameter \a sequence if the last
			/// id is fetched without parameter.
			///
			/// Should be called after exec() for insert statement, otherwise the behavior is undefined.
			///
			/// MUST throw not_supported_by_backend() if such option is not supported by the DB engine.
			///
			virtual long long sequence_last(std::string const &sequence) = 0;
			///
			/// Return the number of affected rows by last statement.
			///
			/// Should be called after exec(), otherwise behavior is undefined.
			///
			virtual unsigned long long affected() = 0;
			///
			/// Return SQL Query result, MAY throw cppdb_error if the statement is not a query
			///
			virtual result *query() = 0;
			///
			/// Execute a statement, MAY throw cppdb_error if the statement returns results.
			///
			virtual void exec() = 0;

			/// \cond INTERNAL 
			// Caching support
			static void dispose(statement *selfp);
			
			void cache(statements_cache *c);
			statement();
			virtual ~statement() ;
			/// \endcond
		private:
			struct data;
			std::auto_ptr<data> d;
			statements_cache *cache_;
		};
	
		/// \cond INTERNAL	
		class CPPDB_API statements_cache {
			statements_cache(statements_cache const &);
			void operator=(statements_cache const &);
		public:
			statements_cache();
			bool active();
			void set_size(size_t n);
			void put(statement *p_in);
			void clear();
			ref_ptr<statement> fetch(std::string const &q);
			~statements_cache();
		private:
			struct data;
			std::auto_ptr<data> d;
		};

		/// \endcond

		class connection;

		///
		/// \brief This class represents a driver that creates connections for 
		/// given connection string, custom drivers can be are installed using this
		/// class
		///
		class CPPDB_API driver : public ref_counted {
			driver(driver const &);
			void operator=(driver const &);
		public:
			driver() {}
			virtual ~driver() {}
			///
			/// Return true if the driver in use (i.e. if there is any open connection exist (connection object)
			/// so it can't be removed from the driver
			///
			virtual bool in_use() = 0;
			///
			/// Create a connection object - should be implemented by driver
			///
			virtual connection *open(connection_info const &cs) = 0;
			///
			/// Create a connection object, generally calls open() but may add some information (as registering objects)
			/// and unregistering them
			///
			virtual connection *connect(connection_info const &cs);
		};
	
		///
		/// \brief This class represents a driver that can be unloaded from the driver_manager.
		///	
		class CPPDB_API loadable_driver : public driver {
			loadable_driver(loadable_driver const &);
			void operator=(loadable_driver const &);
		public:
			loadable_driver() {}
			///
			/// Returns true if any of generated connections still exits
			///
			virtual bool in_use();
			virtual ~loadable_driver() {}

			///
			/// Creates a new connection object and keeps track of them for handing (in_use) correctly
			///
			virtual connection *connect(connection_info const &cs);
		};

		extern "C" {
			///
			/// This function type is the function that is generally resolved from the shared objects when loaded
			///
			typedef cppdb::backend::connection *cppdb_backend_connect_function(connection_info const &ci);
		}


		///
		/// \brief Create a static driver using connection function (usable for statically linking drivers).
		///
		class CPPDB_API static_driver : public driver {
		public:
			///
			/// Typedef of the function pointer that is used for creation of connection objects.
			///
			typedef cppdb_backend_connect_function *connect_function_type;

			///
			/// Create a new driver that creates connection using function \a c
			///
			static_driver(connect_function_type c);
			~static_driver();
			///
			/// Always returns true as this driver cannot be unloaded
			///
			bool in_use();
			///
			/// Create new connection - basically calls the function to create the object
			///
			backend::connection *open(connection_info const &ci);
		private:
			connect_function_type connect_;
		};


		///
		/// \brief this class represents connection to database
		///
		class CPPDB_API connection : public ref_counted {
		public:
			///
			/// Create a new object. Connection information \a info is required
			///
			connection(connection_info const &info);
			virtual ~connection();
			/// \cond INTERNAL
			void set_pool(ref_ptr<pool> p);
			ref_ptr<pool> get_pool(); 
			void set_driver(ref_ptr<loadable_driver> drv);
			static void dispose(connection *c);
			ref_ptr<statement> prepare(std::string const &q);
			ref_ptr<statement> get_prepared_statement(std::string const &q);
			ref_ptr<statement> get_prepared_uncached_statement(std::string const &q);
			ref_ptr<statement> get_statement(std::string const &q);
			/// \endcond 

			// API 

			///
			/// Start new isolated transaction. Would not be called
			/// withing other transaction on current connection.
			///
			virtual void begin() = 0;
			///
			/// Commit the transaction, you may assume that is called after begin()
			/// was called.
			///
			virtual void commit() = 0;
			///
			/// Rollback the transaction. MUST never throw!!!
			///
			virtual void rollback() = 0;
			///
			/// Create a prepared statement \a q. May throw if preparation had failed.
			/// Should never return null value.
			///
			virtual statement *prepare_statement(std::string const &q) = 0;
			///
			/// Create a (unprepared) statement \a q. May throw if preparation had failed.
			/// Should never return null value.
			///
			virtual statement *create_statement(std::string const &q) = 0;
			///
			/// Escape a string for inclusion in SQL query. May throw not_supported_by_backend() if not supported by backend.
			///
			virtual std::string escape(std::string const &) = 0;
			///
			/// Escape a string for inclusion in SQL query. May throw not_supported_by_backend() if not supported by backend.
			///
			virtual std::string escape(char const *s) = 0;
			///
			/// Escape a string for inclusion in SQL query. May throw not_supported_by_backend() if not supported by backend.
			///
			virtual std::string escape(char const *b,char const *e) = 0;
			///
			/// Get the name of the driver, for example sqlite3, odbc
			///
			virtual std::string driver() = 0;
			///
			/// Get the name of the SQL Server, for example sqlite3, mssql, oracle, differs from driver() when
			/// the backend supports multiple databases like odbc backend.
			///
			virtual std::string engine() = 0;

			///
			/// Clear statements cache
			///
			void clear_cache();

			///
			/// Check if session specific preparations are done
			///
			/// For new connections always false
			///
			bool once_called() const;
			
			///
			/// Set once status - true if called flase 
			/// 
			void once_called(bool v);

			///
			/// Get connection specific data pointer of the type \a type , default 0.
			///
			/// The ownership is not changed
			///
			connection_specific_data *connection_specific_get(std::type_info const &type) const;
			///
			/// Release ownership connection specific data pointer of the type \a type
			///
			connection_specific_data *connection_specific_release(std::type_info const &type);
			///
			/// Remove old connection specific data and set new one for a given
			/// type \a type , the ownership on \a p is transferred to connection.
			///
			void connection_specific_reset(std::type_info const &type,connection_specific_data *p = 0);

			///
			/// Check if this back-end can be recycled for reuse in a pool.
			///
			/// If an exception is thrown during operation on DB this flag is reset
			/// to false by the front-end classes result, statement, session.
			///
			/// Default is true 
			/// 
			bool recyclable();
			
			///
			/// Set recyclable state of the session. If some problem occurs on connection
			/// that prevents its reuse it should be called with false parameter.
			/// 
			void recyclable(bool value);

		private:

			struct data;
			std::auto_ptr<data> d;
			statements_cache cache_;
			ref_ptr<loadable_driver> driver_;
			ref_ptr<pool> pool_;
			unsigned default_is_prepared_ : 1;
			unsigned once_called_ : 1;
			unsigned recyclable_ : 1;
			unsigned reserverd_ : 29;
		};

	} // backend
} // cppdb

#endif
