namespace oess_1
{
/*!
	\brief Компонента %io.

	Данная компонента содержит основные классы и определения средств
	ввода/вывода простейших типов.

Вершиной иерархии классов ввода/вывода являются классы istream_t и ostream_t,
которые определяют интерфейс потоков ввода-вывода.  Классы istream_t, ostream_t
являются абстрактными. Предполагается, что сериализация возможна в различные
представления, как текстовые, так и двоичные. Поэтому данные классы не
привязаны к конкретным форматам представления данных. Вместо этого istream_t,
ostream_t определяют типы, которые поддерживаются системой ввода-вывода
ObjESSty. Для этих типов определены операторы сдвига из/в istream_t, ostream_t.

Для поддержки ввода/вывода в двоичный формат предназначенные классы
ibinstream_t, obinstream_t и производные от них классы. Сами классы
ibinstream_t, obinstream_t так же являются абстрактными. Они определяют только
процедуры преобразования значений из bin-endian представления в представление
конкретной платформы и обратно. Но они не знают откуда именно нужно брать
данные или куда их нужно помещать. Этим занимаются производные классы.

Для того, чтобы коротко описать существующие классы ввода/вывода определим
структуру на которой использование классов будет демонстрироваться. Пусть
есть описание некоторого заголовка запроса/ответа в клиент-сервером
приложении:
\code
struct	request_header_t
	{
		// Номер версии протокола, в котором определен запрос.
		oess_1::ushort_t	m_version;
		// Идентификатор запроса.
		oess_1::uint_t	m_req_id;
		// Длина запроса без учета заголовка.
		oess_1::uint_t m_data_len;
		// Зарезервированные поля.
		oess_1::uint_t	m_reserved[ 5 ];
	};
\endcode

Для того, чтобы объекты request_header_t можно было читать/записывать из/в
потоков ввода/вывода ObjESSty необходимо определить для request_header_t
операторы сдвига:
\code
oess_1::io::istream_t &
operator>>( oess_1::io::istream_t & s, request_header_t & o )
	{
		s >> o.m_version >> o.m_req_id >> o.m_data_len;
		s.read( o.m_reserved,
				sizeof( o.m_reserved ) / sizeof( o.m_reserved[ 0 ] ) ); 
		return s;
	}
oess_1::io::ostream_t &
operator<<( oess_1::io::ostream_t & s, const request_header_t & o )
	{
		s << o.m_version << o.m_req_id << o.m_data_len;
		s.write( o.m_reserved,
				sizeof( o.m_reserved ) / sizeof( o.m_reserved[ 0 ] ) ); 
		return s;
	}
\endcode

Класс ibstring_t полезен в случаях, когда сериализованные данные находятся
в объекте std::string. Например, если они были прочитаны блоком из файла
или переданы в виде std::string в каком-либо объекте-сообщении. Например,
для извлечения request_header_t можно воспользоваться ibstring_t вот так:
\code
request_header_t
unpack( const std::string & from )
	{
		oess_1::io::ibstring_t stream( from );
		request_header_t result;
		stream >> result;

		return result;
	}
\endcode

Класс obstring_t, напротив, производит запись данных в объект std::string.
Он полезен, если сериализованное представление объекта должно быть передано
куда-то или сохранено где-то в виде std::string. Особенностью obstring_t
является то, что он всегда дописывает данные в конец строки, расширяя при
этом строку. С одной стороны, это позволяет легко сконкатенировать
представление нескольких объектов в одной стороке:
\code
request_header_t header;
...
some_request_t request;
...
std::string request_image;
oess_1::io::obstring_t stream( request_image );
stream << header << request;
\endcode
Но с другой стороны, если в std::string помещается много данных, то
операция std::string::append может не эффективно увеличивать размер буфера
для хранения строки, что может сказаться на скорости записи данных. В этом
случае лучше заранее зарезервировать в строке достаточно места для
сохранения данных (методом std::string::reserve). Или воспользоваться
классом mem_buf_t.

Классы ifixed_mem_buf_t, ofixed_mem_buf_t предназначены для работы с буферами
фиксированного размера. Например, в каком-то протоколе обговаривается, что
стороны обмениваются датаграммами фиксированного размера, скажем 2Kb. Тогда,
чтобы сериализовать запрос в датаграмму, можно воспользоваться
ofixed_mem_buf_t:
\code
void
send_request( 
	const request_header_t & header, const some_request_t & request )
	{
		oess_1::char_t datagram[ datagram_size ];
		oess_1::io::ofixed_mem_buf_t stream( datagram, datagram_size );

		stream << header << request;

		send( datagram );
	}
\endcode
Соответственно, для десериализации запроса можно использовать
ifixed_mem_buf_t:
\code
void
receive_request(
	request_header_t & header, some_request_t & request )
	{
		oess_1::char_t datagram[ datagram_size ];
		receive( datagram );

		oess_1::io::ifixed_mem_buf_t stream( datagram, datagram_size );
		stream >> header >> request;
	}
\endcode

Класс isubbinstream_t полезен в случаях, когда нужно организовать
самостоятельный "подпоток" в рамках уже существующего входного потока. Например,
это часто бывает необходимо при разборе протоколов на основе TLV
(Tag, Length, Value). В этих случаях уже есть входной поток из которого
сначала считывается Tag, затем Length, а затем Value. Но при чтении Value
хорошо было бы ограничить возможность чтения всего Length байтами. В этом
случае создается объект isubbinstream_t, который не позволяет прочитать из
родительского входного потока более Length байт:
\code
void
read_pdu_one( oess_1::io::istream_t & stream )
	{
		stream >> ...
	}
void
read_pdu_two( oess_1::io::istream_t & stream )
	{
		stream >> ...
	}
...
void
parse_tlv_stream( const oess_1::char_t * datagram,
	oess_1::uint_t datagram_size )
	{
		oess_1::io::ifixed_mem_buf_t stream( datagram, datagram_size );

		oess_1::ushort_t tag;
		oess_1::uint_t length;

		stream >> tag >> length;
		oess_1::io::isubbinstream_t substream( stream, length );

		if( pdu_one == tag )
			read_pdu_one( substream );
		else if( pdu_two == tag )
			read_pdu_two( substream );
		...
	}
\endcode

Самым сложным и продвинутым классом двоичного ввода-вывода является класс
mem_buf_t. Он объединяет в себе функциональность входного и выходного
потоков. Работа ведется с буфером памяти, который контролирует mem_buf_t.
При необходимости этот буфер динамически расширяется. Важными особенностями
класса mem_buf_t являются:
- способность вручную изменять размер и емкость буфера (методы
mem_buf_t::set_size(), mem_buf_t::change_capacity());
- перемещать текущую позицию в буфере в любом направлении с помощью
метода mem_buf_t::set_pos(). Текущая позиция в mem_buf является аналогом
понятия текущей позиции в файле;
- вставлять данные в произвольное место буфера (методы mem_buf_t::insert());
- изымать данные из буфера (методы mem_buf_t::erase());
- брать фрагменты буфера не изымая их (методы mem_buf_t::subbuf_t()).

В качестве примера использования mem_buf_t можно представить ситуацию, когда
в некотором коммуникационном протоколе необходимо передавать сообщения
произвольной длины. При этом каждое сообщение начинается с двух обязательных
полей: общей длины данных (без учета этих двух полей) и контрольной суммы.
С помощью mem_buf_t реализовать это можно, например, так:
\code
void
serialize_and_send( const some_msg_t & msg )
	{
		// Размер двух обязательных полей.
		const oess_1::uint_t header_size =
				oess_1::io::bin_data_size_t< oess_1::uint_t >::image_size * 2;
		oess_1::io::mem_buf_t msg_image(
				// В начальную емкость буфера нужно включить два
				// обязательных поля.
				header_size );

		// Пропускаем заголовок, т.к. пока его значение не известно.
		msg_image.set_size( header_size );
		msg_image.set_pos( header_size );

		// Сериализуем сообщение.
		msg_image << msg;

		// Теперь можно сохранить длину сообщения и CRC.
		msg_image.set_pos( 0 );
		msg_image << oess_1::uint_t( msg_image.size() - header_size )
				<< calc_crc32( msg_image.ptr() + header_size,
							msg_image.size() - header_size );

		...
	}
\endcode

Класс bin_data_size_t позволяет получить размер образа основных типов
ObjESSty в байтах.

*/
namespace io
{

/*!
	\brief Коды ошибок компоненты %io.
*/
namespace err {}

} /* namespace io */

} /* namespace oess_1 */
