/*

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 <oess_1/defs/h/ex.hpp>

#include <oess_1/scheme/h/errno.hpp>
#include <oess_1/scheme/h/builtin_names.hpp>

#include <oess_1/scheme/impl/h/scheme_impl.hpp>

namespace oess_1 {

namespace scheme {

namespace impl {

//FIX ME: в исключениях, связанных с неправильными или
//недопустимыми именами типов необходимо сообщать как
//можно больше информации о контексте (например имя типа и
//атрибута, для которого не правильно указано имя типа и т.д.)

//
// std_attr_editor_t
//
std_attr_editor_t::std_attr_editor_t(
	std_scheme_t & scheme ) :
	m_scheme( scheme )
{
}

std_attr_editor_t::~std_attr_editor_t()
{
}

void
std_attr_editor_t::def(
	const std::string & attr_name,
	const std::string & type_name,
	unsigned int extension_level )
{
	m_ref_attr = ref_std_attr_t( new std_attr_t(
		attr_name,
		find_type( type_name ),
		extension_level ) );
}

const ref_attr_t &
std_attr_editor_t::query_ref_attr() const
{
	return m_ref_attr;
}

void
std_attr_editor_t::make_ptr_kind()
{
	m_ref_attr->set_type_kind( attr_type_t::e_ptr );
}

void
std_attr_editor_t::make_extension_kind()
{
	m_ref_attr->set_type_kind( attr_type_t::e_extension );
}

void
std_attr_editor_t::make_single_obj_kind()
{
	ref_attr_cont_kind_t ref( new single_obj_kind_t() );
	set_cont_kind( ref );
}

void
std_attr_editor_t::make_stl_vector_kind()
{
	ref_attr_cont_kind_t ref( new stl_vector_kind_t() );
	set_cont_kind( ref );
}

void
std_attr_editor_t::make_stl_list_kind()
{
	ref_attr_cont_kind_t ref( new stl_list_kind_t() );
	set_cont_kind( ref );
}

void
std_attr_editor_t::make_stl_deque_kind()
{
	ref_attr_cont_kind_t ref( new stl_deque_kind_t() );
	set_cont_kind( ref );
}

void
std_attr_editor_t::make_stl_set_kind()
{
	ref_attr_cont_kind_t ref( new stl_set_kind_t() );
	set_cont_kind( ref );
}

void
std_attr_editor_t::make_stl_multiset_kind()
{
	ref_attr_cont_kind_t ref( new stl_multiset_kind_t() );
	set_cont_kind( ref );
}

void
std_attr_editor_t::make_stl_map_kind(
	const std::string & key_type_name )
{
	const type_t & type = find_type( key_type_name );

	ref_attr_cont_kind_t ref( new std_stl_map_kind_t( type ) );
	set_cont_kind( ref );
}

void
std_attr_editor_t::make_stl_multimap_kind(
	const std::string & key_type_name )
{
	const type_t & type = find_type( key_type_name );

	ref_attr_cont_kind_t ref( new std_stl_multimap_kind_t( type ) );
	set_cont_kind( ref );
}

void
std_attr_editor_t::make_fixed_vector_kind(
	size_t capacity )
{
	ref_attr_cont_kind_t ref( new std_fixed_vector_kind_t( capacity ) );
	set_cont_kind( ref );
}

void
std_attr_editor_t::set_default_existence(
	bool is_optional )
{
	m_ref_attr->make_std_attr_default( is_optional );
}

void
std_attr_editor_t::set_cpp_default(
	const std::string & expr )
{
	m_ref_attr->std_attr_default().set_cpp( expr );
}

void
std_attr_editor_t::set_cpp_present_if(
	const std::string & expr )
{
	m_ref_attr->std_attr_default().std_present_if().set_cpp( expr );
}

void
std_attr_editor_t::set_cont_kind(
	const ref_attr_cont_kind_t & ref )
{
	m_ref_attr->set_cont_kind( ref );
}

const type_t &
std_attr_editor_t::find_type(
	const std::string & type_name ) const
{
	const ref_std_type_t & ref_type =
		m_scheme.find_or_create_type( type_name );

	return *( ref_type.query() );
}

//
// ref_std_attr_editor_t
//
SMART_REF_3_IMPL_REF( std_attr_editor_t )

//
// std_cpp_mapping_editor_t
//
std_cpp_mapping_editor_t::std_cpp_mapping_editor_t(
	std_cpp_mapping_t & cpp_mapping ) :
	m_cpp_mapping( cpp_mapping )
{
}

std_cpp_mapping_editor_t::~std_cpp_mapping_editor_t()
{
}

void
std_cpp_mapping_editor_t::set_name(
	const std::string & name )
{
	m_cpp_mapping.set_name( name );
}

//
// ref_std_cpp_mapping_editor_t
//
SMART_REF_3_IMPL_REF( std_cpp_mapping_editor_t )

//
// std_type_editor_t
//
std_type_editor_t::std_type_editor_t(
	std_scheme_t & scheme )
:	m_scheme( scheme )
,	m_extension_deep( 0 )
{
}

std_type_editor_t::~std_type_editor_t()
{
}

void
std_type_editor_t::def( const std::string & name )
{
	m_ref_type = m_scheme.find_or_create_type( name );
	// Тип не должен быть определенным!
	if( m_ref_type->is_defined() )
		OESS_THROW_LOGIC(
			oess_1::scheme::err::c_type_already_defined,
			"type: " << name )

	// Нужно определить редактор отображения в C++.
	m_ref_cpp_mapping_editor = ref_std_cpp_mapping_editor_t(
		new std_cpp_mapping_editor_t(
			m_ref_type->query_cpp_mapping() ) );
}

void
std_type_editor_t::make_defined()
{
	m_ref_type->make_defined();
}

void
std_type_editor_t::make_abstract()
{
	m_ref_type->make_abstract();
}

void
std_type_editor_t::make_extensible()
{
	m_ref_type->make_extensible();
}

void
std_type_editor_t::turn_subclassing_by_extension_on()
{
	m_ref_type->turn_subclassing_by_extension_on();
}

void
std_type_editor_t::add_base(
	const std::string & name,
	bool is_virtual )
{
	// Указанный тип должен существовать.
	const ref_std_type_t & ref_type =
		m_scheme.find_or_create_type( name );

	// Указанный тип не должен быть встроенным.
	if( ref_type->is_builtin() )
		OESS_THROW_LOGIC(
			oess_1::scheme::err::c_builtin_type_as_base,
			"builtin type: " << name << " as base for: "
			<< m_ref_type->query_name() )

	// Можно попробовать добавить тип в список базовых.
	ref_base_t ref_base( new std_base_t(
		*( ref_type.query() ), is_virtual ) );
	m_ref_type->add_base( ref_base );
}

void
std_type_editor_t::add_base_as_extension(
	const std::string & name )
{
	// Указанный тип должен существовать.
	const ref_std_type_t & ref_type =
		m_scheme.find_or_create_type( name );

	// Указанный тип не должен быть встроенным.
	if( ref_type->is_builtin() )
		OESS_THROW_LOGIC(
			oess_1::scheme::err::c_builtin_type_as_base,
			"builtin type: " << name << " as base for: "
			<< m_ref_type->query_name() )

	// Можно попробовать добавить тип в список базовых.
	ref_base_t ref_base( new std_base_t(
		*ref_type,
		std_base_t::subclassing_by_extension ) );
	m_ref_type->add_base_as_extension( ref_base );
}

attr_editor_t &
std_type_editor_t::start_attr_def(
	const std::string & attr_name,
	const std::string & type_name )
{
	if( m_ref_attr_editor.query() )
		OESS_THROW_PHYSIC( oess_1::scheme::err::c_attr_editor_exists,
			"std_type_editor_t::start_attr_def( \""
			<< attr_name << "\", \""
			<< type_name << "\")" )

	ref_std_attr_editor_t ref_editor(
		new std_attr_editor_t( m_scheme ) );
	ref_editor->def( attr_name, type_name, m_extension_deep );

	// Если мы оказались здесь без исключений,
	// значит редактор атрибута успешно создан.
	m_ref_attr_editor = ref_editor;

	return *( m_ref_attr_editor.query() );
}

void
std_type_editor_t::commit_attr_def()
{
	if( !m_ref_attr_editor.query() )
		OESS_THROW_PHYSIC( oess_1::scheme::err::c_attr_editor_not_exists,
			"std_type_editor_t::commit_attr_def()" )

	// Защищаемся от возможных исключений.
	ref_std_attr_editor_t ref_editor( m_ref_attr_editor );
	m_ref_attr_editor.destroy();

	m_ref_type->add_attr(
		ref_editor->query_ref_attr(), m_extension_deep );
}

void
std_type_editor_t::rollback_attr_def()
{
	if( !m_ref_attr_editor.query() )
		OESS_THROW_PHYSIC( oess_1::scheme::err::c_attr_editor_not_exists,
			"std_type_editor_t::rollback_attr_def()" )

	m_ref_attr_editor.destroy();
}

cpp_mapping_editor_t &
std_type_editor_t::cpp_mapping()
{
	return *( m_ref_cpp_mapping_editor.query() );
}

void
std_type_editor_t::start_extension()
{
	if( m_ref_attr_editor.query() )
		OESS_THROW_PHYSIC( oess_1::scheme::err::c_attr_editor_exists,
			"unable to start extension when unfinished attr exists" )

	++m_extension_deep;
	m_ref_type->start_extension();
}

//
// ref_std_type_editor_t
//
SMART_REF_3_IMPL_REF( std_type_editor_t )

//
// std_scheme_t
//
std_scheme_t::std_scheme_t()
{
	fill_builtin_types();
}

std_scheme_t::~std_scheme_t()
{
}

size_t
std_scheme_t::query_type_count() const
{
	return m_user_types.size();
}

const type_t &
std_scheme_t::query_type( size_t index ) const
{
	if( index >= m_user_types.size() )
		OESS_THROW_LOGIC( oess_1::scheme::err::c_invalid_index_of_type,
			"type count: " << m_user_types.size()
			<< ", index: " << index )

	return *( m_user_types[ index ].query() );
}

const type_t *
std_scheme_t::find_type( const std::string & name ) const
{
	type_map_t::const_iterator it = m_type_map.find( name );
	if( it != m_type_map.end() )
		return (*it).second;
	else
		return 0;
}

type_editor_t &
std_scheme_t::start_type_def(
	const std::string & name )
{
	if( m_ref_type_editor.query() )
		OESS_THROW_PHYSIC( oess_1::scheme::err::c_type_editor_exists,
			"std_scheme_t::start_type_def( \""
			<< name << "\")" )

	ref_std_type_editor_t ref_editor(
		new std_type_editor_t( *this ) );
	ref_editor->def( name );

	// Если мы оказались здесь без исключений,
	// значит редактор типа успешно создан.
	m_ref_type_editor = ref_editor;

	return *m_ref_type_editor;
}

void
std_scheme_t::commit_type_def()
{
	if( !m_ref_type_editor.query() )
		OESS_THROW_PHYSIC( oess_1::scheme::err::c_type_editor_not_exists,
			"std_scheme_t::commit_type_def()" )

	// Защищаемся от возможных исключений.
	ref_std_type_editor_t ref_editor( m_ref_type_editor );
	m_ref_type_editor.destroy();

	ref_editor->make_defined();
}

void
std_scheme_t::rollback_type_def()
{
	if( !m_ref_type_editor.query() )
		OESS_THROW_PHYSIC( oess_1::scheme::err::c_type_editor_not_exists,
			"std_scheme_t::rollback_type_def()" )

	m_ref_type_editor.destroy();
}

class	type_finder_t :
	public std::unary_function< const ref_std_type_t &, bool >
{
	private :
		const std::string &	m_key;
	public :
		type_finder_t( const std::string & key ) : m_key( key ) {}

		result_type
		operator()( argument_type a ) {
			return ( m_key == a->query_name() );
		}
};

const ref_std_type_t &
std_scheme_t::find_or_create_type(
	const std::string & name )
{
	type_finder_t finder( name );

	ref_std_type_deque_t::iterator it = std::find_if(
		m_builtin_types.begin(), m_builtin_types.end(), finder );
	if( it != m_builtin_types.end() )
		// Тип найден среди встроенных типов.
		return (*it);

	it = std::find_if(
		m_user_types.begin(), m_user_types.end(), finder );
	if( it != m_user_types.end() )
		// Тип найден среди пользовательских типов.
		return (*it);

	// Нужно создать новый тип и добавить его в
	// список пользовательских типов.
	ref_std_type_t ref_type( new std_type_t( name ) );
	m_user_types.push_back( ref_type );
	add_to_map( *ref_type );

	return (*(m_user_types.rbegin()));
}

void
std_scheme_t::add_to_map( std_type_t & type )
{
	m_type_map.insert( type_map_t::value_type(
		type.query_name(), &type ) );
}

void
std_scheme_t::fill_builtin_types()
{
	add_builtin_type( builtin_char() );
	add_builtin_type( builtin_schar() );
	add_builtin_type( builtin_uchar() );
	add_builtin_type( builtin_short() );
	add_builtin_type( builtin_ushort() );
	add_builtin_type( builtin_int() );
	add_builtin_type( builtin_uint() );
	add_builtin_type( builtin_single() );
	add_builtin_type( builtin_double() );
	add_builtin_type( builtin_std_string() );
	add_builtin_type( builtin_ent_id() );

	add_builtin_serializable_t();
}

void
std_scheme_t::add_builtin_type(
	const std::string & name )
{
	std_type_t * ptr = new std_type_t( name, true );
	ptr->make_defined();

	ref_std_type_t ref_type( ptr );
	m_builtin_types.push_back( ref_type );
	add_to_map( *ref_type );
}

void
std_scheme_t::add_builtin_serializable_t()
{
	std_type_t * ptr = new std_type_t(
		builtin_stdsn_serializable(), true );
	ptr->make_defined();
	ptr->make_abstract();

	ref_std_type_t ref_type( ptr );
	m_builtin_types.push_back( ref_type );
	add_to_map( *ref_type );
}

//
// create_std_scheme
//
scheme_t *
create_std_scheme() {
	return new std_scheme_t();
}

//
// destroy_std_scheme
//
void
destroy_std_scheme( scheme_t * ptr ) {
	delete ptr;
}

} /* namespace impl */

} /* namespace scheme */

} /* namespace oess_1 */

