// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "stdmisc.h"

#include "nel/misc/bit_mem_stream.h"
#include "nel/misc/bit_set.h"

#ifdef LOG_ALL_TRAFFIC
#include "nel/misc/command.h"
#endif

using namespace std;

#ifdef DEBUG_NEW
	#define new DEBUG_NEW
#endif

namespace NLMISC {

#ifdef LOG_ALL_TRAFFIC
bool VerboseAllTraffic = false;
#endif


const char * SerialTypeToCStr [ TBMSSerialInfo::NbSerialTypes ] = { "Bool ", "Ui32N", "Ui64N", "Float", "Btfld", "Buffr" };


/*
 * Constructor
 */
CBitMemStream::CBitMemStream( bool inputStream, uint32 defaultcapacity ) :
	CMemStream( inputStream, false, defaultcapacity ),
	_FreeBits( 0 )
{
	resetBufPos();
}


/*
 * Copy constructor
 */
CBitMemStream::CBitMemStream( const CBitMemStream& other ) :
	CMemStream( other ),
	_FreeBits( other._FreeBits ),
	_DbgInfo( other._DbgInfo )
{
}

/*
 *	Exchange
 */
void CBitMemStream::swap(CBitMemStream &other)
{
	CMemStream::swap(other);
	std::swap(_FreeBits, other._FreeBits);
	_DbgInfo.swap(other._DbgInfo);
}


/*
 * Serialize a buffer
 */
void CBitMemStream::serialBuffer( uint8 *buf, uint len )
{
	_DbgInfo.addSerial( getPosInBit(), len*8, TBMSSerialInfo::Buffer );
	uint i;
	uint32 v;
	if ( isReading() )
	{
		for ( i=0; i!=len; ++i )
		{
			internalSerial( v, 8 );
			buf[i] = (uint8)v;
		}
	}
	else
	{
		for ( i=0; i!=len; ++i )
		{
			v = (uint32)buf[i];
			internalSerial( v, 8 );
		}
	}
}


/*
 * Serialize one bit
 */
void CBitMemStream::serialBit( bool& bit )
{
	_DbgInfo.addSerial( getPosInBit(), 1, TBMSSerialInfo::B );
	uint32 ubit=0;
	if ( isReading() )
	{
		internalSerial( ubit, 1 );
		bit = ( ubit!=0 );
	}
	else
	{
		ubit = bit;
		internalSerial( ubit, 1 );
	}
}


//sint32 CBitMemStream::getPosInBit ()


#ifdef LOG_ALL_TRAFFIC

void	CBitMemStream::_serialAndLog( const char *argstr, uint32& value, uint nbits )
{
	_DbgInfo.setSymbolOfNextSerialEvent( argstr );
	sint32 bitpos = getPosInBit();
	serial( value, nbits );
	if ( VerboseAllTraffic )
		nldebug( "TRAFFIC/%p/%s: %s: %u bits at bitpos %d (%u)", this, isReading()?"I":"O", argstr, nbits, bitpos, value );
}

void	CBitMemStream::_serialAndLog( const char *argstr, uint64& value, uint nbits )
{
	_DbgInfo.setSymbolOfNextSerialEvent( argstr );
	sint32 bitpos = getPosInBit();
	serial( value, nbits );
	if ( VerboseAllTraffic )
		nldebug( "TRAFFIC/%p/%s: %s: %u bits at bitpos %d (%u)", this, isReading()?"I":"O", argstr, nbits, bitpos, value );
}

void	CBitMemStream::_serialBitAndLog( const char *argstr, bool& bit )
{
	_DbgInfo.setSymbolOfNextSerialEvent( argstr );
	sint32 bitpos = getPosInBit();
	serialBit( bit );
	if ( VerboseAllTraffic )
		nldebug( "TRAFFIC/%p/%s: %s: 1 bit at bitpos %d (%hu)", this, isReading()?"I":"O", argstr, bitpos, (uint16)bit );
}

NLMISC_CATEGORISED_COMMAND(nel, verboseAllTraffic, "Verbose the all-traffic logs", "" )
{
	if(args.size()>1)
		return false;

	if(args.size()==1)
	{
		if(args[0]==string("on")||args[0]==string("ON")||args[0]==string("true")||args[0]==string("TRUE")||args[0]==string("1"))
			VerboseAllTraffic=true;
		else if(args[0]==string("off")||args[0]==string("OFF")||args[0]==string("false")||args[0]==string("FALSE")||args[0]==string("0"))
			VerboseAllTraffic=false;
	}

	nlinfo("BMS: verboseAllTraffic is %s",VerboseAllTraffic?"ON":"OFF");
	return true;
}

#endif


/*
 * Serialize only the nbits lower bits of value
 */
void	CBitMemStream::internalSerial( uint32& value, uint nbits, bool resetvalue )
{
	if ( nbits == 0 )
		return;
	if ( nbits > 32 )
		throw EMemStream (string("trying to serial ")+NLMISC::toString(nbits)+string(" bits"));

	if ( isReading() )
	{
		const uint8 *buffer = _Buffer.getBuffer().getPtr();
		// Check that we don't read more than there is to read
		uint32 pib = getPosInBit();
		uint32 len = ((uint32)lengthR());
		if ( pib + nbits > len * 8 )
		{
			//displayStream( "Stream Overflow" );
			throw EStreamOverflow( "CBitMemStream overflow: Read past %u bytes", len );
		}

		if ( resetvalue )
		{
			value = 0;
		}

		// Clear high-order bits after _FreeBits
		uint8 v = *(buffer + _Buffer.Pos) & ((1 << _FreeBits) - 1);

		if ( nbits > _FreeBits )
		{
			//nldebug( "Reading byte %u from %u free bits (%u remaining bits)", lengthS(), _FreeBits, nbits );
			value |= (v << (nbits-_FreeBits));
//			++_BufPos;
			++_Buffer.Pos;
			uint readbits = _FreeBits;
			//displayByteBits( *_BufPos, 8, readbits-1, false );
			_FreeBits = 8;
			internalSerial( value, nbits - readbits, false ); // read without resetting value
		}
		else
		{
			//nlinfo( "Reading last byte %u from %u free bits (%u remaining bits)", lengthS(), _FreeBits, nbits );
			//displayByteBits( *_BufPos, 8, _FreeBits-1, false );
			value |= (v >> (_FreeBits-nbits));
			//displayByteBits( *_BufPos, 8, _FreeBits-1, false );
			_FreeBits -= nbits;
			if ( _FreeBits == 0 )
			{
				_FreeBits = 8;
//				++_BufPos;
				++_Buffer.Pos;
			}
		}
	}
	else
	{
		uint8 *buffer = _Buffer.getBufferWrite().getPtr();
		// Clear high-order bits after nbits
		//displayDwordBits( value, 32, nbits-1, false );

		//uint32 mask = (-1 >> (32-nbits)); // does not work
		uint32 v;
		if ( nbits != 32 ) // arg of shl/sal/shr/sal ranges from 0 to 31
		{
			uint32 mask = (1 << nbits) - 1;
			v = value & mask;
		}
		else
		{
			v = value;
		}

#ifdef NL_DEBUG
		// Check that the current byte is prepared
		nlassert( ! ((_FreeBits == 8) && (*(buffer+_Buffer.Pos) != 0)) );
#endif

		// Set
		if ( nbits > _FreeBits )
		{
			// Longer than the room in the current byte
			//nldebug( "Writing byte %u into %u free bits (%u remaining bits)", lengthS(), _FreeBits, nbits );
			//displayDwordBits( value, 32, nbits-1, false );
//			*_BufPos |= (v >> (nbits - _FreeBits));
			*(buffer+_Buffer.Pos) |= (v >> (nbits - _FreeBits));
			uint filledbits = _FreeBits;
			//displayByteBits( *_BufPos, 8, filledbits-1, false );
			prepareNextByte();
			internalSerial( v, nbits - filledbits );
		}
		else
		{
			// Shorter or equal
			//nldebug( "Writing last byte %u into %u free bits (%u remaining bits)", lengthS(), _FreeBits, nbits );
			//displayByteBits( *_BufPos, 8, 7, false );
//			*_BufPos |= (v << (_FreeBits-nbits));
			*(buffer+_Buffer.Pos) |= (v << (_FreeBits-nbits));
			//displayByteBits( *_BufPos, 8, _FreeBits-1, false );
			_FreeBits -= nbits;
			if ( _FreeBits == 0 )
			{
				prepareNextByte();
			}
		}
	}
}


/*
 * Same as CMemStream::reserve()
 */
sint32	CBitMemStream::reserve( uint byteLen )
{
	sint32 p = getPos();
	reserveBits( byteLen * 8 );
	return p;
}


/*
 * In a output bit stream, serialize nbits bits (no matter their value).
 * Works even if the number of bits to add is larger than 64. See also poke() and pokeBits().
 */
void	CBitMemStream::reserveBits( uint nbits )
{
#ifdef LOG_ALL_TRAFFIC
	if ( VerboseAllTraffic )
		nldebug( "TRAFFIC/%p/%s: Reserving %u bits at bitpos %d", this, isReading()?"I":"O", nbits, getPosInBit() );
#endif

	uint32 v = 0;
	while ( nbits > 32 )
	{
		serial( v, 32 );
		nbits -= 32;
	}
	if ( nbits != 0 )
		serial( v, nbits );
}


/*
 * Helper for poke(), to write a value inside an output stream (works because reserveBits sets to 0)
 */
void	CBitMemStream::serialPoke( uint32 value, uint nbits )
{
	uint32 v;
	if ( nbits != 32 ) // arg of shl/sal/shr/sal ranges from 0 to 31
	{
		uint32 mask = (1 << nbits) - 1;
		v = value & mask;
	}
	else
	{
		v = value;
	}

	uint8 *buffer = _Buffer.getBufferWrite().getPtr();

	// Set
	if ( nbits > _FreeBits )
	{
		// Longer than the room in the current byte
		//nldebug( "Writing byte %u into %u free bits (%u remaining bits)", lengthS(), _FreeBits, nbits );
		//displayDwordBits( value, 32, nbits-1, false );
//		*_BufPos |= (v >> (nbits - _FreeBits));
		*(buffer + _Buffer.Pos) |= (v >> (nbits - _FreeBits));
		uint filledbits = _FreeBits;
		//displayByteBits( *_BufPos, 8, filledbits-1, false );
		pointNextByte(); // do not set next byte to 0!
//		nlassert( _BufPos < _Buffer.getPtr() + _Buffer.size() );
		nlassert( _Buffer.Pos < _Buffer.getBuffer().size() );
		serialPoke( v, nbits - filledbits );
	}
	else
	{
		// Shorter or equal
		//nldebug( "Writing last byte %u into %u free bits (%u remaining bits)", lengthS(), _FreeBits, nbits );
		//displayByteBits( *_BufPos, 8, 7, false );
//		*_BufPos |= (v << (_FreeBits-nbits));
		*(buffer + _Buffer.Pos) |= (v << (_FreeBits-nbits));
		//displayByteBits( *_BufPos, 8, _FreeBits-1, false );
		_FreeBits -= nbits;
		if ( _FreeBits == 0 )
		{
			pointNextByte(); // do not set next byte to 0!
//			nlassert( _BufPos < _Buffer.getPtr() + _Buffer.size() );
			nlassert( _Buffer.Pos < _Buffer.getBuffer().size() );
		}
	}
}


/*
 * Rewrite the nbbits lowest bits of a value at the specified position bitpos of the current output bit stream.
 *
 * Preconditions:
 * - bitpos+nbbits <= the current length in bit of the stream.
 * - The bits poked must have been reserved by reserve() (i.e. set to 0)
 */
void	CBitMemStream::poke( uint32 value, uint bitpos, uint nbits )
{
#ifdef NL_DEBUG
	nlassert( (nbits <= 32) && (nbits != 0) );
	nlassert( ! isReading() );
	nlassert( bitpos+nbits <= (uint)getPosInBit() );
#endif

	// Save the current pointers of the stream, and make them point to the required position
	uint savedFreeBits = _FreeBits;
	uint bytepos = bitpos >> 3;
	_FreeBits = 8 - (bitpos - (bytepos << 3));
//	uint8 *savedBufPos = _BufPos;
	uint32 savedBufPos = _Buffer.Pos;
//	_BufPos = _Buffer.getPtr() + bytepos;
	_Buffer.Pos = bytepos;

	// Serial
	_DbgInfo.addPoke( bitpos, nbits, TBMSSerialInfo::U );
	serialPoke( value, nbits );

	// Restore the current pointers
	_FreeBits = savedFreeBits;
//	_BufPos = savedBufPos;
	_Buffer.Pos = savedBufPos;
}


/* Rewrite the bitfield at the specified position bitpos of the current output bit stream.
 * The size of the bitfield is *not* written into stream (unlike serialCont()).
 * Precondition: bitpos+bitfield.size() <= the current length in bit of the stream. See also reserveBits().
 */
void	CBitMemStream::pokeBits( const CBitSet& bitfield, uint bitpos )
{
#ifdef NL_DEBUG
	nlassert( ! isReading() );
	nlassert( bitpos+bitfield.size() <= (uint)getPosInBit() );
#endif

	// Save the current pointers of the stream, and make them point to the required position
	uint savedFreeBits = _FreeBits;
	uint bytepos = bitpos >> 3;
	_FreeBits = 8 - (bitpos - (bytepos << 3));
//	uint8 *savedBufPos = _BufPos;
	uint32 savedBufPos = _Buffer.Pos;
//	_BufPos = _Buffer.getPtr() + bytepos;
	_Buffer.Pos = bytepos;

	// Serial
	_DbgInfo.addPoke( bitpos, bitfield.size(), TBMSSerialInfo::BF );
	const vector<uint32>& uintVec = bitfield.getVector();
	if ( ! uintVec.empty() )
	{
		uint len = bitfield.size();
		uint i = 0;
		while ( len > 32 )
		{
			serialPoke( uintVec[i], 32 );
			len -= 32;
			++i;
		}
		if ( len != 0 )
			serialPoke( uintVec[i], len );
	}

	// Restore the current pointers
	_FreeBits = savedFreeBits;
//	_BufPos = savedBufPos;
	_Buffer.Pos = savedBufPos;
}


/*
 * Read bitfield.size() bits from the input stream to fill the bitfield.
 * It means you have to know the size and to resize the bitfield yourself.
 */
void	CBitMemStream::readBits( NLMISC::CBitSet& bitfield )
{
#ifdef NL_DEBUG
	nlassert( isReading() );
#endif
	uint len = bitfield.size();
	if ( len != 0 )
	{
#ifdef LOG_ALL_TRAFFIC
		if ( VerboseAllTraffic )
			nldebug( "TRAFFIC/%p/%s: Reading %u bits bitfield at bitpos %d", this, isReading()?"I":"O", len, getPosInBit() );
#endif
		uint i = 0;
		uint32 v;
		while ( len > 32 )
		{
			serial( v, 32 );
			//nldebug( "Bitfield: Read %u at %d", v, _BufPos-_Buffer.getPtr()-4 );
			bitfield.setUint( v, i );
			len -= 32;
			++i;
		}
		serial( v, len );
		//nldebug( "Bitfield: Read %u at %d", v, _BufPos-_Buffer.getPtr()-4 );
		bitfield.setUint( v, i );
	}
}


/*
 * Serial float
 */
void	CBitMemStream::serial(float &b)
{
	_DbgInfo.addSerial( getPosInBit(), sizeof(b)*8, TBMSSerialInfo::F );
	uint32 uf=0;
	if ( isReading() )
	{
		internalSerial( uf, sizeof(b)*8 );
		memcpy(&b, &uf, sizeof(b));
	}
	else
	{
		memcpy(&uf, &b, sizeof(b));
		internalSerial( uf, sizeof(b)*8 );
	}
}


/*
 * Serial string
 */
void	CBitMemStream::serial(std::string &b)
{
#ifdef LOG_ALL_TRAFFIC
	sint32 bitpos = getPosInBit();
#endif

	uint32 len=0;

	// Serialize length
	if ( isReading() )
	{
		serial( len );
		if (len > length()-(uint32)getPos())
			throw NLMISC::EInvalidDataStream( "BMS: Trying to read a string of %u bytes, past stream size", len );
		b.resize( len );
	}
	else
	{
		len = (uint32)b.size();
		if (len>1000000)
			throw NLMISC::EInvalidDataStream( "BMS: Trying to write a string of %u bytes", len );
		serial( len );
	}

	// Serialize buffer
	if ( len != 0 )
	{
		serialBuffer( (uint8*)(&*b.begin()), len );
	}

#ifdef LOG_ALL_TRAFFIC
	if ( VerboseAllTraffic )
		nldebug( "TRAFFIC/%p/%s: String (size 32+%u*8 bits) at bitpos %d", this, isReading()?"I":"O", len, bitpos );
#endif
}


/*
 * Serial string
 */
inline	void		CBitMemStream::serial(ucstring &b)
{
#ifdef LOG_ALL_TRAFFIC
	sint32 bitpos = getPosInBit();
#endif

	if ( _StringMode )
	{
		uint32	len=0;
		// Read/Write the length.
		if(isReading())
		{
			serial(len);
			if (len > (uint32)(sint32(length())-sint32(getPos())))
				throw NLMISC::EInvalidDataStream( "BMS: Trying to read an ucstring of %u bytes, past stream size", len );
			b.resize(len);
		}
		else
		{
			len= (uint32)b.size();
			if (len>1000000)
				throw NLMISC::EInvalidDataStream( "BMS: Trying to write an ucstring of %u bytes", len );
			serial(len);
		}
		// Read/Write the string.
		for(uint i=0;i!=len;++i)
			serialBuffer( (uint8*)&b[i], sizeof(b[i]) );

		char sep = SEPARATOR;
		serialBuffer( (uint8*)&sep, 1 );
	}
	else
	{
		IStream::serial( b );
	}

#ifdef LOG_ALL_TRAFFIC
	if ( VerboseAllTraffic )
		nldebug( "TRAFFIC/%p/%s: Ucstring at bitpos %d", this, isReading()?"I":"O", bitpos );
#endif

}


/*
 * Append the contents of a bitmemstream at the end of our bitmemstream
 */
void	CBitMemStream::append( const CBitMemStream& newBits )
{
	nlassert ( !isReading() );
	serialBuffer( const_cast<uint8*>(newBits.buffer()), newBits.getPos() );

	uint nbRemainingBits = 8 - newBits._FreeBits;
	_DbgInfo.addSerial( getPosInBit(), nbRemainingBits, TBMSSerialInfo::Buffer );
	uint32 lastByte = (uint32)(*(newBits.buffer() + newBits.getPos())) >> newBits._FreeBits;
	internalSerial( lastByte, nbRemainingBits );
}


/*
 * Serial bitmemstream
 */
void	CBitMemStream::serialMemStream(CMemStream &b)
{
#ifdef LOG_ALL_TRAFFIC
	sint32 bitpos = getPosInBit();
#endif

	uint32 len=0;

	// Serialize length
	if ( isReading() )
	{
		// fill b with data from this
		serial (len);
		if (len > length()-getPos())
			throw NLMISC::EInvalidDataStream( "BMS: Trying to read a BMS of %u bytes, past stream size", len );

		serialBuffer (b.bufferToFill (len), len);
		b.resetBufPos ();
	}
	else
	{
		// fill this with data from b
		len = b.length();
		// Accept to write a big sized BMS

		serial( len );
		serialBuffer( (uint8*) b.buffer (), len );
	}

#ifdef LOG_ALL_TRAFFIC
	if ( VerboseAllTraffic )
		nldebug( "TRAFFIC/%p/%s: Sub-bitmemstream (size 32+%u*8 bits) at bitpos %d", this, isReading()?"I":"O", len, bitpos );
#endif
}

/*
 * Specialisation of serialCont() for vector<bool>
 */
void CBitMemStream::serialCont(std::vector<bool> &cont)
{
#ifdef LOG_ALL_TRAFFIC
	sint32 bitpos = getPosInBit();
#endif

	sint32	len=0;
	if(isReading())
	{
		serial(len);
		if (len/8 > (sint32)(length()-getPos()))
		{
			throw NLMISC::EInvalidDataStream( "BMS: Trying to read a vec<bool> of %u bytes, past stream size", len/8 );
		}
		// special version for vector: adjust good size.
		contReset(cont);
		cont.reserve(len);

		for(sint i=0;i<len;i++)
		{
			bool	v;
			serialBit(v);
			cont.insert(cont.end(), v);
		}
	}
	else
	{
		len= (sint32)cont.size();
		serial(len);

		std::vector<bool>::iterator it= cont.begin();
		for(sint i=0;i<len;i++, it++)
		{
			bool b = *it;
			serialBit( b );
		}
	}

#ifdef LOG_ALL_TRAFFIC
	if ( VerboseAllTraffic )
		nldebug( "TRAFFIC/%p/%s: Container (header: 32 bits) at bitpos %d", this, isReading()?"I":"O", bitpos );
#endif

}


/*
 * Display the bits of the stream just before the current pos
 */
void	CBitMemStream::displayLastBits( sint nbits, sint bitpos, NLMISC::CLog *log )
{
	if ( bitpos == -1 )
		bitpos = getPosInBit();
	displayBitStream( *this, max(bitpos-nbits, 0), bitpos-1, log );
}


/*
 * Display a part of a bitmemstream
 */
void	displayBitStream( const CBitMemStream& msg, sint beginbitpos, sint endbitpos, NLMISC::CLog *log )
{
	sint beginpos = beginbitpos/8;
	sint endpos = endbitpos/8;
	nlinfo( "BMS: beginpos %d endpos %d beginbitpos %d endbitpos %d", beginpos, endpos, beginbitpos, endbitpos );
	displayByteBits( *(msg.buffer()+beginpos), 8, 8-(beginbitpos-beginpos*8), true, log );
	const uint8 *p;
	for ( p=msg.buffer()+beginpos+1; p<msg.buffer()+endpos-1; ++p )
	{
		displayByteBits( *p, 8, 0, false, log );
	}
	if ( endpos > beginpos )
	{
		displayByteBits( *(msg.buffer()+endpos), 8, 0, false, log );
	}
}


/*
 * Returns the stream as a string with 0 and 1.
 */
void		CBitMemStream::displayStream( const char *title, CLog *log )
{
//	nlassert( (_BufPos >= _Buffer.getPtr()) && (_BufPos <= _Buffer.getPtr() + _Buffer.size()) );
	nlassert( _Buffer.Pos <= _Buffer.getBuffer().size() );

	// Display title and information
	string s = (isReading()?string("I"):string("O")) + string("BMS ") + string(title) + ": ";
	string sLegend;
//	if ( _BufPos == _Buffer.getPtr() )
	if ( _Buffer.Pos == 0 )
	{
		log->displayNL( (s + "Empty").c_str() );
		return;
	}

//	s += NLMISC::toString( "BitPos=%d Pos=%u FreeBits=%u Size=%u ", getPosInBit(), (uint32)(_BufPos-_Buffer.getPtr()), _FreeBits, _Buffer.size() );
	s += NLMISC::toString( "BitPos=%d Pos=%u FreeBits=%u Size=%u ", getPosInBit(), _Buffer.Pos, _FreeBits, _Buffer.getBuffer().size() );
	log->displayNL( s.c_str() );
	s.clear();

	// Display bitstream (output: until _BufPos/_FreeBits; input: whole buffer)
	_DbgInfo.beginEventBrowsing();
	sint32 eventId;
	uint32 bitpos = 0;
	const uint8 *p;
//	uint8 *endPos = isReading() ? (_Buffer.getPtr() + _Buffer.size()) : (_BufPos+1);
	const uint8 *endPos = isReading() ? (_Buffer.getBuffer().getPtr() + _Buffer.getBuffer().size()) : (_Buffer.getBuffer().getPtr() + _Buffer.Pos+1);
//	for ( p=_Buffer.getPtr(); p!=endPos; ++p )
	for ( p=_Buffer.getBuffer().getPtr(); p!=endPos; ++p )
	{
		sint i;
		for ( i=7; i!=-1; --i )
		{
			//bitpos = (p-_Buffer.getPtr())*8 + (7-i);
			if ( bitpos == (uint32)getPosInBit() )
				s += "<P>"; // display the current position
			s += _DbgInfo.getEventIdAtBitPos( bitpos, &eventId );
			s += ( ((*p) >> i) & 1 ) ? '1' : '0';
			sLegend += _DbgInfo.getEventLegendAtBitPos( *this, eventId );
			++bitpos;
		}

		s += ' '; // a blank char between each byte
		if ( bitpos % 64 == 0 ) // limit to 8 bytes per line
		{
			log->displayRawNL( s.c_str() );
			s.clear();
		}
	}
	if ( bitpos % 64 != 0 )
		log->displayRawNL( s.c_str() );
	_DbgInfo.endEventBrowsing();

	// Display legend
	string::size_type lineStart = 0;
	string::size_type crp = sLegend.find( '\n', lineStart );
	while ( crp != string::npos )
	{
		log->displayRawNL( sLegend.substr( lineStart, crp-lineStart ).c_str() );
		lineStart = crp + 1;
		crp = sLegend.find( '\n', lineStart );
	}
	// sLegend ends with a '\n'
}


/*
 * Return a string showing the serial item
 */
std::string		CBitMemStream::getSerialItem( const TBMSSerialInfo& serialItem )
{
	// Save the current pointers of the stream, and make them point to the required position
	uint savedFreeBits = _FreeBits;
	uint bytepos = serialItem.BitPos >> 3;
	_FreeBits = 8 - (serialItem.BitPos - (bytepos << 3));
//	uint8 *savedBufPos = _BufPos;
	uint32 savedBufPos = _Buffer.Pos;
//	_BufPos = _Buffer.getPtr() + bytepos;
	_Buffer.Pos = bytepos;

	bool wasOutput = false;;
	if ( ! isReading() )
	{
		setInOut( true ); // lighter than invert()
		wasOutput = true;
	}

	// Read and format string
	string s;
	if ( getPosInBit() + serialItem.BitSize > lengthR() * 8 )
	{
		s = "<Stream Overflow>";
	}
	else
	{
		switch ( serialItem.Type )
		{
		case TBMSSerialInfo::B:
			{
				bool b;
				serialBit( b );
				s = NLMISC::toString( "%s", b?"TRUE":"FALSE" );
				break;
			}
		case TBMSSerialInfo::U: // no distinction with signed int!
			{
				uint32 u;
				serial( u, serialItem.BitSize );
				s = NLMISC::toString( "%u", u );
				break;
			}
		case TBMSSerialInfo::U64: // no distinction with signed int64!
			{
				uint64 u;
				serial( u, serialItem.BitSize );
				s = NLMISC::toString( "%"NL_I64"u", u );
				break;
			}
		case TBMSSerialInfo::F: // what about double?
			{
				float f;
				serial( f );
				s = NLMISC::toString( "%g", f );
				break;
			}
		case TBMSSerialInfo::BF:
			{
				CBitSet bs;
				bs.resize( serialItem.BitSize );
				readBits( bs );
				s = bs.toString();
				break;
			}
		case TBMSSerialInfo::Buffer:
			{
				uint32 len = serialItem.BitSize / 8;
				s.resize( len + 2 );
				if ( len != 0 )
				{
					serialBuffer( &((uint8&)(s[1])), len );
					string::size_type p;
					for ( p=1; p!=len+1; ++p )
					{
						if ( ! isalnum(s[p]) )
							s[p] = '?'; // remove end-of-c_string
					}
				}
				s[0] = '[';
				s[len+1] = ']';
				break;
			}
		default:
		  break;
		}
	}

	// Restore the current pointers
	if ( wasOutput )
	{
		setInOut( false );
	}

	_FreeBits = savedFreeBits;
//	_BufPos = savedBufPos;
	_Buffer.Pos = savedBufPos;

	return s;
}


} // NLMISC