/*

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
	\brief Реализация базовых классов для двоичных потоков.
*/

#include <auto_ptr_3/h/vect_ptr.hpp>

#include <oess_1/defs/log/h/out.hpp>

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

namespace oess_1 {

namespace io {

//
// big_endian_decoder_t
//

/*!
	\brief Класс, который занимается расшифровкой BigEndian
	представления.

	Конструктор этого класса сам определяет на какой платформе
	осуществляется работа и должным образом выставляет указатели
	m_short_decoder, m_int_decoder, m_single_decoder,
	m_double_decoder.
*/
class	big_endian_decoder_t {
	public :
		//! Конструктор автоматически определяет,
		//! какой способ представления чисел используется
		//! на данной платформе.
		big_endian_decoder_t();

	/*! \name Методы для выполнения преобразований */
	/*!\{*/
		//! Ничего не делает.
		static void
		decode( oess_1::char_t * p, size_t count );

		//! Ничего не делает.
		static void
		decode( oess_1::schar_t * p, size_t count );

		//! Ничего не делает.
		static void
		decode( oess_1::uchar_t * p, size_t count );

		/*! Вызывает функцию по указателю m_short_decoder. */
		static void
		decode( oess_1::short_t * p, size_t count );

		/*! Вызывает функцию по указателю m_short_decoder. */
		static void
		decode( oess_1::ushort_t * p, size_t count );

		/*! Вызывает функцию по указателю m_int_decoder. */
		static void
		decode( oess_1::int_t * p, size_t count );

		/*! Вызывает функцию по указателю m_int_decoder. */
		static void
		decode( oess_1::uint_t * p, size_t count );

		/*! Вызывает функцию по указателю m_single_decoder. */
		static void
		decode( oess_1::single_t * p, size_t count );

		/*! Вызывает функцию по указателю m_double_decoder. */
		static void
		decode( oess_1::double_t * p, size_t count );
	/*!\}*/

	private :
	/*!
		\name Указатели на реальные преобразователи значений

		Все public-функции обращаются к функциям,
		указатели на которые содержатся в этих
		переменных.
	*/
	/*!\{*/
		typedef void (*pfn_short_decoder_t)(
			oess_1::short_t * p, size_t count );
		//! Указатель на функцию преобразования short.
		static pfn_short_decoder_t	m_short_decoder;

		typedef void (*pfn_int_decoder_t)(
			oess_1::int_t * p, size_t count );
		//! Указатель на функцию преобразования int.
		static pfn_int_decoder_t	m_int_decoder;

		typedef void (*pfn_single_decoder_t)(
			oess_1::single_t * p, size_t count );
		//! Указатель на функцию преобразования float.
		static pfn_single_decoder_t	m_single_decoder;

		typedef void (*pfn_double_decoder_t)(
			oess_1::double_t * p, size_t count );
		//! Указатель на функцию преобразования double.
		static pfn_double_decoder_t	m_double_decoder;
	/*!\}*/

	/*! \name Преобразователи для big-endian платформ

		Все эти функции ничего не делают.
		Нужны для того, чтобы можно было взять указатели на
		функции выполнения преобразований на платформах,
		которые используют big-endian представление.
	*/
	/*!\{*/
		static void
		big_endian( oess_1::short_t * p, size_t count );

		static void
		big_endian( oess_1::int_t * p, size_t count );

		static void
		big_endian( oess_1::single_t * p, size_t count );

		static void
		big_endian( oess_1::double_t * p, size_t count );
	/*!\}*/

	/*! \name Преобразователи для little-endian платформ

		Эти функции выполняют реальное преобразование данных.
	*/
	/*!\{*/
		static void
		little_endian( oess_1::short_t * p, size_t count );

		static void
		little_endian( oess_1::int_t * p, size_t count );

		static void
		little_endian( oess_1::single_t * p, size_t count );

		static void
		little_endian( oess_1::double_t * p, size_t count );
	/*!\}*/
};

big_endian_decoder_t::pfn_short_decoder_t
big_endian_decoder_t::m_short_decoder = &big_endian_decoder_t::little_endian;

big_endian_decoder_t::pfn_int_decoder_t
big_endian_decoder_t::m_int_decoder = &big_endian_decoder_t::little_endian;

big_endian_decoder_t::pfn_single_decoder_t
big_endian_decoder_t::m_single_decoder = &big_endian_decoder_t::little_endian;

big_endian_decoder_t::pfn_double_decoder_t
big_endian_decoder_t::m_double_decoder = &big_endian_decoder_t::little_endian;

big_endian_decoder_t::big_endian_decoder_t() {
	oess_1::ushort_t us = 0x1234;
	oess_1::uchar_t * hi = reinterpret_cast< oess_1::uchar_t * >( &us );
	if( 0x12 == *hi ) {
		// Это платформа BinEndian.
		m_short_decoder = &big_endian_decoder_t::big_endian;
		m_int_decoder = &big_endian_decoder_t::big_endian;
		m_single_decoder = &big_endian_decoder_t::big_endian;
		m_double_decoder = &big_endian_decoder_t::big_endian;
	}
}

void
big_endian_decoder_t::decode( oess_1::char_t * p, size_t count )
{
	// Однобайтовые значения не преобразуются.
}

void
big_endian_decoder_t::decode( oess_1::schar_t * p, size_t count )
{
	// Однобайтовые значения не преобразуются.
}

void
big_endian_decoder_t::decode( oess_1::uchar_t * p, size_t count )
{
	// Однобайтовые значения не преобразуются.
}

void
big_endian_decoder_t::decode( oess_1::short_t * p, size_t count )
{
	(*m_short_decoder)( p, count );
}

void
big_endian_decoder_t::decode( oess_1::ushort_t * p, size_t count )
{
	(*m_short_decoder)(
		reinterpret_cast< oess_1::short_t * >( p ), count );
}

void
big_endian_decoder_t::decode( oess_1::int_t * p, size_t count )
{
	(*m_int_decoder)( p, count );
}

void
big_endian_decoder_t::decode( oess_1::uint_t * p, size_t count )
{
	(*m_int_decoder)(
		reinterpret_cast< oess_1::int_t * >( p ), count );
}

void
big_endian_decoder_t::decode( oess_1::single_t * p, size_t count )
{
	(*m_single_decoder)( p, count );
}

void
big_endian_decoder_t::decode( oess_1::double_t * p, size_t count )
{
	(*m_double_decoder)( p, count );
}

void
big_endian_decoder_t::big_endian(
	oess_1::short_t * p, size_t count )
{
	// Ничего не делаем.
}

void
big_endian_decoder_t::big_endian(
	oess_1::int_t * p, size_t count )
{
	// Ничего не делаем.
}

void
big_endian_decoder_t::big_endian(
	oess_1::single_t * p, size_t count )
{
	// Ничего не делаем.
}

void
big_endian_decoder_t::big_endian(
	oess_1::double_t * p, size_t count )
{
	// Ничего не делаем.
}

void
big_endian_decoder_t::little_endian(
	oess_1::short_t * p, size_t count )
{
	for( ; count; --count, ++p ) {
		register char * pch = (char *) p;
		register char ch = pch[ 0 ]; pch[ 0 ] = pch[ 1 ]; pch[ 1 ] = ch;
	}
}

void
big_endian_decoder_t::little_endian(
	oess_1::int_t * p, size_t count )
{
	for( ; count; --count, ++p ) {
		oess_1::short_t * pus = (oess_1::short_t *) p;
		oess_1::short_t us = pus[ 0 ]; pus[ 0 ] = pus[ 1 ]; pus[ 1 ] = us;
		little_endian( pus, 2 );
	}
}

void
big_endian_decoder_t::little_endian(
	oess_1::single_t * p, size_t count )
{
	little_endian( (oess_1::int_t *)p, count );
}

void
big_endian_decoder_t::little_endian(
	oess_1::double_t * p, size_t count )
{
	for( ; count; --count, ++p ) {
		oess_1::int_t * pui = (oess_1::int_t *) p;
		oess_1::int_t ui = pui[ 0 ]; pui[ 0 ] = pui[ 1 ]; pui[ 1 ] = ui;
		little_endian( pui, 2 );
	}
}

//
// Объект, который автоматически определит, как осуществлять
// декодирование на конкретной платформе.
//
static	big_endian_decoder_t	g_big_endian_decoder;

//
// ibuffer_accessor_t
//

/*!
	\brief Вспомогательный класс

	Предназначен для предоставления доступа к защищенным методам
	in_reserve и in_shift.
*/
class	ibuffer_accessor_t {
	private :
		/*!
			Входной поток, доступ к методам in_reserve, in_shift
			будет предоставлять данный экземпляр ibuffer_accessor_t.
		*/
		ibinstream_t &	m_s;

	public :
		inline
		ibuffer_accessor_t( ibinstream_t & s ) : m_s( s ) {
		}

		//! Предоставление доступа к методу in_reserve.
		inline const oess_1::char_t *
		in_reserve(
			size_t item_count,
			size_t item_size,
			size_t & item_available ) {
			return m_s.in_reserve( item_count, item_size,
				item_available );
		}

		//! Предоставление доступа к методу in_shift.
		inline void
		in_shift(
			size_t item_count,
			size_t item_size ) {
			m_s.in_shift( item_count, item_size );
		}
};

//
// read_helper
//

template< class Type >
void	read_helper(
	ibuffer_accessor_t & s, Type * p, size_t count )
{
	while( count ) {
		size_t item_available = 0;

		const oess_1::char_t * from = s.in_reserve(
			count, sizeof( Type ), item_available );
		if( item_available > count )
			item_available = count;

		memcpy( p, from, item_available * sizeof( Type ) );

		big_endian_decoder_t::decode( p, item_available );

		p += item_available * sizeof( Type );

		s.in_shift( item_available, sizeof( Type ) );
		count -= item_available;
	}
}

//
// ibinstream_t
//

ibinstream_t::~ibinstream_t() {
}

void
ibinstream_t::read( oess_1::char_t * p, size_t count )
{
	ibuffer_accessor_t buf_accessor( *this );
	read_helper( buf_accessor, p, count );
}

void
ibinstream_t::read( oess_1::schar_t * p, size_t count )
{
	ibuffer_accessor_t buf_accessor( *this );
	read_helper( buf_accessor, p, count );
}

void
ibinstream_t::read( oess_1::uchar_t * p, size_t count )
{
	ibuffer_accessor_t buf_accessor( *this );
	read_helper( buf_accessor, p, count );
}

void
ibinstream_t::read( oess_1::short_t * p, size_t count )
{
	ibuffer_accessor_t buf_accessor( *this );
	read_helper( buf_accessor, p, count );
}

void
ibinstream_t::read( oess_1::ushort_t * p, size_t count )
{
	ibuffer_accessor_t buf_accessor( *this );
	read_helper( buf_accessor, p, count );
}

void
ibinstream_t::read( oess_1::int_t * p, size_t count )
{
	ibuffer_accessor_t buf_accessor( *this );
	read_helper( buf_accessor, p, count );
}

void
ibinstream_t::read( oess_1::uint_t * p, size_t count )
{
	ibuffer_accessor_t buf_accessor( *this );
	read_helper( buf_accessor, p, count );
}

void
ibinstream_t::read( oess_1::single_t * p, size_t count )
{
	ibuffer_accessor_t buf_accessor( *this );
	read_helper( buf_accessor, p, count );
}

void
ibinstream_t::read( oess_1::double_t * p, size_t count )
{
	ibuffer_accessor_t buf_accessor( *this );
	read_helper( buf_accessor, p, count );
}

void
ibinstream_t::read( std::string & p )
{
	// Значению строки предшествует длина.
	oess_1::uint_t len;
	read( &len, 1 );

	if( len ) {
		// Нужно считывать строку.
//FIX ME: может быть, это не самая оптимальная реализация.
		auto_ptr_3::vect_ptr_t< oess_1::char_t > buf(
			new oess_1::char_t[ len ], len );
		read( buf.query(), len );

		p.assign( buf.query(), len );
	}
	else {
		// Строка пустая.
		p = "";
	}
}

//
// obuffer_accessor_t
//

/*!
	\brief Вспомогательный класс.

	Предназначен для предоставления доступа к защищенным методам
	out_reserve и out_shift.
*/
class	obuffer_accessor_t {
	private :
		/*!
			Выходной поток, к методам out_reserve и out_shift будет
			предоставлять данный экземпляр класса obuffer_accessor_t.
		*/
		obinstream_t &	m_s;

	public :
		inline
		obuffer_accessor_t( obinstream_t & s ) : m_s( s ) {
		}

		//! Предоставление доступа к методу out_reserve.
		inline oess_1::char_t *
		out_reserve(
			size_t item_count,
			size_t item_size,
			size_t & item_available ) {
			return m_s.out_reserve( item_count, item_size,
				item_available );
		}

		//! Предоставление доступа к методу out_shift.
		inline void
		out_shift(
			size_t item_count,
			size_t item_size ) {
			m_s.out_shift( item_count, item_size );
		}
};

//
// write_helper
//

//FIX ME: нужно проверить, как сдесь обстоят дела с
//выравниванием данных. Это может сказаться на таких
//платформах, как SPARC.
template< class Type >
void	write_helper(
	obuffer_accessor_t & s, const Type * p, size_t count )
{
	while( count ) {
		size_t item_available = 0;

		oess_1::char_t * to = s.out_reserve(
			count, sizeof( Type ), item_available );
		if( item_available > count )
			item_available = count;

		memcpy( to, p, item_available * sizeof( Type ) );

		// Для перекодирования в BigEndian используется
		// тот факт, что кодирование и декодирование являются
		// симетричными операциями. Поэтому можно
		// использовать уже существующий код.
		big_endian_decoder_t::decode(
			reinterpret_cast< Type * >( to ), item_available );

		p += item_available * sizeof( Type );

		s.out_shift( item_available, sizeof( Type ) );
		count -= item_available;
	}
}

//
// obinstream_t
//

obinstream_t::~obinstream_t()
{
}

void
obinstream_t::write( const oess_1::char_t * p, size_t count )
{
	obuffer_accessor_t buf_accessor( *this );
	write_helper( buf_accessor, p, count );
}

void
obinstream_t::write( const oess_1::schar_t * p, size_t count )
{
	obuffer_accessor_t buf_accessor( *this );
	write_helper( buf_accessor, p, count );
}

void
obinstream_t::write( const oess_1::uchar_t * p, size_t count )
{
	obuffer_accessor_t buf_accessor( *this );
	write_helper( buf_accessor, p, count );
}

void
obinstream_t::write( const oess_1::short_t * p, size_t count )
{
	obuffer_accessor_t buf_accessor( *this );
	write_helper( buf_accessor, p, count );
}

void
obinstream_t::write( const oess_1::ushort_t * p, size_t count )
{
	obuffer_accessor_t buf_accessor( *this );
	write_helper( buf_accessor, p, count );
}

void
obinstream_t::write( const oess_1::int_t * p, size_t count )
{
	obuffer_accessor_t buf_accessor( *this );
	write_helper( buf_accessor, p, count );
}

void
obinstream_t::write( const oess_1::uint_t * p, size_t count )
{
	obuffer_accessor_t buf_accessor( *this );
	write_helper( buf_accessor, p, count );
}

void
obinstream_t::write( const oess_1::single_t * p, size_t count )
{
	obuffer_accessor_t buf_accessor( *this );
	write_helper( buf_accessor, p, count );
}

void
obinstream_t::write( const oess_1::double_t * p, size_t count )
{
	obuffer_accessor_t buf_accessor( *this );
	write_helper( buf_accessor, p, count );
}

void
obinstream_t::write( const std::string & p )
{
	// Значению строки предшествует длина.
	oess_1::uint_t len = p.length();
	write( &len, 1 );

	if( len ) {
		// Нужно записывать строку.
		write( p.data(), len );
	}
}

} /* namespace io */

} /* namespace oess_1 */
