/*

Copyright (c) 2002-2005, Yauheni Akhotnikau
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

- Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.

- The name of the author may not be used to endorse or promote products derived
from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.

*/

#include <algorithm>
#include <functional>

#include <cls_2/h/simple_tag_formatter.hpp>

#include <oess_1/scheme/impl/h/text_scheme_repres.hpp>
#include <oess_1/scheme/impl/h/cls_tags.hpp>

namespace oess_1 {

namespace scheme {

namespace impl {

namespace text_scheme_representation {

//
// base_to_type_t
//
class	base_to_type_t :
	public std::unary_function< const base_t &, void >
{
	private :
		tag_type_t &	m_tag_type;

	public :
		base_to_type_t(
			tag_type_t & tag_type ) :
			m_tag_type( tag_type )
		{}

		result_type
		operator()( argument_type a )
		{
			if( a.is_extension_of() )
				m_tag_type.derive_as_extension_of(
					a.query_type().query_name() );
			else
			{
				tag_base_t * tag_base = new tag_base_t();
				tag_base->make_defined(
					a.query_type().query_name(),
					a.is_virtual() );

				m_tag_type.query_bases().make_defined( tag_base );
			}
		}
};

//
// process_bases
//
static void
process_bases(
	const type_t & what,
	tag_type_t & tag_type )
{
	pseudo_base_list_t	bases( what );
	std::for_each( bases.begin(), bases.end(),
		base_to_type_t( tag_type ) );
}

//
// attr_type_kind_processor_t
//
class	attr_type_kind_processor_t :
	public type_kind_processor_t
{
	private :
		tag_attr_type_t &	m_tag_attr_type;

	public :
		attr_type_kind_processor_t(
			tag_attr_type_t & tag_attr_type ) :
			m_tag_attr_type( tag_attr_type )
		{}

	protected :
		virtual void
		on_value_kind(
			const attr_type_t & k )
		{
			m_tag_attr_type.make_defined_as_value(
				k.query_type().query_name() );
		}

		virtual void
		on_ptr_kind(
			const attr_type_t & k )
		{
			m_tag_attr_type.make_defined_as_ptr(
				k.query_type().query_name() );
		}

		virtual void
		on_extension_kind(
			const attr_type_t & k )
		{
			m_tag_attr_type.make_defined_as_extension(
				k.query_type().query_name() );
		}
};

//
// attr_cont_kind_processor_t
//
class	attr_cont_kind_processor_t :
	public cont_kind_processor_t
{
	private :
		tag_attr_t &	m_tag_attr;
		const std::string &	m_attr_name;

	public :
		attr_cont_kind_processor_t(
			tag_attr_t & tag_attr,
			const std::string & attr_name ) :
			m_tag_attr( tag_attr ),
			m_attr_name( attr_name )
		{}

	protected :
		virtual void
		on_single_obj_kind(
			const single_obj_kind_t & k )
		{
			m_tag_attr.make_defined_as_single_obj( m_attr_name );
		}

		virtual void
		on_stl_vector_kind(
			const stl_vector_kind_t & k )
		{
			m_tag_attr.make_defined_as_stl_vector( m_attr_name );
		}

		virtual void
		on_stl_list_kind(
			const stl_list_kind_t & k )
		{
			m_tag_attr.make_defined_as_stl_list( m_attr_name );
		}

		virtual void
		on_stl_deque_kind(
			const stl_deque_kind_t & k )
		{
			m_tag_attr.make_defined_as_stl_deque( m_attr_name );
		}

		virtual void
		on_stl_set_kind(
			const stl_set_kind_t & k )
		{
			m_tag_attr.make_defined_as_stl_set( m_attr_name );
		}

		virtual void
		on_stl_multiset_kind(
			const stl_multiset_kind_t & k )
		{
			m_tag_attr.make_defined_as_stl_multiset( m_attr_name );
		}

		virtual void
		on_stl_map_kind(
			const stl_map_kind_t & k )
		{
			m_tag_attr.make_defined_as_stl_map( m_attr_name,
				k.query_key_type().query_name() );
		}

		virtual void
		on_stl_multimap_kind(
			const stl_multimap_kind_t & k )
		{
			m_tag_attr.make_defined_as_stl_multimap( m_attr_name,
				k.query_key_type().query_name() );
		}

		virtual void
		on_fixed_vector_kind(
			const fixed_vector_kind_t & k )
		{
			m_tag_attr.make_defined_as_fixed_vector( m_attr_name,
				k.query_capacity() );
		}
};

//
// attr_to_tag_t
//

//! Предикат для std::for_each.
template< class Tag >
class	attr_to_tag_t :
	public std::unary_function< const attr_t &, void >
{
	private :
		Tag &	m_receiver;

		//! Установить значения атрибута по-умолчанию
		//! в теге описания атрибута.
		/*! \since v.1.2.0 */
		void
		process_attr_default(
			//! Описание значения по-умолчанию.
			const attr_default_t & attr_default,
			//! Тег с описанием атрибута.
			tag_attr_t & tag )
		{
			tag_attr_default_t & tag_default = tag.query_default();
			tag_default.make_defined();
			if( attr_default.is_cpp() )
				tag_default.make_cpp( attr_default.cpp() );
			if( attr_default.is_optional() )
			{
				tag_default.changeable_present_if().make_defined();
				if( attr_default.present_if().is_cpp() )
					tag_default.changeable_present_if().make_cpp(
						attr_default.present_if().cpp() );
			}
		}

	public :
		attr_to_tag_t( Tag & receiver )
		:	m_receiver( receiver )
		{}

		result_type
		operator()( argument_type a ) {
			tag_attr_t * attr = new tag_attr_t();

			attr_type_kind_processor_t type_kind_processor(
				attr->query_attr_type() );
			type_kind_processor.process_type_kind(
				a.query_attr_type() );

			attr_cont_kind_processor_t cont_kind_processor(
				*attr, a.query_name() );
			cont_kind_processor.process_cont_kind(
				a.query_attr_type().query_cont_kind() );

			const attr_default_t * attr_default = a.query_attr_default();
			if( attr_default )
				process_attr_default( *attr_default, *attr );

			m_receiver.query_attrs().make_defined( attr );
		}
};

//
// process_attrs
//
static void
process_attrs(
	const type_t & what,
	tag_type_t & tag_type )
{
	pseudo_extension_attr_list_t	attrs( what, 0 );
	std::for_each( attrs.begin(), attrs.end(),
		attr_to_tag_t< tag_type_t >( tag_type ) );
}

//
// process_cpp_mapping
//
static void
process_cpp_mapping(
	const type_t & what,
	tag_type_t & tag_type )
{
	tag_type.query_cpp_mapping().make_defined(
		what.query_cpp_mapping().query_name() );
}

//
// process_extension_attrs
//

/*! \since v.1.2.0
	\brief Обработать атрибуты указанного расширения.
*/
static void
process_extension_attrs(
	const type_t & what,
	unsigned int extension,
	unsigned int extension_deep,
	tag_extension_t & tag_extension )
{
	pseudo_extension_attr_list_t	attrs( what, extension );
	std::for_each( attrs.begin(), attrs.end(),
		attr_to_tag_t< tag_extension_t >( tag_extension ) );

	if( extension != extension_deep )
		process_extension_attrs( what, extension + 1, extension_deep,
			tag_extension.make_extension() );

	tag_extension.make_defined();
}

//
// process_extensions
//

//! Обработка расширений типа.
/*! \since v.1.2.0 */
static void
process_extensions(
	const type_t & what,
	tag_type_t & tag_type )
{
	unsigned int extension_deep = what.extension_deep();
	if( extension_deep )
	{
		process_extension_attrs(
			what, 1, extension_deep, tag_type.make_extension() );
	}
}

} /* namespace text_scheme_representation */

//
// make_type_representation
//

/*
	Сформировать текстовое описание типа схемы данных.
*/
OESS_1__SCHEME__FUNC(void)
make_type_representation(
	const type_t & what,
	std::ostream & to,
	bool is_mapping_needed )
{
	using namespace text_scheme_representation;

	tag_type_t tag_type;
	tag_type.make_defined( what.query_name(),
		what.is_abstract(),
		what.is_extensible() );
	if( what.is_subclassing_by_extension() )
		tag_type.turn_subclassing_by_extension_on();

	process_bases( what, tag_type );
	process_attrs( what, tag_type );
	process_extensions( what, tag_type );

	if( is_mapping_needed )
		process_cpp_mapping( what, tag_type );

	cls_2::simple_tag_formatter_t< std::ostream >
		formatter( to );
	tag_type.format( formatter );
}

} /* namespace impl */

} /* namespace scheme */

} /* namespace oess_1 */
