//
// 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 src/Example_Consumer_Sqlite.cpp
 * @author Ronald Kluth
 * @date 2009/11/28
 * @brief Example showing the usage of DatabaseAccessor as consumer member
 * @since 0.1
 */

/**
 * @example src/Example_Consumer_Sqlite.cpp
 *
 * This example demonstrates how to write a custom consumer class that
 * logs records to an SQLite database. It makes use of the library's
 * default @c Record type to transport arbitrary information in log records.
 * These so-called info types @c I1, @c I2, and @c I3 will be stored in table 
 * columns named like the string parameter provided in their declaration with 
 * the macro @c CPPLOG_DECLARE_INFO_TYPE. The subsequently declared list of 
 * info types @c Infos is used to iterate over the info types during database
 * table creation and the insertion of records.
 *
 * The consumer class @c SqliteWriter is initialized with a database file name 
 * and a buffer limit. For connecting and communicating with the database, 
 * a @c DatabaseAccessor object is used. To further speed up the insertion of 
 * records, an @c SqlInserter object is used, which holds a prepared statement,
 * thus avoiding SQL parsing overhead. For convenience, the accessor provides
 * method templates that iterate the info list and extract the necessary 
 * information automatically. The macro @c CPPLOG_DECLARE_SQL_COLUMN_TYPES is
 * used to provide the SQL data types to be used in columns that store info
 * types.
 * 
 * During initialization, the database table is created if
 * it does not exist, and the inserter is initialized. When the buffer limit is 
 * reached in @c consume, the buffered log records are efficiently written to 
 * the database in one transaction by calling @c flush. The insertion is 
 * implemented by providing the buffer and an insert functor as parameters to
 * the accessor's @c storeData method.
 *
 * In the main function, a channel object is created and an @c SqliteWriter is
 * registered as consumer. Afterwards, a large number of records is created and
 * sent via the channel. One final call to @c flush is made to store any 
 * remaining data from the buffer. Since the database functionality is 
 * implemented on top of the Poco.Data library, we added a @c try-catch block 
 * to handle possible exceptions.
 */

#define CPPLOG_USE_SQLITE

#include <CppLog.h>
#include <iostream>
using namespace Log;

CPPLOG_DECLARE_INFO_TYPE( I1, "info_1", std::string );
CPPLOG_DECLARE_INFO_TYPE( I2, "info_2", int );
CPPLOG_DECLARE_INFO_TYPE( I3, "info_3", float );
CPPLOG_DECLARE_INFO_TYPE_LIST( Infos, I1, I2, I3 );

class SqliteWriter: public Consumer<> {
public:
	SqliteWriter( const std::string& dbFileName, std::size_t bufferLimit )
	:	db_( dbFileName ), bufferLimit_( bufferLimit ),
		recordCount_( 0 ), initialized_( false )
	{}
	void flush();
private:
	virtual void consume( const ChannelId channelId, const Record& record );
	void initialize();
	void insert( const Record& record );
private:
	DatabaseAccessor db_;
	std::auto_ptr< DatabaseAccessor::SqlInserter > inserter_;
	std::vector< Record > buffer_;
	std::size_t bufferLimit_, recordCount_;
	bool initialized_;
};

void SqliteWriter::consume( const ChannelId channelId, const Record& record ) {
	if( ! initialized_ ) initialize();

	if( recordCount_ < bufferLimit_ ) {
		++recordCount_;
	} else {
		flush();
		recordCount_ = 0;
	}
	buffer_.push_back( record );
}

CPPLOG_DECLARE_SQL_COLUMN_TYPES( Infos, sqlTypes, "VARCHAR", "INTEGER", "REAL" );

void SqliteWriter::initialize() {
	if( ! db_.tableExists( "log_record" ) ) {
		db_.createTable( "log_record" )
			.column( "id", "INTEGER" )
			.column( "text", "VARCHAR" )
			.columns< Infos >( sqlTypes )
			.primaryKey( "id" );
	}
	inserter_ = db_.createInserter();
	inserter_->table( "log_record" )
				.column( "text" )
				.columns< Infos >();
	initialized_ = true;
}

void SqliteWriter::flush() {
	using namespace std::tr1;
	db_.storeData( buffer_, bind( &SqliteWriter::insert, this, placeholders::_1 ) );
	buffer_.clear();
}

void SqliteWriter::insert( const Record& record ) {
	inserter_->value( record.getText().c_str() )
				.values< Infos >( record )
				.execute();
}

int main() {
	try {
		typedef std::tr1::shared_ptr< SqliteWriter > SqlitePtr;
		SqlitePtr sqlite( new SqliteWriter( "Example_Consumer_Sqlite.db", 100000 ) );

		Channel<> channel;
		channel.addConsumer( sqlite );

		int i = 100000;
		while( i-- )
		{
			channel << Record( "Logging example: SQLite database access" )
						+ I1( "string detail" ) + I2( 2 ) + I3( 3.0f );
		}
		sqlite->flush();
	}
	catch( const Poco::Exception& ex ) { std::cout << ex.displayText(); }
}
