/*
	Проверка создания новой БД.
*/

#include <iostream>
#include <stdexcept>

#include <auto_ptr_3/h/obj_ptr.hpp>

#include <cpp_util_2/h/lexcast.hpp>

#include <oess_1/defs/h/ex.hpp>

#include <oess_1/io/h/mem_buf.hpp>
#include <oess_1/io/h/bstring_buf.hpp>

#include <oess_1/db/storage/h/stream_storage.hpp>

const std::string physic_name = "test/stream_storage/entity/db";

oess_1::db::storage::stream_storage_t *
create_db()
	{
		const oess_1::db::storage::config_t config;

		return oess_1::db::storage::create_stream_storage(
			physic_name, config );
	}

void
create_test_stream(
	oess_1::db::storage::stream_storage_t & storage,
	const std::string & stream_name )
	{
		storage.trans_start();
		storage.stream_create( stream_name );
		storage.trans_commit();
	}

void
test_enumerate_empty_stream(
	oess_1::db::storage::stream_storage_t & storage )
	{
		// В пустом потоке метод entity_find_next должен сразу
		// возвращать пустое значение.
		const std::string stream_name = "empty_stream";
		create_test_stream( storage, stream_name );

		oess_1::db::storage::entity_stream_t & stream =
				storage.stream_get( stream_name );

		oess_1::db::storage::chain_id_t ent_id = stream.entity_find_next(
				oess_1::db::storage::invalid_chain_id );

		if( oess_1::db::storage::invalid_chain_id != ent_id )
			throw std::runtime_error( "test_enumerate_empty_stream FAILED! "
					"ent_id != invalid_chain_id" );
	}

void
test_fill_and_refill(
	oess_1::db::storage::stream_storage_t & storage )
	{
		// Если создать N объектов в потоке, а затем удалить их
		// все методом entity_destroy_all, а затем создать эти же
		// N объектов в том же порядке, то у новых объектов будут
		// те же самые идентификаторы, что и у старых.

		const std::string stream_name = "fill_and_refill_stream";
		create_test_stream( storage, stream_name );

		oess_1::db::storage::entity_stream_t & stream =
				storage.stream_get( stream_name );

		const std::string value = "This is the value of test object";
		oess_1::db::storage::chain_id_t ids[ 100 ];

		{
			storage.trans_start();
			for( int i = 0; i < ( sizeof(ids) / sizeof(ids[ 0 ]) ); ++i )
				{
					oess_1::io::ibstring_t value_stream( value );
					ids[ i ] = stream.entity_create( value_stream, value.size() );
				}
			storage.trans_commit();
		}

		{
			storage.trans_start();
			stream.entity_destroy_all();
			storage.trans_commit();
		}

		{
			storage.trans_start();
			for( int i = 0; i < ( sizeof(ids) / sizeof(ids[ 0 ]) ); ++i )
				{
					oess_1::io::ibstring_t value_stream( value );
					oess_1::db::storage::chain_id_t id =
							stream.entity_create( value_stream, value.size() );
					if( ids[ i ] != id )
						throw std::runtime_error( "test_fill_and_refill FAILED! "
								"expected id: " + cpp_util_2::slexcast( ids[ i ] ) +
								", real value: " + cpp_util_2::slexcast( id ) );
				}
			storage.trans_commit();
		}
	}

void
test_fill_enumerate(
	oess_1::db::storage::stream_storage_t & storage )
	{
		// Если создать N объектов в потоке, а затем воспользоваться
		// методом entity_find_next, то нужно получить те же самые
		// идентификаторы объектов.

		const std::string stream_name = "fill_enumerate";
		create_test_stream( storage, stream_name );

		oess_1::db::storage::entity_stream_t & stream =
				storage.stream_get( stream_name );

		const std::string value = "This is the value of "
				"yet another test object";
		oess_1::db::storage::chain_id_t ids[ 100 ];

		{
			storage.trans_start();
			for( int i = 0; i < ( sizeof(ids) / sizeof(ids[ 0 ]) ); ++i )
				{
					oess_1::io::ibstring_t value_stream( value );
					ids[ i ] = stream.entity_create( value_stream, value.size() );
				}
			storage.trans_commit();
		}

		{
			int i = 0;
			oess_1::db::storage::chain_id_t id =
					oess_1::db::storage::invalid_chain_id;
			id = stream.entity_find_next( id );
			while( oess_1::db::storage::invalid_chain_id != id &&
					i != ( sizeof( ids ) / sizeof( ids[ 0 ] ) ) )
				{
					if( ids[ i ] != id )
						throw std::runtime_error( "test_fill_enumerate FAILED! "
								"expected id: " + cpp_util_2::slexcast( ids[ i ] ) +
								", real value: " + cpp_util_2::slexcast( id ) );
					++i;
					id = stream.entity_find_next( id );
				}

			if( !( oess_1::db::storage::invalid_chain_id == id &&
					i == ( sizeof( ids ) / sizeof( ids[ 0 ] ) ) ) )
				throw std::runtime_error( "test_fill_enumerate FAILED! "
						"expected end of stream; id: " +
						cpp_util_2::slexcast( id ) +
						", i: " + cpp_util_2::slexcast( i ) );

		}
	}

void
test_fill_partialy_destroy_enumerate(
	oess_1::db::storage::stream_storage_t & storage )
	{
		// Если создать N объектов в потоке, а затем удалить
		// каждый второй из них и воспользоваться
		// методом entity_find_next, то нужно получить 
		// идентификаторы оставшихся объектов.
		// Если же затем создать N/2 объектов, то они должны
		// получить идентификаторы удаленных ранее объектов.

		const std::string stream_name = "fill_partially_destroy_enumerate";
		create_test_stream( storage, stream_name );

		oess_1::db::storage::entity_stream_t & stream =
				storage.stream_get( stream_name );

		const std::string value = "This is the value of "
				"yet another test object";
		oess_1::db::storage::chain_id_t ids[ 100 ];

		{
			storage.trans_start();
			for( int i = 0; i < ( sizeof(ids) / sizeof(ids[ 0 ]) ); ++i )
				{
					oess_1::io::ibstring_t value_stream( value );
					ids[ i ] = stream.entity_create( value_stream, value.size() );
				}
			storage.trans_commit();
		}

		{
			storage.trans_start();
			for( int i = 0; i < ( sizeof(ids) / sizeof(ids[ 0 ]) ); i += 2 )
				{
					stream.entity_destroy( ids[ i ] );
				}
			storage.trans_commit();
		}

		{
			int i = 1;
			oess_1::db::storage::chain_id_t id =
					oess_1::db::storage::invalid_chain_id;
			id = stream.entity_find_next( id );
			while( oess_1::db::storage::invalid_chain_id != id &&
					i < ( sizeof( ids ) / sizeof( ids[ 0 ] ) ) )
				{
					if( ids[ i ] != id )
						throw std::runtime_error(
								"test_fill_partialy_destroy_enumerate FAILED! "
								"expected id: " + cpp_util_2::slexcast( ids[ i ] ) +
								", real value: " + cpp_util_2::slexcast( id ) );
					i += 2;
					id = stream.entity_find_next( id );
				}

			if( !( oess_1::db::storage::invalid_chain_id == id &&
					i >= ( sizeof( ids ) / sizeof( ids[ 0 ] ) ) ) )
				throw std::runtime_error(
						"test_fill_partialy_destroy_enumerate FAILED! "
						"expected end of stream; id: " +
						cpp_util_2::slexcast( id ) +
						", i: " + cpp_util_2::slexcast( i ) );
		}

		{
			storage.trans_start();
			for( int i = 0; i < ( sizeof(ids) / sizeof(ids[ 0 ]) ); i += 2 )
				{
					oess_1::io::ibstring_t value_stream( value );
					oess_1::db::storage::chain_id_t id =
							stream.entity_create( value_stream, value.size() );
					if( ids[ i ] != id )
						throw std::runtime_error(
								"test_fill_partialy_destroy_enumerate FAILED! "
								"expected id: " + cpp_util_2::slexcast( ids[ i ] ) +
								", real value: " + cpp_util_2::slexcast( id ) );
				}
			storage.trans_commit();
		}
	}

void
test_create_update(
	oess_1::db::storage::stream_storage_t & storage )
	{
		// Если создать объект, обновить его и получить его значение,
		// то должно быть получено обновленное значение.
		const std::string test_name( "test_create_update" );

		const std::string stream_name = test_name;
		create_test_stream( storage, stream_name );

		oess_1::db::storage::entity_stream_t & stream =
				storage.stream_get( stream_name );

		const std::string value = "This is initial value of object";
		const std::string changed_value = "This is changed value of object";
		oess_1::db::storage::chain_id_t id;

		{
			storage.trans_start();
			oess_1::io::ibstring_t value_stream( value );
			id = stream.entity_create( value_stream, value.size() );
			storage.trans_commit();
		}

		{
			std::string real_value;
			oess_1::io::obstring_t value_stream( real_value );
			stream.entity_load( id, value_stream );

			if( value != real_value )
				throw std::runtime_error( test_name + " FAILED! "
						"value != real_value" );
		}

		{
			storage.trans_start();
			oess_1::io::ibstring_t value_stream( changed_value );
			stream.entity_update( id, value_stream, changed_value.size() );
			storage.trans_commit();
		}

		{
			std::string real_value;
			oess_1::io::obstring_t value_stream( real_value );
			stream.entity_load( id, value_stream );

			if( changed_value != real_value )
				throw std::runtime_error( test_name + " FAILED! "
						"changed_value != real_value" );
		}
	}

int
main( int, char ** )
	{
		try
			{
				auto_ptr_3::obj_ptr_t<
						oess_1::db::storage::stream_storage_t >
					storage( create_db() );

				test_enumerate_empty_stream( *storage );
				test_fill_and_refill( *storage );
				test_fill_enumerate( *storage );
				test_fill_partialy_destroy_enumerate( *storage );
				test_create_update( *storage );

				return 0;
			}
		catch( const std::exception & x )
			{
				std::cerr << x.what() << std::endl;
			}
		catch( ... )
			{
				std::cerr << "Unknown exception!" << std::endl;
			}

		return 2;
	}
