/*
 * statement.hpp
 *
 * Copyright (C) 2009 Tim Marston <edam@waxworlds.org>
 *
 * This file is part of sqlitepp (hereafter referred to as "this program").
 * See http://www.waxworlds.org/edam/software/sqlitepp for more information.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef STATEMENT_HPP_
#define STATEMENT_HPP_


#include <sqlite3.h>
#include <boost/utility.hpp>
#include <boost/lexical_cast.hpp>
#include <sqlitepp/exception.hpp>


namespace sqlite
{


class database;


struct _null_t { };
struct _exec_t { };
struct _set_index_t { unsigned int _index; };

/**
 * Auto-binding manipulator, for use with a statment's stream operator. This
 * specifies a NULL value to bind to a parameter.
 */
extern const _null_t null;

/**
 * Auto-binding manipulator, for use with a statment's stream operator. This
 * indicates that the statement should be executed immediately. Unlike a
 * statment's exec() method, this will throw on error. Also, it will throw if
 * the execution returns any result rows.
 */
extern const _exec_t exec;

/**
 * Auto-binding manipulator, for use with a statment's stream operator. This
 * manipulator sets the index used to automatically assign values to parameters.
 * @param index the new index for incremental assignment
 */
_set_index_t set_index(
	unsigned int index );


/**
 * The statement class represents an SQL statement. It is the bas class for both
 * the command and the query classes which should be used for those purposes.
 * The statement class its self has protected instantiation.
 */
class statement
	:
	private boost::noncopyable
{
//______________________________________________________________________________
//                                                                 instantiation
protected:

	/**
	 * Constructor that provides a database upon which to act and the SQL
	 * statement.
	 * @param database a reference to the database
	 * @param sql an SQL statement in UTF-8
	 */
	statement(
		database &database,
		const std::string &sql );

	virtual ~statement() throw( );

//______________________________________________________________________________
//                                                              public interface
public:

	/**
	 * Prepare an SQL statement.
	 * @param sql an SQL statement in UTF-8
	 * @returns an sqlite error code
	 * @see sqlite3_prepare_v2()
	 */
	int prepare(
		const std::string &sql );

	/**
	 * Step through one execution cycle of the SQL statement. If this is an SQL
	 * statement that doesn't return any rows, only one cycle is required,
	 * otherwise, each cycle will return another row
	 * @return an sqlite error code
	 * @see sqlite3_step()
	 */
	int step();

	/**
	 * Reset the statement, ready to re-execute it. This does not clear any of
	 * the values bound to the statement.
	 * @returns an sqlite error code
	 * @see sqlite3_reset()
	 */
	int reset();

	/**
	 * Clears the values bound to a statement to NULL.
	 * @returns an sqlite error code
	 * @see sqlite3_clear_bindings()
	 */
	int clear_bindings();

	/**
	 * Bind a value to the SQL statement via it's index. This template will take
	 * a variety of data types and bind them as text. This is how sqlite
	 * internally stores the data anyway, so always binding as text just means
	 * we do the conversion instead of sqlite and is no less efficient.
	 * @param index the index of the parameter to bind to
	 * @param value the value to bind
	 * @returns an sqlite error code
	 * @see sqlite3_bind_text()
	 */
	template< class T >
	int bind(
		unsigned int index,
		T value )
	{
		std::string string_value = boost::lexical_cast< std::string >( value );
		return sqlite3_bind_text( _handle, index, string_value.c_str(),
			string_value.length(), SQLITE_TRANSIENT );
	}

	/**
	 * Bind a string value to the SQL statement via it's index where the value
	 * of that string will not change for the duration of the statement. This is
	 * more optimal because sqlite will not have to make it's own copy of the
	 * data.
	 * @param index the index of the parameter to bind to
	 * @param value the invariant string value
	 * @returns an sqlite error code
	 * @see sqlite3_bind_text()
	 */
	int bind_static(
		unsigned int index,
		const char *value,
		unsigned int value_length );

	/**
	 * Bind a string value to the SQL statement via it's index where the value
	 * of that string will not change for the duration of the statement. This is
	 * more optimal  because sqlite will not have to make it's own copy of the
	 * data.
	 * @param index the index of the parameter to bind to
	 * @param value the invariant string value
	 * @returns an sqlite error code
	 * @see sqlite3_bind_text()
	 */
	int bind_static(
		unsigned int index,
		const char *value );

	/**
	 * Bind a string value to the SQL statement via it's index where the value
	 * of that string will not change for the duration of the statement. This is
	 * more optimal because sqlite will not have to make it's own copy of the
	 * data.
	 * @param index the index of the parameter to bind to
	 * @param value the invariant string value
	 * @returns an sqlite error code
	 * @see sqlite3_bind_text()
	 */
	int bind_static(
		unsigned int index,
		const std::string &value );

	/**
	 * Bind a NULL value to the SQL statement via it's index.
	 * @param index the index of the parameter to bind to
	 * @returns an sqlite error code
	 * @see sqlite3_bind_null()
	 */
	int bind_null(
		unsigned int index );

	/**
	 * Bind a value to the SQL statement via a named parameter. This template
	 * will take a variety of data types and bind them as text. This is how
	 * sqlite internally stores the data anyway, so always binding as text just
	 * means we do the conversion instead of sqlite and is no less efficient.
	 * @param name the named parameter to bind to
	 * @param value the value to bind
	 * @returns an sqlite error code
	 * @see sqlite3_bind_text()
	 */
	template< class T >
	int bind(
		const std::string &name,
		T value )
	{
		unsigned int index =
			sqlite3_bind_parameter_index( _handle, name.c_str() );
		return bind( index, value );
	}

	/**
	 * Bind a string value to the SQL statement via a named parameter where the
	 * string value will not change for the duration of the statement. This
	 * prevents a copy of the string being taken.
	 * @param name the named parameter to bind to
	 * @param value the invariant string value
	 * @returns an sqlite error code
	 * @see sqlite3_bind_text()
	 */
	int bind_static(
		const std::string &name,
		const char *value,
		unsigned int value_length );

	/**
	 * Bind a string value to the SQL statement via a named parameter where the
	 * string value will not change for the duration of the statement. This
	 * prevents a copy of the string being taken.
	 * @param name the named parameter to bind to
	 * @param value the invariant string value
	 * @returns an sqlite error code
	 * @see sqlite3_bind_text()
	 */
	int bind_static(
		const std::string &name,
		const char *value );

	/**
	 * Bind a string value to the SQL statement via a named parameter where the
	 * string value will not change for the duration of the statement. This
	 * prevents a copy of the string being taken.
	 * @param name the named parameter to bind to
	 * @param value the invariant string value
	 * @returns an sqlite error code
	 * @see sqlite3_bind_text()
	 */
	int bind_static(
		const std::string &name,
		const std::string &value );

	/**
	 * Bind a NULL value to the SQL statement via a named parameter.
	 * @param name the named parameter to bind to
	 * @returns an sqlite error code
	 * @see sqlite3_bind_null()
	 */
	int bind_null(
		const std::string &name );

	/**
	 * Stream operator is used to bind values to parameters automatically, in
	 * ascending order. In addition, the null, execute and set_index() auto-
	 * binding manipulators can be used.
	 * @param value a value to bind
	 */
	template< class T >
	statement &operator <<( T value )
	{
		int error_code = bind( _bind_index, value );
		if( error_code != SQLITE_OK ) throw sqlite_error( error_code );
		_bind_index++;
		return *this;
	}

//______________________________________________________________________________
//                                                                implementation
protected:

	/**
	 * Finalise an SQL statement.
	 * @returns an sqlite error code
	 * @see sqlite3_finalize()
	 */
	int finalize();

private:

	/** the database upon which to act */
	database &_database;

	/** the statement handle */
	sqlite3_stmt *_handle;

	/** index used when auto-binding */
	unsigned int _bind_index;

};


// template specialisations for statement::operator <<()
template< >
statement& statement::operator << < _null_t >(
	_null_t );
template< >
statement& statement::operator << < _exec_t >(
	_exec_t );
template< >
statement& statement::operator << < _set_index_t >(
	_set_index_t t );


}


#endif /* STATEMENT_HPP_ */
