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

#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() : m_value( "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 value_t *
		clone() const
		{
			return new value_t( m_value );
		}

		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) << ")";
			}
		};

		static void
		value_destroyer( const map_t::value_type & a )
		{
			delete a.second;
		}

		static map_t::value_type
		cloner( const map_t::value_type & from )
			{
				return map_t::value_type( from.first, from.second->clone() );
			}

		static void
		extension_droper( const map_t::value_type & from )
			{
				from.second->oess_drop_unknown_extensions();
			}

		void
		clear()
			{
				std::for_each( m_data.begin(), m_data.end(), value_destroyer );
				m_data.clear();
			}

	protected :
		virtual void
		oess_pre_unpack()
		{
			clear();
		}

	public :
		data_t() {}
		data_t( const data_t & o )
			:	base_type_t( o )
			,	m_data( o.m_data ) {}
		virtual ~data_t()
		{
			clear();
		}

		data_t &
		operator =( const data_t & o )
		{
			if( &o != this )
			{
				base_type_t::operator=( o );

				clear();
				std::transform( o.m_data.begin(), o.m_data.end(),
					std::inserter( m_data, m_data.end() ),
					cloner );
			}
			return *this;
		}

		void
		insert(
			oess_1::uint_t index,
			value_t * value )
			{
				m_data[ index ] = value;
			}

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

		void
		drop_value_extensions()
			{
				std::for_each( m_data.begin(), m_data.end(),
					extension_droper );
			}
};

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

//
// timeout_value_t
//

class	timeout_value_t
:	public value_t
{
	OESS_SERIALIZER( timeout_value_t )

	static std::string
	type_name()
		{
			return "timeout_value_t";
		}

	public :
		timeout_value_t()
			:	value_t( type_name() )
			, m_millisec( 0 )
			{}
		timeout_value_t( unsigned int millisec)
			:	value_t( type_name() )
			,	m_millisec( millisec )
			{}
		timeout_value_t(
			const timeout_value_t & o )
			:	value_t( type_name() )
			{
				(*this) = o;
			}
		virtual ~timeout_value_t() {}

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

		virtual value_t *
		clone() const
		{
			return new timeout_value_t( *this );
		}

		virtual std::ostream &
		dump( std::ostream & to ) const
		{
			return ( value_t::dump( to ) << "," << m_millisec );
		}

	private :
		unsigned int	m_millisec;
};

//
// compression_value_t
//

class	compression_value_t
:	public value_t
{
	OESS_SERIALIZER( compression_value_t )

	static std::string
	type_name()
	{
		return "compression_value_t";
	}

	public :
		compression_value_t()
			:	value_t( type_name() )
			{}
		compression_value_t( const std::string & preferred )
			:	value_t( type_name() )
			,	m_preferred( preferred )
			{}
		compression_value_t(
			const compression_value_t & o )
			:	value_t( type_name() )
			{
				(*this) = o;
			}
		virtual ~compression_value_t() {}

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

		virtual value_t *
		clone() const
		{
			return new compression_value_t( *this );
		}

		virtual std::ostream &
		dump( std::ostream & to ) const
		{
			return ( value_t::dump( to ) << "," << m_preferred );
		}

	private :
		std::string	m_preferred;
};

//
// cipher_value_t
//

class	cipher_value_t
	:	public value_t
	{
		OESS_SERIALIZER( cipher_value_t )

		static std::string
		type_name()
			{
				return "cipher_value_t";
			}

		public :
			cipher_value_t()
				:	value_t( type_name() )
				{}
			cipher_value_t(
				const std::string & type,
				const std::string & preferred )
				:	value_t( type )
				,	m_preferred( preferred )
				{}
			cipher_value_t(
				const cipher_value_t & o )
				:	value_t( type_name() )
				{
					(*this) = o;
				}
			virtual ~cipher_value_t() {}

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

			virtual value_t *
			clone() const
				{
					return new cipher_value_t( *this );
				}

			virtual std::ostream &
			dump( std::ostream & to ) const
				{
					to << "[cipher]";
					return ( value_t::dump( to ) << "," << m_preferred );
				}

		private :
			std::string	m_preferred;
	};

//
// symmetric_cipher_value_t
//

class	symmetric_cipher_value_t
	:	public cipher_value_t
	{
		OESS_SERIALIZER( symmetric_cipher_value_t )

		public :
			symmetric_cipher_value_t(
				const std::string & type,
				const std::string & preferred,
				unsigned int key_length )
				:	cipher_value_t( type,	preferred )
				,	m_key_length( key_length )
				{}
			symmetric_cipher_value_t(
				const symmetric_cipher_value_t & o )
				:	cipher_value_t( o )
				{
					(*this) = o;
				}
			virtual ~symmetric_cipher_value_t() {}

			symmetric_cipher_value_t &
			operator =( const symmetric_cipher_value_t & o )
				{
					if( &o != this )
						{
							cipher_value_t::operator=( o );
							m_key_length = o.m_key_length;
						}
					return *this;
				}

			virtual value_t *
			clone() const = 0;

			virtual std::ostream &
			dump( std::ostream & to ) const
				{
					to << "[symmetric]";
					return  ( cipher_value_t::dump( to ) << ","
						<< m_key_length );
				}

		private :
			unsigned int	m_key_length;
	};

//
// asymmetric_cipher_value_t
//

class	asymmetric_cipher_value_t
	:	public cipher_value_t
	{
		OESS_SERIALIZER( asymmetric_cipher_value_t )

		public :
			asymmetric_cipher_value_t(
				const std::string & type,
				const std::string & preferred )
				:	cipher_value_t( type,	preferred )
				{}
			asymmetric_cipher_value_t(
				const asymmetric_cipher_value_t & o )
				:	cipher_value_t( o )
				{}
			virtual ~asymmetric_cipher_value_t() {}

			asymmetric_cipher_value_t &
			operator =( const asymmetric_cipher_value_t & o )
				{
					cipher_value_t::operator=( o );
					return *this;
				}

			virtual value_t *
			clone() const = 0;

			virtual std::ostream &
			dump( std::ostream & to ) const
				{
					to << "[asymmetric]";
					return  cipher_value_t::dump( to );
				}
	};

//
// blowfish_cipher_value_t
//

class	blowfish_cipher_value_t
	:	public symmetric_cipher_value_t
	{
		OESS_SERIALIZER( blowfish_cipher_value_t )

		static std::string
		type_name()
			{
				return "blowfish_cipher_value_t";
			}

		public :
			blowfish_cipher_value_t()
				:	symmetric_cipher_value_t( type_name(),	"blowfish", 64 )
				{}
			blowfish_cipher_value_t(
				const blowfish_cipher_value_t & o )
				:	symmetric_cipher_value_t( o )
				{}
			virtual ~blowfish_cipher_value_t() {}

			blowfish_cipher_value_t &
			operator =( const blowfish_cipher_value_t & o )
				{
					symmetric_cipher_value_t::operator=( o );
					return *this;
				}

			virtual value_t *
			clone() const
				{
					return new blowfish_cipher_value_t( *this );
				}
	};

#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/subclassing_by_extension/pack_unpack/v1/empty.dat", d );
}

void
test_store_v1_not_empty()
{
	data_t d;
	d.insert( 1, new timeout_value_t( 1000 ) );
	d.insert( 4, new compression_value_t( "gzip" ) );
	d.insert( 0, new compression_value_t( "bzip2" ) );
	d.insert( 3, new blowfish_cipher_value_t() );

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

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

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

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

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

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

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

void
test_load_v2_not_empty()
{
	data_t d;
	load( "test/subclassing_by_extension/pack_unpack/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/subclassing_by_extension/pack_unpack/v2/not_empty.dat", d );

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

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

void
test_load_v2_not_empty_and_drop_extensions()
{
	data_t d;
	load( "test/subclassing_by_extension/pack_unpack/v2/not_empty.dat", d );

	d.drop_value_extensions();
	std::cout << "load_v2_not_empty_and_drop_extensions: "
		<< d << std::endl;

	store( "test/subclassing_by_extension/pack_unpack/v2/from_v1_without_extensions.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
		,	&test_load_v2_not_empty_and_drop_extensions
		,	0
		};

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

	return 0;
}
