/*

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.

*/

/*!
	\file oess_1/tlv/h/compound.hpp
	\brief Класс %oess_1::tlv::coumpound_tlv_t.
*/

#if !defined( _OESS_1__TLV__COMPOUND_HPP_ )
#define _OESS_1__TLV__COMPOUND_HPP_

#include <oess_1/tlv/h/declspec.hpp>

#include <map>
#include <string>
#include <functional>
#include <algorithm>

#include <cpp_util_2/h/nocopy.hpp>

#include <oess_1/defs/h/ex.hpp>

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

#include <oess_1/tlv/h/errno.hpp>
#include <oess_1/tlv/h/base.hpp>
#include <oess_1/tlv/h/default_processors.hpp>

namespace oess_1
{

namespace tlv
{

//
// tlv_add_caller_t
//

//! Вспомогательный класс для упрощения занесения
//! подчиненных TLV в объемлющий TLV.
/*!
	\since v.1.0.9

	При использовании compound_tlv_t возникает одна сложность:
	все подчиненные TLV должны быть занесены в объемлющий
	TLV посредством compound_tlv_t::tlv_add(). Если же объемлющий
	TLV имеет много подчиненных TLV, то легко забыть вызвать
	tlv_add для какого-то из них. Что приведет к труднообнаруживаемой
	ошибке.

	Класс tlv_add_caller_t упрощает эту ситуацию следующим образом:
	- для каждого подчиненного TLV, нуждающегося в автоматическом
	вызове compound_tlv_t::tlv_add(), описывается атрибут типа
	tlv_add_caller_t;
	- в конструктор каждого из объектов необходимо передавать
	ссылку на объект compound_tlv_t:

\code
typedef oess_1::tlv::compound_tlv_t<tag_type_t > compound_t;
class	my_compound_tlv_t :
	public compound_t
{
	private :
		oess_1::tlv::tagged_scalar_wrapper_t<
				0x02, string_tlv_t >
			m_member_1;
		const tlv_add_t	m_member_1_inserter;

		oess_1::tlv::tagged_scalar_wrapper_t<
				0x05, oess_1::tlv::scalar_tlv_t<
					tag_type_t, oess_1::ushort_t > >
			m_member_2;
		const tlv_add_t	m_member_2_inserter;

	public :
		my_compound_tlv_t(
			const std::string & member_1,
			oess_1::ushort_t member_2 )
			:
				compound_t( 0x01 ),
				m_member_1( member_1 ),
				m_member_1_inserter( tlv_self(), m_member_1 ),

				m_member_2( member_2 ),
				m_member_2_inserter( tlv_self(), m_member_2 ),
		{
		}

		my_compound_tlv_t()
			:
				compound_t( 0x01 ),
				m_member_1_inserter( tlv_self(), m_member_1 ),
				m_member_2_inserter( tlv_self(), m_member_2 )
		{
		}
};
\endcode
*/
template< class Compound >
class	tlv_add_caller_t
{
	public :
		/*!
			Только вызывает метод tlv_add() у объекта owner.
		*/
		tlv_add_caller_t(
			//! Объемлющий TLV.
			Compound & owner,
			//! Вложенный TLV.
			tlv_base_t & nested )
		{
			owner.tlv_add( nested );
		}
};

//
// compound_tlv_t
//

//! TLV для сериализации составных объектов.
/*!
	Объекты compound_tlv_t не могут копироваться.

\par Пример (см. oess_1::tlv::basic_string_tlv_t)
\code
typedef oess_1::tlv::compound_tlv_t<oess_1::ushort_t > compound_t;
class	my_compound_tlv_t :
	public compound_t
{
	private :
		oess_1::tlv::basic_string_tlv_t<
				oess_1::ushort, oess_1::char_t >
			m_member_1;
		oess_1::tlv::basic_string_tlv_t<
				oess_1::ushort, oess_1::char_t >
			m_member_2;
	public :
		my_compound_tlv_t() :
			compound_t( 0x01 ),
			m_member_1( 0x02 ),
			m_member_2( 0x03 )
		{
			tlv_add( 0x02, m_member_1 );
			tlv_add( 0x03, m_member_2 );
		}
};
\endcode
*/
template<
	class Tag_type,
	class Tag_processor = default_tag_processor_t< Tag_type >,
	class Length_processor = default_length_processor_t< Tag_type > >
class	compound_tlv_t :
	public tlv_base_t,
	private cpp_util_2::nocopy_t
{
	public :
		//! Синоним для compound_tlv_t< Tag_type,
		//! Tag_processor, Length_processor >.
		typedef compound_tlv_t< Tag_type,
				Tag_processor, Length_processor >
			self_t;

		//! Тип объекта %tlv_add_caller_t для
		//! данного составного TLV.
		/*! \since v.1.0.9 */
		typedef tlv_add_caller_t< self_t >	tlv_add_t;

	private :
		//! Тип словаря дочерних TLV.
		typedef std::map< default_tag_type_t, tlv_base_t * >
			tlv_map_t;

		//! Предикат для std::for_each.
		/*!
			Вычисляет размер всех дочерних TLV.
		*/
		class	length_calc_t :
			public std::unary_function<
				const typename tlv_map_t::value_type &, void >
		{
			private :
				size_t &	m_length;
			public :
				length_calc_t( size_t & length )
					:
						m_length( length )
				{
				}

				result_type
				operator()( argument_type a )
				{
					m_length += a.second->tlv_size();
				}
		};

		//! Предикат для std::for_each.
		/*!
			Записывает всех дочерние TLV.
		*/
		class	packer_t :
			public std::unary_function<
				const typename tlv_map_t::value_type &, void >
		{
			private :
				oess_1::io::obinstream_t &	m_stream;
			public :
				packer_t( oess_1::io::obinstream_t & stream )
					:
						m_stream( stream )
				{
				}

				result_type
				operator()( argument_type a )
				{
					a.second->tlv_pack( m_stream );
				}
		};

		//! Значение поля Tag.
		Tag_processor	m_tag;

		//! Обработчик поля Length.
		Length_processor	m_length;

		//! Дочерние TLV.
		tlv_map_t	m_tlvs;

		//! Попытка извлечь TLV из потока.
		/*!
			Вызывается, когда из потока уже было извлечено поле Tag.
		*/
		void
		try_unpack_tlv(
			//! Поле Tag, извлеченное из потока.
			default_tag_type_t tag,
			//! Поток, из которого происходит чтение.
			oess_1::io::ibinstream_t & s )
		{
			typename tlv_map_t::iterator it = m_tlvs.find( tag );
			if( it != m_tlvs.end() )
				// Тег известен.
				it->second->tlv_unpack( s, tag );
			else
				// Тег не известен.
				tlv_on_unexpected_tag( tag, s );
		}


	protected :
		//! Обработчик не описанного TLV, который был
		//! обнаружен во входном потоке.
		/*!
			Реализация в базовом классе порождает исключение
			oess_1::io::physic_ex_t с кодом
			oess_1::tlv::err::c_unexpected_tag.
		*/
		virtual void
		tlv_on_unexpected_tag(
			//! Поле Tag, извлеченное из потока.
			default_tag_type_t tag,
			//! Поток, из которого происходит чтение.
			/*!
				Производные классы могут использовать данный
				аргумент для извлечения из потока значения TLV.
			*/
			oess_1::io::ibinstream_t & s )
		{
			OESS_THROW_PHYSIC( oess_1::tlv::err::c_unexpected_tag,
				"compound tag: " << m_tag.query_tag()
				<< ", child tag found: " << tag )
		}

	public :
		compound_tlv_t( Tag_type id )
			:
				m_tag( id )
		{
		}

		virtual ~compound_tlv_t()
		{
		}

		//! Получить значение Tag.
		virtual default_tag_type_t
		tlv_tag() const
		{
			return m_tag.query_tag();
		}

		//! Добавить дочерний TLV.
		/*!
			Если в compound_tlv_t уже был описан TLV
			Tag, равным tlv.tlv_tag(), то его описание меняется.
		*/
		void
		tlv_add(
			//! Дочерний TLV.
			/*!
				Указанный объект должен существовать все время,
				пока он описан в объекте compound_tlv_t.
			*/
			tlv_base_t & tlv )
		{
			m_tlvs[ tlv.tlv_tag() ] = &tlv;
		}

		//! Добавить дочерний TLV с указанным полем Tag.
		/*!
			Если в compound_tlv_t уже был описан TLV с таким
			Tag, то его описание меняется.

			Данный метод предназначен для случаев, когда
			дочерний объект tlv должен обрабатывать TLV с
			иным Tag (т.е. tag != tlv.tlv_tag()).

			Либо для случаев, когда объект tlv может работать
			с несколькими представлениями TLV, каждое из которых
			имеет собственное значение Tag.
		*/
		void
		tlv_add(
			//! Поле Tag для дочернего TLV.
			default_tag_type_t tag,
			//! Дочерний TLV.
			/*!
				Указанный объект должен существовать все время,
				пока он описан в объекте compound_tlv_t.
			*/
			tlv_base_t & tlv )
		{
			m_tlvs[ tag ] = &tlv;
		}

		//! Изъять дочерний TLV с указанным полем Tag.
		/*!
			Отсутствие дочернего TLV ошибкой не считается.
		*/
		void
		tlv_remove(
			//! Идентификатор изымаемого TLV.
			default_tag_type_t tag )
		{
			m_tlvs.erase( tag );
		}

		//! Количество байт, которые требуются
		//! для представления всего TLV (с учетом Tag, Length,
		//! Value).
		virtual size_t
		tlv_size() const
		{
			size_t length = 0;
			std::for_each( m_tlvs.begin(), m_tlvs.end(),
				length_calc_t( length ) );

			length += m_length.tlv_length_size( length );
			length += m_tag.tlv_tag_size();

			return length;
		}

		//! Извлечение дочерних TLV из потока.
		virtual void
		tlv_unpack(
			//! Поток, с текущей позиции которого будет
			//! осуществляться чтение полей Length и Value.
			oess_1::io::ibinstream_t & s,
			//! В данном классе игнорируется.
			default_tag_type_t )
		{
			size_t length;
			m_length.tlv_unpack_length( s, length );

			// Последовательно считываем дочерние TLV.
			oess_1::io::isubbinstream_t tmp_istream( s, length );
			while( !tmp_istream.eof() )
			{
				Tag_processor tag;
				tag.tlv_unpack_tag( tmp_istream );

				try_unpack_tlv( tag.query_tag(), tmp_istream );
			}
		}

		//! Запись TLV в выходной поток.
		virtual void
		tlv_pack(
			//! Поток, с текущей позиции которого будет
			//! осуществляться запись TLV.
			oess_1::io::obinstream_t & s ) const
		{
			// Длина всех дочерних тегов.
			size_t length = 0;
			std::for_each( m_tlvs.begin(), m_tlvs.end(),
				length_calc_t( length ) );

			m_tag.tlv_pack_tag( s );
			m_length.tlv_pack_length( s, length );
			if( length )
			{
				packer_t packer( s );
				std::for_each( m_tlvs.begin(), m_tlvs.end(), packer );
			}
		}

		//! Возвращает ссылку на самого себя.
		/*! \since v.1.0.9

			Предназначен для использования класса
			tlv_add_caller_t. */
		self_t &
		tlv_self()
		{
			return *this;
		}
};

} /* namespace tlv */

} /* namespace oess_1 */

#endif

