//
// Copyright (c) 2009-2010 Ronald Kluth
//
// 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)
//

/**
 * @file SqlInserter.h
 * @author Ronald Kluth
 * @date created at 2009/07/30
 * @brief Declaration and implementation of class SqlInserter
 * @since 0.1
 */

#ifndef LOG_DETAIL_SQLINSERTER_INCLUDED
#define LOG_DETAIL_SQLINSERTER_INCLUDED

#include "string_literal.hpp"
#include "Bindings.h"

#include <Poco/Data/Statement.h>
#include <Poco/Data/StatementImpl.h>
#include <Poco/Data/AbstractBinding.h>

namespace Log {
namespace Detail {

/**
 * @brief Helper class to build SQL INSERT INTO TABLE statements
 */
class SqlInserter
{
private:
	/// String literal type to wrap C character arrays
	typedef boost::log::string_literal StringLiteral;

/// FIXME Hacking around Poco fixupBinding and bindings being protected.
        class LogStatementImpl : public Poco::Data::StatementImpl {
                public:
                void fixupBinding () {
                        Poco::Data::StatementImpl::fixupBinding ();
                }

		Poco::Data::AbstractBindingVec& bindings() {
			return Poco::Data::StatementImpl::bindings();
		}
        };

	class LogStatement: public Poco::Data::Statement {
		public:
			LogStatement(Poco::Data::Session& session) : Poco::Data::Statement(session) 
			{}

			void fixupBinding () {
				LogStatementImpl& lsi = (LogStatementImpl&) *impl();
				lsi.fixupBinding();
			}

			Poco::Data::AbstractBindingVec& bindings () {
				LogStatementImpl& lsi = (LogStatementImpl&) *impl();
				return  lsi.bindings();
			}
	};
	
public:
	/// Construction without table name, to be used with DatabaseAccessor::createInserter
	SqlInserter( Poco::Data::Session& session, bool autoExec = false )
	:	session_( &session )
	,	stmt_( session )
	,	executed_( false )
	,	autoExec_( autoExec )
	{}

	/// Construction with table name, used for temporary inserter objects
	SqlInserter( std::string const& tableName, Poco::Data::Session& session, bool autoExec = false )
	:	session_( &session )
	,	stmt_( session )
	,	tableName_( tableName )
	,	executed_( false )
	,	autoExec_( autoExec )
	{}

	/// Destruction, executes the statement if necessary
	~SqlInserter()
	{
		if( ! executed_ && autoExec_ )
		{
			execute();
		}
	}

	/// Set the table of an inserter object
	SqlInserter& table( std::string const& tableName )
	{
		executed_ = false;
		tableName_ = tableName;
		return *this;
	}

	/// Add a column and a placeholder to the SQL statement
	SqlInserter& column( std::string const& columnName )
	{
		executed_ = false;
		// add a comma if not the first value
		if( ! columnNames_.empty() )
		{
			columnNames_.append( 1, ',' );
			placeholders_.append( 1, ',' );
		}
		// store the column name and a placeholder
		columnNames_.append( columnName );
		placeholders_.append( 1, '?' );
		return *this;
	}

	/// Add multiple columns and placeholders, according to the info type list
	template < typename InfoListT >
	SqlInserter& columns()
	{
		executed_ = false;
		if( SizeOf< InfoListT >::value > 0 )
		{
			ColumnLoop<
				InfoListT,
				SizeOf< InfoListT >::value - 1
			>::call( columnNames_, placeholders_ );
		}
		return *this;
	}

	/// Bind a value that corresponds to a placeholder in the SQL statement
	template < typename ValueT >
	SqlInserter& value( ValueT const& val )
	{
		executed_ = false;
		columnBindings_.push_back( Poco::Data::Keywords::useRef( val ) );
		return *this;
	}

	/// Bind multiple values from a Record object that correspond to placeholders
	template < typename InfoListT, typename RecordT >
	SqlInserter& values( RecordT const& record )
	{
		executed_ = false;
		ValueAdder addBindings( columnBindings_ );
		record.template iterateInfo< InfoListT >( addBindings );
		return *this;
	}

	/// Add a column and a placeholder and bind the given value to it
	template < typename ValueT >
	SqlInserter& columnValue( std::string const& columnName, ValueT const& val )
	{
		executed_ = false;
		// add a comma if not the first value
		if( ! columnNames_.empty() )
		{
			columnNames_.append( 1, ',' );
			placeholders_.append( 1, ',' );
		}
		// store the column name and a placeholder
		columnNames_.append( columnName );
		placeholders_.append( 1, '?' );
		// store the data
		columnBindings_.push_back( Poco::Data::Keywords::useRef( val ) );
		return *this;
	}

	/// Add multiple columns and placeholders and bind the Record's stored values to them
	template < typename InfoListT, typename RecordT >
	SqlInserter& columnValues( RecordT const& record )
	{
		executed_ = false;
		NameValueAdder addColumns( columnNames_, placeholders_, columnBindings_ );
		record.template iterateInfo< InfoListT >( addColumns );
		return *this;
	}

	/// Execute the constructed SQL statement
	void execute()
	{
		// create the SQL command if it has not been constructed yet
		if( sql_.empty() )
		{
			makeSql();
			stmt_ << sql_;
			stmt_.addBinding( columnBindings_, false );
		}
		else
		{
			stmt_.addBinding( columnBindings_, false );
			stmt_.fixupBinding();
		}
		// execute the statement and reset bindings
		stmt_.execute();
		if( ! stmt_.done() )
		{
			throw std::runtime_error( "SqlInserter::execute(): "
					"statement not done after calling execute" );
		}
		executed_ = true;
		resetBindings();
	}

	std::string getSql()
	{
		if( sql_.empty() )
		{
			makeSql();
		}
		std::string sqlcopy = sql_;
		sql_.clear();
		return sqlcopy;
	}

	std::size_t getBindingCount() const
	{
		return columnBindings_.size();
	}

	bool isExecuted() const
	{
		return executed_;
	}

private:
	/// Vector type to hold info value bindings
	typedef Poco::Data::AbstractBindingVec BindingVec;

	/// Reference to the database session
	Poco::Data::Session* session_;
	/// Reusable statement object, reset after every execution
	LogStatement stmt_;
	/// Stores the SQL insert command for fast inserter reuse
	std::string sql_;
	/// Stores the table name where data is inserted
	std::string tableName_;
	/// Stores the column names for which data is provided
	std::string columnNames_;
	/// Stores the same amount of place holders as column names
	std::string placeholders_;
	/// Stores value bindings to column data to be inserted
	BindingVec columnBindings_;
	/// Stores whether the statement has been executed with the current bindings
	bool executed_;
	/// Stores whether the statement shall be executed automatically during destruction
	bool autoExec_;

private:

	/// Create an SQL string from the accumulated column data (names, placeholders)
	void makeSql()
	{
		sql_.append( "INSERT INTO " );
		sql_.append( tableName_ );
		sql_.append( "(" );
		sql_.append( columnNames_ );
		sql_.append( ") VALUES(" );
		sql_.append( placeholders_ );
		sql_.append( ")" );
	}

	/// Clear the binding vector (after executing the SQL insert)
	void resetBindings()
	{
		columnBindings_.clear();
		stmt_.bindings().clear();
	}

private:
	/// Loop type to iterate over an info list, adding column definitions to a string
	template < typename InfoListT, int Index >
	struct ColumnLoop
	{
		static void call( std::string& columnNames, std::string& placeholders )
		{
			// recurse first
			ColumnLoop< InfoListT, Index - 1 >::call( columnNames, placeholders );

			// add column data
			typedef typename TypeOf< InfoListT, Index >::type CurrentType;
			columnNames.append( 1, ',' );
			columnNames.append( CurrentType::getName().c_str() );
			placeholders.append( ",?" );
		}
	};

#ifndef DOXYGEN_SKIP
	/// Partial specialization of ColumnLoop, stops the info list recursion
#endif
	template < typename InfoListT >
	struct ColumnLoop< InfoListT, 0 >
	{
		static void call( std::string& columnNames, std::string& placeholders )
		{
			// end recursion, add first column data
			typedef typename TypeOf< InfoListT, 0 >::type CurrentType;
			if( ! columnNames.empty() )
			{
				columnNames.append( 1, ',' );
				placeholders.append( 1, ',' );
			}
			columnNames.append( CurrentType::getName().c_str() );
			placeholders.append( 1, '?' );
		}
	};

	/// Functor for adding record info value bindings
	struct ValueAdder
	{
		BindingVec& bindings_;

		ValueAdder( BindingVec& bindings )
		:	bindings_( bindings )
		{}

		// overload for StringLiteral value
		void operator()( StringLiteral const& name, StringLiteral const& val, bool found )
		{
			if( found )
			{
				// must use string wrapper here because ODBC bind is not implemented for const char*
				bindings_.push_back( Poco::Data::Keywords::bind( std::string( val.c_str() ) ) );
			}
			else
			{
				bindings_.push_back( Poco::Data::Keywords::useRef( Poco::Data::Keywords::null ) );
			}
		}

		template < typename InfoValueT >
		void operator()( StringLiteral const& name, InfoValueT const& val, bool found )
		{
			if( found )
			{
				bindings_.push_back( Poco::Data::Keywords::useRef( val ) );
			}
			else
			{
				bindings_.push_back( Poco::Data::Keywords::useRef( Poco::Data::Keywords::null ) );
			}
		}
	};

	/// Functor for adding record info names, placeholders, and values
	struct NameValueAdder
	{
		std::string& names_;
		std::string& placeholders_;
		BindingVec& bindings_;

		NameValueAdder( std::string& names, std::string& placeholders, BindingVec& bindings )
		:	names_( names )
		,	placeholders_( placeholders )
		,	bindings_( bindings )
		{}

		// overload for StringLiteral value
		void operator()( StringLiteral const& name, StringLiteral const& val, bool found )
		{
			if( ! names_.empty() )
			{
				names_.append( 1, ',' );
				placeholders_.append( 1, ',' );
			}
			names_.append( name.c_str() );
			placeholders_.append( 1, '?' );
			if( found )
			{
				bindings_.push_back( Poco::Data::Keywords::bind( std::string( val.c_str() ) ) );
			}
			else
			{
				bindings_.push_back( Poco::Data::Keywords::useRef( Poco::Data::Keywords::null ) );
			}
		}

		template < typename InfoValueT >
		void operator()( StringLiteral const& name, InfoValueT const& val, bool found )
		{
			if( ! names_.empty() )
			{
				names_.append( 1, ',' );
				placeholders_.append( 1, ',' );
			}
			names_.append( name.c_str() );
			placeholders_.append( 1, '?' );
			if( found )
			{
				bindings_.push_back( Poco::Data::Keywords::useRef( val ) );
			}
			else
			{
				bindings_.push_back( Poco::Data::Keywords::useRef( Poco::Data::Keywords::null ) );
			}
		}
	};
};

} } // namespace Log::Detail

#endif /* LOG_DETAIL_SQLINSERTER_INCLUDED */
