#include <iostream>
#include <fstream>
#include <map>
#include <string>
#include <algorithm>
#include <vector>
#include <stdexcept>

#include <cpp_util_2/h/lexcast.hpp>

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

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

//
// key_type_t
//

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

	OESS_SERIALIZER( key_type_t )
	public :
		key_type_t() : m_index( 0 ) {}
		key_type_t( oess_1::uint_t index ) : m_index( index ) {}
		key_type_t( const key_type_t & o )
			:	base_type_t( o )
			,	m_index( o.m_index )
			{}
		virtual ~key_type_t() {}

		key_type_t &
		operator =( const key_type_t & o )
		{
			base_type_t::operator=( o );
			m_index = o.m_index;
			return *this;
		}

		bool
		operator<( const key_type_t & o ) const
		{
			return ( m_index < o.m_index );
		}

		virtual std::ostream &
		dump( std::ostream & to ) const
		{
			return ( to << m_index );
		}

	private :
		oess_1::uint_t	m_index;
};

std::ostream &
operator<<( std::ostream & to, const key_type_t & what )
{
	return what.dump( to );
}

//
// value_t
//

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

	OESS_SERIALIZER( value_t )
	public :
		value_t() {}
		value_t( const std::string & value ) : m_value( value ) {}
		value_t( const value_t & o )
			:	base_type_t( o )
			,	m_value( o.m_value )
			{}
		virtual ~value_t() {}

		value_t &
		operator =( const value_t & o )
		{
			if( &o != this )
			{
				base_type_t::operator=( o );
				m_value = o.m_value;
			}
			return *this;
		}

		virtual std::ostream &
		dump( std::ostream & to ) const
		{
			return ( to << m_value );
		}

	private :
		std::string	m_value;
};

std::ostream &
operator<<( std::ostream & to, const value_t & what )
{
	return what.dump( to );
}

//
// data_t
//

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

	OESS_SERIALIZER( data_t )
	private :
		typedef std::map< key_type_t, value_t >	map_t;
		map_t	m_data;

		struct	show_item
		{
			std::ostream &	m_to;
			show_item( std::ostream & to ) : m_to( to ) {}

			void
			operator()( const map_t::value_type & a )
			{
				m_to << "(" << a.first << ":" << a.second << ")";
			}
		};

	public :
		data_t() {}
		data_t( const data_t & o ) : m_data( o.m_data ) {}
		virtual ~data_t() {}

		data_t &
		operator =( const data_t & o )
		{
			if( &o != this )
			{
				base_type_t::operator=( o );
				m_data = o.m_data;
			}
			return *this;
		}

		void
		insert(
			oess_1::uint_t index,
			const std::string & value )
		{
			m_data[ index ] = value;
		}

		virtual std::ostream &
		dump( std::ostream & to ) const
		{
			to << "{";
			std::for_each( m_data.begin(), m_data.end(), show_item( to ) );
			to << "}";
			return to;
		}
};

std::ostream &
operator<<( std::ostream & to, const data_t & what )
{
	return what.dump( to );
}

#include "main.ddl.cpp"

void
store(
	const std::string & file_name,
	const data_t & data )
{
	std::ofstream file( file_name.c_str(), std::ios::out | std::ios::binary );
	if( !file )
		throw std::runtime_error( file_name + ": unable to create" );

	std::string b;
	oess_1::io::obstring_t s( b );
	oess_1::stdsn::oent_no_markers_t oent( s );
	oent << data;

	file.write( b.data(), b.size() );
}

void
load(
	const std::string & file_name,
	data_t & data )
{
	std::ifstream file( file_name.c_str(), std::ios::in | std::ios::binary );
	if( !file )
		throw std::runtime_error( file_name + ": unable to open" );

	file.seekg( 0, std::ios::end );
	unsigned int data_size = file.tellg();
	file.seekg( 0 );

	std::vector< char > binary_data( data_size );
	file.read( &( binary_data.front() ), data_size );

	oess_1::io::ifixed_mem_buf_t s( &( binary_data.front() ), data_size );
	oess_1::stdsn::ient_no_markers_t ient( s );
	ient >> data;
}

void
test_store_v1_empty()
{
	data_t d;
	store( "test/type_extension/v1/empty.dat", d );
}

void
test_store_v1_not_empty()
{
	data_t d;
	d.insert( 1, "A" );
	d.insert( 4, "B" );
	d.insert( 0, "C" );
	d.insert( 8, "D" );
	d.insert( 5, "E" );

	store( "test/type_extension/v1/not_empty.dat", d );
}

void
test_load_v1_empty()
{
	data_t d;
	load( "test/type_extension/v1/empty.dat", d );

	std::cout << "load_v1_empty: " << d << std::endl;
}

void
test_load_v1_not_empty()
{
	data_t d;
	load( "test/type_extension/v1/not_empty.dat", d );

	std::cout << "load_v1_not_empty: " << d << std::endl;
}

void
test_load_v2_empty()
{
	data_t d;
	load( "test/type_extension/v2/empty.dat", d );

	std::cout << "load_v2_empty: " << d << std::endl;
}

void
test_load_v2_not_empty()
{
	data_t d;
	load( "test/type_extension/v2/not_empty.dat", d );

	std::cout << "load_v2_not_empty: " << d << std::endl;
}

void
test_load_v2_not_empty_and_store_again()
{
	data_t d;
	load( "test/type_extension/v2/not_empty.dat", d );

	std::cout << "load_v2_not_empty_and_store_again: "
		<< d << std::endl;

	store( "test/type_extension/v2/retranslated_from_v1.dat", d );
}

int
main( int, char ** )
{
	typedef void (*pfn)();
	pfn	tests[] =
		{
			&test_store_v1_empty
		,	&test_store_v1_not_empty

		,	&test_load_v1_empty
		,	&test_load_v1_not_empty

		,	&test_load_v2_empty
		,	&test_load_v2_not_empty
		,	&test_load_v2_not_empty_and_store_again
		,	0
		};

	for( pfn * p = tests; *p; ++p )
		{
			try
				{
					(*p)();
				}
			catch( const std::exception & x )
				{
					std::cerr << "Oops! " << x.what() << std::endl;
				}
		}

	return 0;
}
