/*
	Проверка работы класса oess_1::db::cln::slice_nonunique_index_t.
*/

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

#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/stdsn/h/serializable.hpp>
#include <oess_1/stdsn/h/inout_templ.hpp>

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

const std::string physic_name = "test/oess_db/nonunique_index/test.normal";
const std::string slice_name = "test_slice";

class	data_t
	:	public oess_1::stdsn::serializable_t
	{
		OESS_SERIALIZER( data_t )
	private :
		int	m_a;
		std::string	m_b;

	public :
		data_t()
			:	m_a( 0 )
			{}
		data_t(
			int a,
			const std::string & b )
			:	m_a( a )
			,	m_b( b )
			{}

		int
		a() const { return m_a; }

		void
		set_a( int a ) { m_a = a; }

		const std::string &
		b() const { return m_b; }

		void
		set_b( const std::string & b ) { m_b = b; }
	};

int
a_extractor( const data_t & d )
	{
		return d.a();
	}

const std::string &
b_extractor( const data_t & d )
	{
		return d.b();
	}

#include "main.ddl.cpp"

typedef std::vector< oess_1::ent_id_t > id_vector_t;
typedef
		oess_1::db::cln::slice_nonunique_index_t<
				data_t,
				int,
				int (*)( const data_t & ) >
		a_index_t;

typedef
		oess_1::db::cln::slice_nonunique_index_t<
				data_t,
				std::string,
				const std::string & (*)( const data_t & ) >
		b_index_t;

const int a_value_1 = 1;
const int a_value_2 = 2;
const int a_value_3 = 3;

const std::string b_value_1 = "a";
const std::string b_value_2 = "b";
const std::string b_value_3 = "c";

void
test_slice_create( oess_1::db::cln::db_t & storage )
	{
		storage.slice_create( slice_name );
	}

template< class Value, class Index >
void
check_value_count(
	const std::string & test_name,
	const Value & value,
	const Index & index,
	unsigned int expected_count )
	{
		if( index.end() == index.lower_bound( value ) )
			throw std::runtime_error(
				test_name + ": value not found! value: " +
				cpp_util_2::slexcast( value ) );

		if( expected_count != std::distance(
				index.lower_bound( value ),
				index.upper_bound( value ) ) )
			throw std::runtime_error(
					test_name + ": must be " +
					cpp_util_2::slexcast( expected_count ) + " values of: " +
					cpp_util_2::slexcast( value ) );
	}

template< class Index >
void
check_index_size(
	const std::string & test_name,
	const std::string & index_name,
	const Index & index,
	unsigned int expected_count )
	{
		if( expected_count != index.size() )
			throw std::runtime_error(
					test_name + ": index size mismatch! index: " +
					index_name + ", expected size: " +
					cpp_util_2::slexcast( expected_count ) + ", actual size: " +
					cpp_util_2::slexcast( index.size() ) );
	}

void
check_values(
	const std::string & test_name,
	const a_index_t & a,
	const b_index_t & b )
	{
		check_value_count( test_name, a_value_1, a, 2 );
		check_value_count( test_name, a_value_2, a, 2 );
		check_value_count( test_name, a_value_3, a, 2 );
		check_value_count( test_name, b_value_1, b, 2 );
		check_value_count( test_name, b_value_2, b, 2 );
		check_value_count( test_name, b_value_3, b, 2 );
	}
	
void
test_initial_fill( oess_1::db::cln::db_t & storage )
	{
		const std::string test_name = "test_initial_fill";

		oess_1::db::cln::slice_image_t< data_t >	image(
				storage, slice_name,
				oess_1::db::cln::slice_image_base_t::auto_load );
		a_index_t	a_index( image, &a_extractor );
		b_index_t	b_index( image, &b_extractor );

		id_vector_t ids;

		ids.push_back( image.insert( data_t( a_value_1, b_value_1 ) ) );
		ids.push_back( image.insert( data_t( a_value_2, b_value_1 ) ) );
		ids.push_back( image.insert( data_t( a_value_3, b_value_2 ) ) );
		ids.push_back( image.insert( data_t( a_value_1, b_value_2 ) ) );
		ids.push_back( image.insert( data_t( a_value_2, b_value_3 ) ) );
		ids.push_back( image.insert( data_t( a_value_3, b_value_3 ) ) );

		if( ids.size() != a_index.size() )
			throw std::runtime_error(
					test_name + ": ids.size() != a_index.size(), ids.size(): " +
					cpp_util_2::slexcast( ids.size() ) + ", a_index.size: " +
					cpp_util_2::slexcast( a_index.size() ) );

		if( ids.size() != b_index.size() )
			throw std::runtime_error(
					test_name + ": ids.size() != b_index.size(), ids.size(): " +
					cpp_util_2::slexcast( ids.size() ) + ", b_index.size: " +
					cpp_util_2::slexcast( b_index.size() ) );

		check_values( test_name, a_index, b_index );
	}

void
test_erase( oess_1::db::cln::db_t & storage )
	{
		const std::string test_name = "test_erase";

		oess_1::db::cln::slice_image_t< data_t >	image(
				storage, slice_name,
				oess_1::db::cln::slice_image_base_t::auto_load );
		a_index_t	a_index( image, &a_extractor );
		b_index_t	b_index( image, &b_extractor );

		check_values( test_name, a_index, b_index );

		b_index.erase( b_value_3 );

		if( b_index.end() != b_index.find( b_value_3 ) )
			throw std::runtime_error(
					test_name + ": item '" + cpp_util_2::slexcast( b_value_3 ) +
					"' still exists in b_index!" );

		check_index_size( test_name, "b_index", b_index, 4 );
		check_index_size( test_name, "a_index", a_index, 4 );

		check_value_count( test_name, a_value_1, a_index, 2 );
		check_value_count( test_name, a_value_2, a_index, 1 );
		check_value_count( test_name, a_value_3, a_index, 1 );
		check_value_count( test_name, b_value_1, b_index, 2 );
		check_value_count( test_name, b_value_2, b_index, 2 );
	}

void
test_erase_all( oess_1::db::cln::db_t & storage )
	{
		const std::string test_name = "test_erase_all";

		oess_1::db::cln::slice_image_t< data_t >	image(
				storage, slice_name,
				oess_1::db::cln::slice_image_base_t::auto_load );
		a_index_t	a_index( image, &a_extractor );
		b_index_t	b_index( image, &b_extractor );

		check_index_size( test_name, "b_index", b_index, 4 );
		check_index_size( test_name, "a_index", a_index, 4 );

		for( a_index_t::iterator it = a_index.begin(); it != a_index.end(); )
			a_index.erase( it++ );

		check_index_size( test_name, "b_index", b_index, 0 );
		check_index_size( test_name, "a_index", a_index, 0 );
	}

void
test_update( oess_1::db::cln::db_t & storage )
	{
		const std::string test_name = "test_update";

		oess_1::db::cln::slice_image_t< data_t >	image(
				storage, slice_name,
				oess_1::db::cln::slice_image_base_t::auto_load );
		a_index_t	a_index( image, &a_extractor );
		b_index_t	b_index( image, &b_extractor );

		check_index_size( test_name, "b_index", b_index, 0 );
		check_index_size( test_name, "a_index", a_index, 0 );

		oess_1::ent_id_t first = image.insert( data_t( a_value_1, b_value_1 ) );
		oess_1::ent_id_t second = image.insert( data_t( a_value_1, b_value_2 ) );

		check_index_size( test_name, "b_index", b_index, 2 );
		check_index_size( test_name, "a_index", a_index, 2 );

		check_value_count( test_name, a_value_1, a_index, 2 );
		check_value_count( test_name, b_value_1, b_index, 1 );
		check_value_count( test_name, b_value_2, b_index, 1 );

		image.update( first, data_t( a_value_1, b_value_2 ) );
		check_index_size( test_name, "b_index", b_index, 2 );
		check_index_size( test_name, "a_index", a_index, 2 );
		check_value_count( test_name, a_value_1, a_index, 2 );
		check_value_count( test_name, b_value_2, b_index, 2 );

		image.update( second, data_t( a_value_3, b_value_3 ) );
		check_index_size( test_name, "b_index", b_index, 2 );
		check_index_size( test_name, "a_index", a_index, 2 );
		check_value_count( test_name, a_value_1, a_index, 1 );
		check_value_count( test_name, a_value_3, a_index, 1 );
		check_value_count( test_name, b_value_2, b_index, 1 );
		check_value_count( test_name, b_value_3, b_index, 1 );
	}

int
main( int, char ** )
	{
		try
			{
				const oess_1::db::storage::config_t config;
   
				oess_1::db::cln::local_db_t local_db( physic_name, config,
						oess_1::db::cln::local_db_t::auto_create );
				oess_1::db::cln::db_t & storage = *( local_db.query_db() ); 

				/*!
					\attention В тестах используется жесткая привязка к
						результатам предыдущих тестов.
				*/
				test_slice_create( storage );
				test_initial_fill( storage );
				test_erase( storage );
				test_erase_all( storage );
				test_update( storage );

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

		return 2;
	}
