/*
	ObjESSty-1
*/

/*
	Измерение производительности oess db при массовом
	создании/удалении объектов.
*/

#include <iostream>
#include <stdexcept>
#include <set>
#include <vector>

#include <cassert>
#include <ctime>
#include <cstdio>
#include <cstdlib>

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

#include <oess_1/stdsn/h/inout_templ.hpp>

#include <oess_1/db/cln/h/local_db.hpp>

class	data_t
	:	public oess_1::stdsn::serializable_t
	{
		typedef oess_1::stdsn::serializable_t	base_type_t;

		OESS_SERIALIZER( data_t )
	private :
		std::string	m_data;

	public :
		data_t()
			{}
		data_t( const char * data, std::size_t data_size )
			:	m_data( data, data_size )
			{}
		data_t( const data_t & o )
			{
				(*this) = o;
			}
		virtual ~data_t()
			{}

		data_t &
		operator=( const data_t & o )
			{
				base_type_t::operator=( o );
				m_data = o.m_data;

				return *this;
			}

		unsigned int
		size() const
			{
				return m_data.size();
			}
	};

#include "main.ddl.cpp"

class	my_timer_t
	{
	private :
		std::string	m_name;
		std::time_t	m_start;

	public :
		my_timer_t( const std::string & name )
			:	m_name( name )
			,	m_start( std::time( 0 ) )
			{}
		~my_timer_t()
			{
				std::time_t finish = std::time( 0 );

				std::cout << m_name << ": " << finish - m_start << std::endl;
			}
	};

#define TEST_CAPACITY 10000/*000*/


#if TEST_CAPACITY == 10
const unsigned int	test_capacity = 10;
#elif TEST_CAPACITY == 1000
const unsigned int	test_capacity = 1000;
#elif TEST_CAPACITY == 2000
const unsigned int	test_capacity = 2000;
#else
const unsigned int	test_capacity = 10000;
#endif

const unsigned int	size_boundary = 512 /* 16 * 1024 */;

inline unsigned int
bounded_size( unsigned int v )
	{
		unsigned int r = v % size_boundary;
		if( !r ) r = size_boundary;
		return r;
	}


const std::string	test_slice_name = "test";

const unsigned int *
creation_test_distribution()
	{
		static unsigned int	distribution[ test_capacity ] =
			{
#if TEST_CAPACITY == 10
				1234, 57890, 1, 78900, 12000,
				99000, 32767, 512, 24000, 82300
#elif TEST_CAPACITY == 1000
#include "1000_create.cpp"
#elif TEST_CAPACITY == 2000
#include "2000_create.cpp"
#else
#include "10000_create.cpp"
#endif
			};

		return distribution;
	}

const unsigned int *
creation_half_test_distribution()
	{
		static unsigned int	distribution[ test_capacity / 2 ] =
			{
#if TEST_CAPACITY == 10
				46982, 102, 37947, 83084, 38472	
#elif TEST_CAPACITY == 1000
#include "1000_create_half.cpp"
#elif TEST_CAPACITY == 2000
#include "2000_create_half.cpp"
#else
#include "10000_create_half.cpp"
#endif
			};

		return distribution;
	}

const unsigned int *
load_test_distribution()
	{
		static unsigned int	distribution[ test_capacity ] =
			{
#if TEST_CAPACITY == 10
				2, 5, 7, 1, 9,
				3, 8, 4, 0, 6
#elif TEST_CAPACITY == 1000
#include "1000_load.cpp"
#elif TEST_CAPACITY == 2000
#include "2000_load.cpp"
#else
#include "10000_load.cpp"
#endif
			};

		return distribution;
	}

const unsigned int *
update_test_index_distribution()
	{
		static unsigned int	distribution[ test_capacity ] =
			{
#if TEST_CAPACITY == 10
				6, 9, 0, 3, 5,
				2, 7, 4, 1, 8
#elif TEST_CAPACITY == 1000
#include "1000_update_index.cpp"
#elif TEST_CAPACITY == 2000
#include "2000_update_index.cpp"
#else
#include "10000_update_index.cpp"
#endif
			};

		return distribution;
	}

const unsigned int *
update_test_size_distribution()
	{
		static unsigned int	distribution[ test_capacity ] =
			{
#if TEST_CAPACITY == 10
				20 * 1024,
				40 * 1024,
				100 * 1024,
				80 * 1024,
				10 * 1024,
				50 * 1024,
				90 * 1024,
				70 * 1024,
				30 * 1024,
				40 * 1024
#elif TEST_CAPACITY == 1000
#include "1000_update_size.cpp"
#elif TEST_CAPACITY == 2000
#include "2000_update_size.cpp"
#else
#include "10000_update_size.cpp"
#endif
			};

		return distribution;
	}

const unsigned int *
destroy_half_test_distribution()
	{
		static unsigned int	distribution[ test_capacity / 2 ] =
			{
#if TEST_CAPACITY == 10
				5, 2, 7, 1, 4
#elif TEST_CAPACITY == 1000
#include "1000_destroy_half.cpp"
#elif TEST_CAPACITY == 2000
#include "2000_destroy_half.cpp"
#else
#include "10000_destroy_half.cpp"
#endif
			};

		return distribution;
	}

typedef oess_1::ent_id_t	oid_t;
typedef oess_1::db::cln::db_t	storage_t;

typedef std::vector< oid_t > oid_vector_t;

class	test_suite_t
	{
	public :
		virtual void
		run( storage_t & storage ) = 0;
	};

class	create_object_suite_t
	:	public test_suite_t
	{
	private :
		const unsigned int	m_capacity;
		const unsigned int *	m_distribution;
		oid_vector_t &	m_oids;

	public :
		create_object_suite_t(
			unsigned int capacity,
			const unsigned int * distribution,
			oid_vector_t & oids )
			:	m_capacity( capacity )
			,	m_distribution( distribution )
			,	m_oids( oids )
			{}

		virtual void
		run( storage_t & storage );
	};

void
create_object_suite_t::run( storage_t & storage )
	{
		static char buf[ 100 * 1024 ];
		static bool is_buf_initialized;
		if( !is_buf_initialized )
			{
				std::srand( std::time( 0 ) );
				unsigned int values = sizeof( buf ) / sizeof( int );

				int * ptr = reinterpret_cast< int * >( buf );

				for( unsigned int j = 0; j != values; ++j )
					*(++ptr) = std::rand();
			}

		for( unsigned int i = 0; i != m_capacity; ++i )
			{
				std::sprintf( buf, "index: %u, length: %u", i,
					bounded_size( m_distribution[ i ] ) );

				data_t value( buf, bounded_size( m_distribution[ i ] ) );
				oid_t oid = storage.ent_create( test_slice_name, value );
				m_oids.push_back( oid );
			}
	}

class	sequential_load_object_suite_t
	:	public test_suite_t
	{
	private :
		const oid_vector_t &	m_oids;

	public :
		sequential_load_object_suite_t(
			const oid_vector_t & oids )
			:	m_oids( oids )
			{}

		virtual void
		run( storage_t & storage );
	};

void
sequential_load_object_suite_t::run( storage_t & storage )
	{
		for( oid_vector_t::const_iterator it = m_oids.begin(),
				it_end = m_oids.end();
				it != it_end;
				++it )
			{
				data_t value;
				storage.ent_load( *it, value );
			}
	}

class	load_object_suite_t
	:	public test_suite_t
	{
	private :
		const unsigned int	m_capacity;
		const unsigned int * m_distribution;
		const oid_vector_t &	m_oids;

	public :
		load_object_suite_t(
			unsigned int capacity,
			const unsigned int * distribution,
			const oid_vector_t & oids )
			:	m_capacity( capacity )
			,	m_distribution( distribution )
			,	m_oids( oids )
			{}

		virtual void
		run( storage_t & storage );
	};

void
load_object_suite_t::run( storage_t & storage )
	{
		for( unsigned int i = 0; i != m_capacity; ++i )
			{
				data_t value;
				storage.ent_load( m_oids[ m_distribution[ i ] ], value );
			}
	}

class	update_object_suite_t
	:	public test_suite_t
	{
	private :
		const unsigned int	m_capacity;
		const unsigned int *	m_index_distribution;
		const unsigned int *	m_size_distribution;
		const oid_vector_t &	m_oids;

	public :
		update_object_suite_t(
			unsigned int capacity,
			const unsigned int * index_distribution,
			const unsigned int * size_distribution,
			oid_vector_t & oids )
			:	m_capacity( capacity )
			,	m_index_distribution( index_distribution )
			,	m_size_distribution( size_distribution )
			,	m_oids( oids )
			{}

		virtual void
		run( storage_t & storage );
	};

void
update_object_suite_t::run( storage_t & storage )
	{
		static char buf[ 100 * 1024 ];
		static bool is_buf_initialized = false;
		if( !is_buf_initialized )
			{
				std::srand( std::time( 0 ) );
				unsigned int values = sizeof( buf ) / sizeof( int );

				int * ptr = reinterpret_cast< int * >( buf );

				for( unsigned int j = 0; j != values; ++j )
					*(++ptr) = std::rand();
			}

		for( unsigned int i = 0; i != m_capacity; ++i )
			{
				storage.ent_update(
					m_oids[ m_index_distribution[ i ] ],
					data_t( buf, bounded_size( m_size_distribution[ i ] ) ) );
			}
	}

class	update_object_no_resize_suite_t
	:	public test_suite_t
	{
	private :
		const unsigned int	m_capacity;
		const unsigned int *	m_index_distribution;
		const oid_vector_t &	m_oids;

	public :
		update_object_no_resize_suite_t(
			unsigned int capacity,
			const unsigned int * index_distribution,
			oid_vector_t & oids )
			:	m_capacity( capacity )
			,	m_index_distribution( index_distribution )
			,	m_oids( oids )
			{}

		virtual void
		run( storage_t & storage );
	};

void
update_object_no_resize_suite_t::run( storage_t & storage )
	{
		static char buf[ 100 * 1024 ];
		static bool is_buf_initialized = false;
		if( !is_buf_initialized )
			{
				std::srand( std::time( 0 ) );
				unsigned int values = sizeof( buf ) / sizeof( int );

				int * ptr = reinterpret_cast< int * >( buf );

				for( unsigned int j = 0; j != values; ++j )
					*(++ptr) = std::rand();
			}

		for( unsigned int i = 0; i != m_capacity; ++i )
			{
				data_t value;
				storage.ent_load( m_oids[ m_index_distribution[ i ] ], value );

				storage.ent_update(
					m_oids[ m_index_distribution[ i ] ],
					data_t( buf, value.size() ) );
			}
	}

class	sequential_update_object_no_resize_suite_t
	:	public test_suite_t
	{
	private :
		const oid_vector_t &	m_oids;

	public :
		sequential_update_object_no_resize_suite_t(
			oid_vector_t & oids )
			:	m_oids( oids )
			{}

		virtual void
		run( storage_t & storage );
	};

void
sequential_update_object_no_resize_suite_t::run( storage_t & storage )
	{
		static char buf[ 100 * 1024 ];
		static bool is_buf_initialized = false;
		if( !is_buf_initialized )
			{
				std::srand( std::time( 0 ) );
				unsigned int values = sizeof( buf ) / sizeof( int );

				int * ptr = reinterpret_cast< int * >( buf );

				for( unsigned int j = 0; j != values; ++j )
					*(++ptr) = std::rand();
			}

		for( oid_vector_t::const_iterator it = m_oids.begin(),
				it_end = m_oids.end();
				it != it_end;
				++it )
			{
				data_t value;
				storage.ent_load( *it, value );

				storage.ent_update( *it,
					data_t( buf, value.size() ) );
			}
	}

class	sequential_update_object_no_resize_no_preload_suite_t
	:	public test_suite_t
	{
	private :
		const oid_vector_t &	m_oids;
		const unsigned int *	m_size_distribution;

	public :
		sequential_update_object_no_resize_no_preload_suite_t(
			oid_vector_t & oids,
			const unsigned int * size_distribution )
			:	m_oids( oids )
			,	m_size_distribution( size_distribution )
			{}

		virtual void
		run( storage_t & storage );
	};

void
sequential_update_object_no_resize_no_preload_suite_t::run( storage_t & storage )
	{
		static char buf[ 100 * 1024 ];
		static bool is_buf_initialized = false;
		if( !is_buf_initialized )
			{
				std::srand( std::time( 0 ) );
				unsigned int values = sizeof( buf ) / sizeof( int );

				int * ptr = reinterpret_cast< int * >( buf );

				for( unsigned int j = 0; j != values; ++j )
					*(++ptr) = std::rand();
			}

		int i = 0;
		for( oid_vector_t::const_iterator it = m_oids.begin(),
				it_end = m_oids.end();
				it != it_end;
				++it, ++i )
			{
				storage.ent_update( *it,
					data_t( buf, bounded_size( m_size_distribution[ i ] ) ) );
			}
	}

class	destroy_half_object_suite_t
	:	public test_suite_t
	{
	private :
		oid_vector_t	m_oids_to_destroy;

	public :
		destroy_half_object_suite_t(
			unsigned int half_capacity,
			const unsigned int * distribution,
			oid_vector_t & oids );

		virtual void
		run( storage_t & storage );
	};

destroy_half_object_suite_t::destroy_half_object_suite_t(
	unsigned int half_capacity,
	const unsigned int * distribution,
	oid_vector_t & oids )
	{
		m_oids_to_destroy.reserve( half_capacity );

		std::set< unsigned int >	oids_to_destroy_index;
		
		for( unsigned int i = 0; i != half_capacity; ++i )
			{
				m_oids_to_destroy.push_back( oids[ distribution[ i ] ] );
				oids_to_destroy_index.insert( distribution[ i ] );
			}

		assert( oids_to_destroy_index.size() == half_capacity );

		for( std::set< unsigned int >::reverse_iterator
			it = oids_to_destroy_index.rbegin();
			it != oids_to_destroy_index.rend(); it++ )
			oids.erase( oids.begin() + *it );

		assert( oids.size() == half_capacity );
		assert( m_oids_to_destroy.size() == half_capacity );
	}

void
destroy_half_object_suite_t::run( storage_t & storage )
	{
		for( oid_vector_t::const_iterator it = m_oids_to_destroy.begin(),
			it_end = m_oids_to_destroy.end(); it != it_end; ++it )
			storage.ent_destroy( *it );
	}

class	destroy_object_suite_t
	:	public test_suite_t
	{
	private :
		const oid_vector_t &	m_oids;

	public :
		destroy_object_suite_t(
			const oid_vector_t & oids )
			:	m_oids( oids )
			{}

		virtual void
		run( storage_t & storage );
	};

void
destroy_object_suite_t::run( storage_t & storage )
	{
		for( oid_vector_t::const_iterator it = m_oids.begin(),
			it_end = m_oids.end(); it != it_end; ++it )
			storage.ent_destroy( *it );
	}

class	destroy_all_at_once_suite_t
	:	public test_suite_t
	{
	public :
		destroy_all_at_once_suite_t()
			{}

		virtual void
		run( storage_t & storage );
	};

void
destroy_all_at_once_suite_t::run( storage_t & storage )
	{
		storage.ent_destroy_all( test_slice_name );
	}

class	destroy_slice_suite_t
	:	public test_suite_t
	{
	private :
		const std::string	m_slice; 

	public :
		destroy_slice_suite_t(
			const std::string & slice )
			:	m_slice( slice )
			{}

		virtual void
		run( storage_t & storage );
	};

void
destroy_slice_suite_t::run( storage_t & storage )
	{
		storage.slice_destroy( m_slice );
	}

void
run_and_measure(
	const std::string & name,
	test_suite_t & suite,
	storage_t & storage )
	{
		my_timer_t t( name );

		suite.run( storage );
	}

void
do_test_for_storage(
	const std::string & name,
	storage_t & storage )
	{
		try
			{
				oid_vector_t oids;
				oids.reserve( test_capacity );

				my_timer_t	timer( name );

				create_object_suite_t create_suite( test_capacity,
					creation_test_distribution(), oids );
				run_and_measure( name + " object creation",
					create_suite, storage );

				load_object_suite_t load_all_suite( test_capacity,
					load_test_distribution(), oids );
				run_and_measure( name + " all object loading (random order)",
					load_all_suite, storage );

				sequential_load_object_suite_t seq_load_all_suite( oids );
				run_and_measure( name + " all object loading (sequential order)",
					seq_load_all_suite, storage );

				sequential_update_object_no_resize_no_preload_suite_t
					seq_update_no_resize_no_preload_all_suite(
							oids, creation_test_distribution() );
				run_and_measure( name + " all object updating (sequential order,"
					" no resize, no_preload)",
					seq_update_no_resize_no_preload_all_suite, storage );

				update_object_suite_t update_all_suite( test_capacity,
					update_test_index_distribution(),
					update_test_size_distribution(), oids );
				run_and_measure( name + " all object updating (random order)",
					update_all_suite, storage );

				update_object_no_resize_suite_t update_no_resize_all_suite(
					test_capacity,
					update_test_index_distribution(),
					oids );
				run_and_measure( name + " all object updating (random order,"
					" no resize)",
					update_no_resize_all_suite, storage );

				sequential_update_object_no_resize_suite_t
					seq_update_no_resize_all_suite( oids );
				run_and_measure( name + " all object updating (sequential order,"
					" no resize)",
					seq_update_no_resize_all_suite, storage );

				destroy_half_object_suite_t destroy_half_suite(
					test_capacity / 2, destroy_half_test_distribution(), oids );
				run_and_measure( name + " half object destroying",
					destroy_half_suite, storage );

				create_object_suite_t create_half_suite( test_capacity / 2,
					creation_half_test_distribution(), oids );
				run_and_measure( name + " half object creation",
					create_half_suite, storage );

				destroy_object_suite_t destroy_all_suite( oids );
				run_and_measure( name + " all object destroying",
					destroy_all_suite, storage );

				oids.clear();
				create_object_suite_t create_suite_2( test_capacity,
					creation_test_distribution(), oids );
				run_and_measure( name + " object creation 2",
					create_suite_2, storage );

				destroy_all_at_once_suite_t destroy_all_at_once_suite;
				run_and_measure( name + " all object at once destroying",
					destroy_all_at_once_suite, storage );

#if 0
				destroy_slice_suite_t destroy_slice_suite( test_slice_name );
				run_and_measure( name + " destroy slice",
					destroy_slice_suite, storage );
#endif
			}
		catch( const std::exception & x )
			{
				std::cerr << "*** Oops! ***\n" << x.what() << std::endl;
			}
	}

int
main( int argc, char ** argv )
	{
		try {
			const std::string physic_name(
				"./test/bench/oess_db/create_update/db" );

			oess_1::db::storage::config_t config;
			if( 2 == argc )
				config = oess_1::db::storage::load_cfg_file( argv[ 1 ] );

			oess_1::db::cln::local_db_t db( physic_name, config );
			db.query_db()->slice_create( test_slice_name );

			do_test_for_storage( "default storage", *( db.query_db() ) );

			return 0;
		}
		catch( oess_1::ex_t & x ) {
			std::cerr << "*** " << x.query_err_code() << " ***"
				<< std::endl;
		}
		catch( const std::exception & x ) {
			std::cerr << "### " << x.what() << std::endl;
		}
		catch( ... ) {
			std::cerr << "Oops! Unknown exception!" << std::endl;
		}

		return 1;
	}
