///////////////////////////////////////////////////////////////////////////////
//                                                                             
//  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)
//
///////////////////////////////////////////////////////////////////////////////
#define CPPDB_SOURCE
#include <cppdb/backend.h>
#include <cppdb/utils.h>
#include <cppdb/pool.h>

#include <map>
#include <list>

namespace cppdb {
	namespace backend {
		//result
		struct result::data {};
		result::result() {}
		result::~result() {}
		
		//statement
		struct statement::data {};

		statement::statement() : cache_(0) 
		{
		}
		statement::~statement()
		{
		}
		void statement::cache(statements_cache *c)
		{
			cache_ = c;
		}

		void statement::dispose(statement *p)
		{
			if(!p)
				return;
			statements_cache *cache = p->cache_;
			p->cache_ = 0;
			if(cache) 
				cache->put(p);
			else
				delete p;
		}
		

		//statements cache//////////////

		struct statements_cache::data {

			data() : 
				size(0),
				max_size(0) 
			{
			}

			struct entry;
			typedef std::map<std::string,entry> statements_type;
			typedef std::list<statements_type::iterator> lru_type;
			struct entry {
				ref_ptr<statement> stat;
				lru_type::iterator lru_ptr;
			};
			
			statements_type statements;
			lru_type lru;
			size_t size;
			size_t max_size;


			void insert(ref_ptr<statement> st)
			{
				statements_type::iterator p;
				if((p=statements.find(st->sql_query()))!=statements.end()) {
					p->second.stat = st;
					lru.erase(p->second.lru_ptr);
					lru.push_front(p);
					p->second.lru_ptr = lru.begin();
				}
				else {
					if(size > 0 && size >= max_size) {
						statements.erase(lru.back());
						lru.pop_back();
						size--;
					}
					std::pair<statements_type::iterator,bool> ins = 
						statements.insert(std::make_pair(st->sql_query(),entry()));
					p = ins.first;
					p->second.stat = st;
					lru.push_front(p);
					p->second.lru_ptr = lru.begin();
					size ++;
				}
			}

			ref_ptr<statement> fetch(std::string const &query)
			{
				ref_ptr<statement> st;
				statements_type::iterator p = statements.find(query);
				if(p==statements.end())
					return st;
				st=p->second.stat;
				lru.erase(p->second.lru_ptr);
				statements.erase(p);
				size --;
				return st;
			}

			void clear()
			{
				lru.clear();
				statements.clear();
				size=0;
			}
		}; // data

		statements_cache::statements_cache() 
		{
		}
		void statements_cache::set_size(size_t n)
		{
			if(n!=0 && !active()) {
				d.reset(new data());
				d->max_size = n;
			}
		}
		void statements_cache::put(statement *p_in)
		{
			if(!active()) {
				delete p_in;
			}
			ref_ptr<statement> p(p_in);
			p->reset();
			d->insert(p);
		}
		ref_ptr<statement> statements_cache::fetch(std::string const &q)
		{
			if(!active())
				return 0;
			return d->fetch(q);
		}
		void statements_cache::clear()
		{
			d->clear();
		}
		statements_cache::~statements_cache()
		{
		}

		bool statements_cache::active()
		{
			return d.get()!=0;
		}

		//////////////
		//connection
		//////////////
		
		struct connection::data {
			typedef std::list<connection_specific_data *> conn_specific_type;
			conn_specific_type conn_specific;
			~data()
			{
				for(conn_specific_type::iterator p=conn_specific.begin();p!=conn_specific.end();++p)
					delete *p;
			}
		};
		ref_ptr<statement> connection::prepare(std::string const &q) 
		{
			if(default_is_prepared_)
				return get_prepared_statement(q);
			else
				return get_statement(q);
		}
		
		ref_ptr<statement> connection::get_statement(std::string const &q)
		{
			ref_ptr<statement> st = create_statement(q);
			return st;
		}

		ref_ptr<statement> connection::get_prepared_statement(std::string const &q)
		{
			ref_ptr<statement> st;
			if(!cache_.active()) {
				st = prepare_statement(q);
				return st;
			}
			st = cache_.fetch(q);
			if(!st)
				st = prepare_statement(q);
			st->cache(&cache_);
			return st;
		}

		ref_ptr<statement> connection::get_prepared_uncached_statement(std::string const &q)
		{
			ref_ptr<statement> st = prepare_statement(q);
			return st;
		}

		connection::connection(connection_info const &info) :
			d(new connection::data),
			pool_(0),
			once_called_(0),
			recyclable_(1)
		{
			int cache_size = info.get("@stmt_cache_size",64);
			if(cache_size > 0) {
				cache_.set_size(cache_size);
			}
			std::string def_is_prep = info.get("@use_prepared","on");
			if(def_is_prep == "on")
				default_is_prepared_ = 1;
			else if(def_is_prep == "off") 
				default_is_prepared_ = 0;
			else
				throw cppdb_error("cppdb::backend::connection: @use_prepared should be either 'on' or 'off'");
		}
		connection::~connection()
		{
		}

		bool connection::once_called() const
		{
			return once_called_;
		}
		void connection::once_called(bool v)
		{
			once_called_ = v;
		}
		connection_specific_data *connection::connection_specific_get(std::type_info const &type) const
		{
			for(data::conn_specific_type::const_iterator p=d->conn_specific.begin();p!=d->conn_specific.end();++p) {
				if(typeid(**p) == type)
					return *p;
			}
			return 0;
		}
		connection_specific_data *connection::connection_specific_release(std::type_info const &type)
		{
			for(data::conn_specific_type::iterator p=d->conn_specific.begin();p!=d->conn_specific.end();++p) {
				if(typeid(**p) == type) {
					connection_specific_data *ptr = *p;
					d->conn_specific.erase(p);
					return ptr;
				}
			}
			return 0;
		}
		void connection::connection_specific_reset(std::type_info const &type,connection_specific_data *ptr)
		{
			std::auto_ptr<connection_specific_data> tmp(ptr);
			if(ptr && typeid(*ptr)!=type) {
				throw cppdb_error(
					std::string("cppdb::connection_specific::Inconsistent pointer type")
					+ typeid(*ptr).name() 
					+ " and std::type_info reference:"
					+ type.name()
				);
			}
			for(data::conn_specific_type::iterator p=d->conn_specific.begin();p!=d->conn_specific.end();++p) {
				if(typeid(**p) == type) {
					delete *p;
					if(ptr)
						*p = tmp.release();
					else
						d->conn_specific.erase(p);
					return;
				}
			}
			if(ptr) {
				d->conn_specific.push_back(0);
				d->conn_specific.back() = tmp.release();
			}
		}

		ref_ptr<pool> connection::get_pool()
		{
			return pool_;
		}
		void connection::set_pool(ref_ptr<pool> p)
		{
			pool_ = p;
		}
		void connection::set_driver(ref_ptr<loadable_driver> p)
		{
			driver_ = p;
		}
		void connection::clear_cache()
		{
			cache_.clear();
		}

		void connection::recyclable(bool opt)
		{
			recyclable_ = opt;
		}

		bool connection::recyclable()
		{
			return recyclable_;
		}
		
		void connection::dispose(connection *c)
		{
			if(!c)
				return;
			ref_ptr<pool> p = c->pool_;
			c->pool_ = 0;
			if(p && c->recyclable())
				p->put(c);
			else {
				c->clear_cache();
				// Make sure that driver would not be
				// destoryed destructor of connection exits
				ref_ptr<loadable_driver> driver = c->driver_;
				delete c;
				driver.reset();
			}
		}
		
		connection *driver::connect(connection_info const &cs)
		{
			return open(cs);
		}
		bool loadable_driver::in_use()
		{
			return use_count() > 1;
		}
		connection *loadable_driver::connect(connection_info const &cs)
		{
			connection *c = open(cs);
			c->set_driver(ref_ptr<loadable_driver>(this));
			return c;
		}

		static_driver::static_driver(connect_function_type c) : connect_(c)
		{
		}
		static_driver::~static_driver()
		{
		}
		bool static_driver::in_use()
		{
			return true;
		}
		backend::connection *static_driver::open(connection_info const &ci)
		{
			return connect_(ci);
		}

	} // backend

	struct connection_specific_data::data {};

	connection_specific_data::connection_specific_data()
	{
	}
	connection_specific_data::~connection_specific_data()
	{
	}
	
	
} // cppdb


