/** \file
 *
 *  Contains the OutStream class implementation.
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 *
 */

#include "OutStream.h"

#include <cstdio>
#include "utils/Str.h"

const unsigned char OutStream::HEX_CHARS[] = "0123456789ABCDEF";

/// writes num bytes from buffer to the output buffer/stream as hex chars
OutStream& OutStream::writeHex( const char* buffer, size_t num )
{
    for( size_t i = 0; i < num; ++i )
    {
        writeCharHex( ( signed char )buffer[i] );
    }
    return *this;
}

/// writes a single unsigned char to the output buffer/stream as 2 hex chars
OutStream& OutStream::writeCharHex( const signed char value )
{
    writeChar( HEX_CHARS[( value & 0x00F0 ) >> 4] );
    writeChar( HEX_CHARS[ value & 0x000F ] );
    lastWritten_ = 2;
    return *this;
}

/// writes a single unsigned char to the output buffer/stream as 2 hex chars
OutStream& OutStream::writeUCharHex( const unsigned char value )
{
    writeChar( HEX_CHARS[( value & 0x00F0 ) >> 4] );
    writeChar( HEX_CHARS[ value & 0x000F ] );
    lastWritten_ = 2;
    return *this;
}

/// writes an integer as a base-radix string
OutStream& OutStream::writeInteger( const int number, const unsigned int radix )
{
    char str[ 33 ];
    unsigned int length = Str::IntToAscii( number, str, radix, 33 );
    write( str, length );
    return *this;
}

OutStream& OutStream::writeNumber( const double number, const unsigned int radix )
{
    writeInteger( ( int )( number + 0.5 ) );
    return *this;
}
/// writes a single short to the output buffer/stream as 4 hex chars
OutStream& OutStream::writeShortHex( const short value )
{
    writeChar( HEX_CHARS[( value & 0xF000 ) >> 12] );
    writeChar( HEX_CHARS[( value & 0x0F00 ) >> 8] );
    writeChar( HEX_CHARS[( value & 0x00F0 ) >> 4] );
    writeChar( HEX_CHARS[ value & 0x000F ] );
    lastWritten_ = 4;
    return *this;
}

/// writes a single short to the output buffer/stream as 4 hex chars
OutStream& OutStream::writeUShortHex( const unsigned short value )
{
    writeChar( HEX_CHARS[( value & 0xF000 ) >> 12] );
    writeChar( HEX_CHARS[( value & 0x0F00 ) >> 8] );
    writeChar( HEX_CHARS[( value & 0x00F0 ) >> 4] );
    writeChar( HEX_CHARS[ value & 0x000F ] );
    lastWritten_ = 4;
    return *this;
}

/// writes an unsigned short integer as 1 to 3 bytes,
/// encoded as follows:
/// xxxxxxx0 - 7 bit value, encoded in 1 byte
/// xxxxxx01+1byte - 14 bit value, encoded in 2 bytes
/// xxxxx011+2bytes - 16 bit value, encoded in 3 bytes
OutStream& OutStream::writeUShortCompact( const unsigned short& value )
{
    //if(1==1) return writeShort((short) value);
    // Start with 1 byte / 7bit test
    unsigned short mask( 0xFF80 );
    unsigned short masked( value & mask );
    if( masked == 0 )  // tests for all zeroes
    {
        masked = ( value & 0x007F ) << 1;
        return write( ( const char* ) &masked, 1 );
    }
    // 2 byte / 14bit test
    mask = 0xC000;
    masked &= mask;
    if( masked == 0 )
    {
        masked = ( ( value & 0x3FFF ) << 2 ) | 0x01;
        return write( ( const char* ) &masked, 2 );
    }
    writeChar( 0x03 ).write( ( const char* ) &value, sizeof( value ) );
    lastWritten_ = 1 + sizeof( value );
    return *this;
}

/// writes a signed long long integer as 1 to 9 bytes,
/// encoded as follows:
/// xxxxxxx0 - 7 bit value, encoded in 1 byte
/// xxxxxx01+1byte - 14 bit value, encoded in 2 bytes
/// xxxxx011+2bytes - 21 bit value, encoded in 3 bytes
/// xxxx0111+3bytes - 28 bit value, encoded in 4 bytes
/// xxx01111+4bytes - 35 bit value, encoded in 5 bytes
/// xx011111+5bytes - 42 bit value, encoded in 6 bytes
/// x0111111+6bytes - 49 bit value, encoded in 7 bytes
/// 01111111+7bytes - 56 bit value, encoded in 8 bytes
/// 11111111+8bytes - 64 bit value, encoded in 9 bytes
OutStream& OutStream::writeLongLongCompact( const long long& value )
{
    // Until we have an unseralizer...
    //if ( 1 == 1 ) return write(( const char* ) &value, sizeof( value ) );

    // Start with 1 byte / 7bit test
    unsigned long long mask( 0xFFFFFFFFFFFFFFC0LL );
    unsigned long long masked( value & mask );
    if( masked == 0 || masked == mask )  // tests for all zeroes or all ones
    {
        masked = ( value & 0x000000000000007FLL ) << 1;
        return write( ( const char* ) &masked, 1 );
    }
    // 2 byte / 14bit test
    mask = 0xFFFFFFFFFFFFE000LL;
    masked &= mask;
    if( masked == 0 || masked == mask )
    {
        masked = ( ( value & 0x0000000000003FFFLL ) << 2 ) | 0x01LL;
        return write( ( const char* ) &masked, 2 );
    }
    // 3 byte / 21bit test
    mask = 0xFFFFFFFFFFF00000LL;
    masked &= mask;
    if( masked == 0 || masked == mask )
    {
        masked = ( ( value & 0x00000000001FFFFFLL ) << 3 ) | 0x03LL;
        return write( ( const char* ) &masked, 3 );
    }
    // 4 byte / 28bit test
    mask = 0xFFFFFFFFF8000000LL;
    masked &= mask;
    if( masked == 0 || masked == mask )
    {
        masked = ( ( value & 0x000000000FFFFFFFLL ) << 4 ) | 0x07LL;
        return write( ( const char* ) &masked, 4 );
    }
    // 5 byte / 35bit test
    mask = 0xFFFFFFFC00000000LL;
    masked &= mask;
    if( masked == 0 || masked == mask )
    {
        masked = ( ( value & 0x00000007FFFFFFFFLL ) << 5 ) | 0x0FLL;
        return write( ( const char* ) &masked, 5 );
    }
    // 6 byte / 42bit test
    mask = 0xFFFFFE0000000000LL;
    masked &= mask;
    if( masked == 0 || masked == mask )
    {
        masked = ( ( value & 0x000003FFFFFFFFFFLL ) << 6 ) | 0x1FLL;
        return write( ( const char* ) &masked, 6 );
    }
    // 7 byte / 49bit test
    mask = 0xFFFF000000000000LL;
    masked &= mask;
    if( masked == 0 || masked == mask )
    {
        masked = ( ( value & 0x0001FFFFFFFFFFFFLL ) << 7 ) | 0x3FLL;
        return write( ( const char* ) &masked, 7 );
    }
    // 8 byte / 56bit test
    mask = 0xFF80000000000000LL;
    masked &= mask;
    if( masked == 0 || masked == mask )
    {
        masked = ( ( value & 0x00FFFFFFFFFFFFFFLL ) << 8 ) | 0x7FLL;
        return write( ( const char* ) &masked, 8 );
    }
    writeChar( 0xFF ).write( ( const char* ) &value, sizeof( value ) );
    lastWritten_ = 1 + sizeof( value );
    return *this;
}
