#include <iostream>
#include <stdexcept>

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

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

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

class	A
	:	virtual public oess_1::stdsn::serializable_t
	{
		OESS_SERIALIZER( A )
	private :
		std::string	m_a;

	protected :
		bool
		is_equal( const A & o ) const
			{
				return m_a == o.m_a;
			}

	public :
		A()
			{}
		A( const std::string & a )
			:	m_a( a )
			{}
		virtual ~A()
			{}

		bool
		operator==( const A & o ) const
			{
				return is_equal( o );
			}
	};

class	B
	:	virtual public A
	{
		OESS_SERIALIZER( B )
	private :
		std::string	m_b;

	protected :
		bool
		is_equal( const B & o ) const
			{
				return m_b == o.m_b;
			}

	public :
		B()
			{}
		B( const std::string & b )
			:	m_b( b )
			{}
		virtual ~B()
			{}

		bool
		operator==( const B & o ) const
			{
				return ( A::is_equal( o ) && is_equal( o ) );
			}
	};

class	C
	:	virtual public A
	{
		OESS_SERIALIZER( C )
	private :
		std::string	m_c;

	protected :
		bool
		is_equal( const C & o ) const
			{
				return m_c == o.m_c;
			}

	public :
		C()
			{}
		C( const std::string & c )
			:	m_c( c )
			{}
		virtual ~C()
			{}

		bool
		operator==( const C & o ) const
			{
				return ( A::is_equal( o ) && is_equal( o ) );
			}
	};

class	D
	:	virtual public oess_1::stdsn::serializable_t
	{
		OESS_SERIALIZER( D )
	private :
		std::string	m_d;

	protected :
		bool
		is_equal( const D & o ) const
			{
				return m_d == o.m_d;
			}

	public :
		D()
			{}
		D( const std::string & d )
			:	m_d( d )
			{}
		virtual ~D()
			{}

		bool
		operator==( const D & o ) const
			{
				return is_equal( o );
			}
	};

class	E
	:	public D
	,	virtual public B
	,	virtual public C
	{
		OESS_SERIALIZER( E )
	private :
		std::string	m_e;

	protected :
		bool
		is_equal( const E & o ) const
			{
				return m_e == o.m_e;
			}

	public :
		E()
			{}
		E( const std::string & e )
			:	D( e + "ThisIsE+D" )
			,	A( e + "ThisIsE+A" )
			,	B( e + "ThisIsE+B" )
			,	C( e + "ThisIsE+C" )
			,	m_e( e )
			{}
		virtual ~E()
			{}

		bool
		operator==( const E & o ) const
			{
				return ( A::is_equal( o ) &&
						B::is_equal( o ) &&
						C::is_equal( o ) &&
						D::is_equal( o ) &&
						is_equal( o ) );
			}
	};

#include "main.ddl.cpp"

std::string
serialize( const E & e )
	{
		std::string receiver;
		oess_1::io::obstring_t	stream( receiver );
		oess_1::stdsn::oent_no_markers_t oent( stream );

		oent << e;

		return receiver;
	}

void
test_ent_create(
	oess_1::db::cln::db_t & db,
	const std::string & slice_name,
	const E & e, const E & e2,
	oess_1::ent_id_t & id_e, oess_1::ent_id_t & id_e2 )
	{
		id_e = db.ent_create( slice_name, e );

		std::string e2_image = serialize( e2 );
		oess_1::io::ibstring_t e2_image_stream( e2_image );
		id_e2 = db.ent_raw_create( slice_name,
				e2_image_stream, e2_image_stream.size() );

		// После обычной загрузки значение объектов должны
		// совпадать.
		E e_copy;
		db.ent_load( id_e, e_copy );
		E e2_copy;
		db.ent_load( id_e2, e2_copy );
		if( !( e_copy == e2_copy ) )
			throw std::runtime_error( "e_copy != e2_copy" );
	}

void
test_ent_load(
	oess_1::db::cln::db_t & db,
	const oess_1::ent_id_t & id_e,
	const oess_1::ent_id_t & id_e2 )
	{
		std::string e_image;
		std::string e2_image;

		oess_1::io::obstring_t e_image_stream( e_image );
		oess_1::io::obstring_t e2_image_stream( e2_image );
		db.ent_raw_load( id_e, e_image_stream );
		db.ent_raw_load( id_e2, e2_image_stream );

		if( e_image != e2_image )
			throw std::runtime_error( "e_image != e2_image in "
					"case of ent_raw_load" );

		E e;
		db.ent_load( id_e, e );
		std::string etalon_image = serialize( e );
		if( e_image != etalon_image )
			throw std::runtime_error( "e_image != etalon_image" );

		if( e_image.size() != db.ent_raw_load( id_e, e2_image_stream, 0 ) )
			throw std::runtime_error( "ent_raw_load( id, stream, 0 ) != "
					"e_image.size()" );
	}

void
test_ent_update(
	oess_1::db::cln::db_t & db,
	const oess_1::ent_id_t & id_e,
	const oess_1::ent_id_t & id_e2 )
	{
		E e( "NewValue" );
		std::string e_image = serialize( e );
		oess_1::io::ibstring_t e_image_stream( e_image );

		db.ent_update( id_e, e );
		db.ent_raw_update( id_e2, e_image_stream, e_image_stream.size() );

		test_ent_load( db, id_e, id_e2 );
	}

int
main( int, char ** )
	{
		try
			{
				E e( "TestString" );
				E e2( "TestString" );
				if( !( e == e2 ) )
					throw std::runtime_error( "e != e2" );

				std::string e_image( serialize( e ) );
				std::string e2_image( serialize( e2 ) );
				if( e_image != e2_image )
					throw std::runtime_error( "e_image != e2_image" );

				oess_1::db::cln::local_db_t local_db(
						"test/oess_db/ent_raw/db",
						oess_1::db::cln::local_db_t::auto_create );
				const std::string slice_name( "test" );
				local_db.query_db()->slice_create( slice_name );

				oess_1::ent_id_t id_e;
				oess_1::ent_id_t id_e2;

				test_ent_create(
						*( local_db.query_db() ), slice_name,
						e, e2, id_e, id_e2 );
				test_ent_load(
						*( local_db.query_db() ), id_e, id_e2 );
				test_ent_update(
						*( local_db.query_db() ), id_e, id_e2 );
			}
		catch( const std::exception & x )
			{
				std::cout << "Oops! " << x.what() << std::endl;
				return 1;
			}

		return 0;
	}

