//
// 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 SqlTableCreator.h
 * @author Ronald Kluth
 * @date created at 2009/07/30
 * @brief Declaration and implementation of class SqlTableCreator
 * @since 0.1
 */

#ifndef LOG_DETAIL_SQLTABLECREATOR_INCLUDED
#define LOG_DETAIL_SQLTABLECREATOR_INCLUDED

#include <Poco/Data/Session.h>
#include <Poco/Data/Statement.h>

#include <string>
#ifdef _MSC_VER
#include <array>
#else
#include <tr1/array>
#endif

#include <vector>

namespace Log {
namespace Detail {

/**
 * @brief Helper class to build SQL CREATE TABLE statements
 */
class SqlTableCreator
{
public:
	/// Construction
	SqlTableCreator( std::string const& tableName, Poco::Data::Session& session )
	:	session_( session )
	,	tableName_( tableName )
	,	executed_( false )
	{}

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

	/// Add a table column to the statement with its corresponding SQL data type
	SqlTableCreator& column( std::string const& columnName, std::string const& sqlDataType )
	{
		if( ! isValidColumnName( columnName ) )
		{
			// prevent automatic execution in destructor and throw error
			executed_ = true;
			throw std::runtime_error( std::string( "SqlTableCreator::column():"
					"invalid column name: " ) + columnName );
		}

		if( ! columnDefs_.empty() )
		{
			columnDefs_.append( 1, ',' );
		}
		columnDefs_.append( columnName + " " + sqlDataType );
		return *this;
	}

	/// Add table columns according to the given info type list and corresponding SQL types
	template < typename InfoListT >
	SqlTableCreator& columns(
			std::tr1::array< std::string, SizeOf< InfoListT >::value > const& sqlDataTypes )
	{
		if( SizeOf< InfoListT >::value > 0 )
		{
			try
			{
				ColumnLoop<
					InfoListT,
					SizeOf< InfoListT >::value - 1
				>::call( columnDefs_, sqlDataTypes );
			}
			catch( std::runtime_error const& )
			{
				executed_ = true;
				throw;
			}
		}
		return *this;
	}

	/// Add a table contraint specifying the primary key
	SqlTableCreator& primaryKey( std::string const& columnName )
	{
		tableConstraints_.append( ",PRIMARY KEY(" );
		tableConstraints_.append( columnName );
		tableConstraints_.append( ")" );
		return *this;
	}

	/// Execute the consructed statement, which creates the table in the database
	void execute()
	{
		if( ! executed_ )
		{
			// create the statement and add text
			session_.begin();

			Poco::Data::Statement stmt( session_ );
			stmt << "CREATE TABLE " << tableName_ << " ("
				<< columnDefs_
				<< tableConstraints_
				<< ")";

			stmt.execute();

			session_.commit();
			executed_ = true;
		}
	}

private:
	/// Reference to the database session
	Poco::Data::Session& session_;
	/// Stores the table name where data is inserted
	std::string tableName_;
	/// Stores the column names and sql data types
	std::string columnDefs_;
	/// Stores the table constraints
	std::string tableConstraints_;
	/// Stores whether the statement has been executed yet
	bool executed_;

private:

	/// Check if a column name contains spaces, which would create invalid SQL
	static bool isValidColumnName( std::string const& columnName )
	{
		std::size_t found = columnName.find_first_of( " " );
		return found == std::string::npos;
	}

	/// 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& columnDefs,
				std::tr1::array< std::string, SizeOf< InfoListT >::value > const& sqlDataTypes )
		{
			// recurse first
			ColumnLoop< InfoListT, Index - 1 >::call( columnDefs, sqlDataTypes );

			// check and add column data
			typedef typename TypeOf< InfoListT, Index >::type CurrentType;
			std::string columnName = CurrentType::getName().c_str();
			if( ! isValidColumnName( columnName ) )
			{
				// prevent automatic execution in destructor and throw error
				throw std::runtime_error( std::string( "SqlTableCreator::ColumnLoop::call():"
						"invalid column name: " ) + columnName );
			}
			columnDefs.append( "," );
			columnDefs.append( columnName );
			columnDefs.append( " " );
			columnDefs.append( sqlDataTypes[ Index ] );
		}
	};

#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& columnDefs,
				std::tr1::array< std::string, SizeOf< InfoListT >::value > const& sqlDataTypes )
		{
			// end recursion, add first column data
			// check and add column data
			typedef typename TypeOf< InfoListT, 0 >::type CurrentType;
			std::string columnName = CurrentType::getName().c_str();
			if( ! isValidColumnName( columnName ) )
			{
				// prevent automatic execution in destructor and throw error
				throw std::runtime_error( std::string( "SqlTableCreator::ColumnLoop::call():"
						"invalid column name: " ) + columnName );
			}

			if( ! columnDefs.empty() )
			{
				columnDefs.append( "," );
			}
			columnDefs.append( columnName );
			columnDefs.append( " " );
			columnDefs.append( sqlDataTypes[ 0 ] );
		}
	};
};

} } // namespace Log::Detail

#endif /* LOG_DETAIL_SQLTABLECREATOR_INCLUDED */
