/*****************************************************************************
 * Author:   Valient Gough <vgough@pobox.com>
 *
 *****************************************************************************
 * Copyright (c) 2004, Valient Gough
 * 
 * This program is free software; you can distribute it and/or modify it under 
 * the terms of the GNU General Public License (GPL), as published by the Free
 * Software Foundation; either version 2 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 General Public License for
 * more details.
 */

#include "ConfigVar.h"
#include <rlog/rlog.h>

using namespace rlog;

#ifndef MIN
inline int MIN(int a, int b)
{
    return (a < b) ? a : b;
}
#endif


ConfigVar::ConfigVar()
    : rel::LinkedOwner()
{
    pd = new ConfigVarData;
    pd->offset = 0;
}

ConfigVar::ConfigVar(const std::string &buf)
    : rel::LinkedOwner()
{
    pd = new ConfigVarData;
    pd->buffer = buf;
    pd->offset = 0;
}

ConfigVar::ConfigVar(const ConfigVar &src)
    : rel::LinkedOwner()
    , pd( src.pd )
{
    shareOwnership( &src );
}

ConfigVar::~ConfigVar()
{
    if(pd && dropOwnership())
    {
	// our responsibility to destroy data..
	delete pd;
	pd = NULL;
    }
}

ConfigVar & ConfigVar::operator = (const ConfigVar &src)
{
    if(src.pd == pd)
	return *this;
    else
    {
	if(pd && dropOwnership())
	{
	    delete pd;
	    pd = NULL;
	}
	pd = src.pd;
	shareOwnership( &src );
    }

    return *this;
}

void ConfigVar::resetOffset()
{
    pd->offset = 0;
}

int ConfigVar::read(unsigned char *buffer_, int bytes) const
{
    int toCopy = MIN( bytes, pd->buffer.size() - pd->offset );

    if(toCopy > 0)
	memcpy( buffer_, pd->buffer.data() + pd->offset, toCopy );

    pd->offset += toCopy;

    return toCopy;
}

int ConfigVar::write(const unsigned char *data, int bytes)
{
    if(pd->buffer.size() == (unsigned int)pd->offset)
    {
	pd->buffer.append( (const char *)data, bytes );
    } else
    {
	pd->buffer.insert( pd->offset, (const char *)data, bytes );
    }

    pd->offset += bytes;

    return bytes;
}

int ConfigVar::size() const
{
    return pd->buffer.size();
}

const char *ConfigVar::buffer() const
{
    return pd->buffer.data();
}

int ConfigVar::at() const
{
    return pd->offset;
}

void ConfigVar::writeString(const char *data, int bytes)
{
    writeBER( bytes );
    write( (const unsigned char *)data, bytes );
}


// convert integer to BER encoded integer
void ConfigVar::writeBER(int val)
{
    // we can represent 7 bits per char output, so a 32bit number may take up
    // to 5 bytes.
    // first byte:    0x0000007f                                   0111,1111
    // second byte:   0x00003f80                         0011,1111 1000,0000
    // third byte:    0x001fb000     0000,0000 0001,1111 1100,0000 0000,0000
    // fourth byte:   0x0fe00000     0000,1111 1110,0000
    // fifth byte:    0xf0000000     1111,0000
    unsigned char digit[5];

    digit[4] =        (unsigned char)((val & 0x0000007f));
    digit[3] = 0x80 | (unsigned char)((val & 0x00003f80) >> 7);
    digit[2] = 0x80 | (unsigned char)((val & 0x001fc000) >> 14);
    digit[1] = 0x80 | (unsigned char)((val & 0x0fe00000) >> 21);
    digit[0] = 0x80 | (unsigned char)((val & 0xf0000000) >> 28);

    // find the starting point - we only need to output starting at the most
    // significant non-zero digit..
    int start = 0;
    while(digit[start] == 0x80)
	++start;

    write( digit + start, 5-start );
}

int ConfigVar::readBER() const
{
    const unsigned char * buf = (const unsigned char *)buffer();
    int bytes = this->size();
    int offset = at();
    int value = 0;
    bool highBitSet;

    rAssert( offset < bytes );

    do
    {
	unsigned char tmp = buf[offset++];
	highBitSet = tmp & 0x80;

	value = (value << 7) | (int)(tmp & 0x7f);
    } while(highBitSet && offset < bytes);

    pd->offset = offset;

    // should never end up with a negative number..
    rAssert( value >= 0 );

    return value;
}

int ConfigVar::readBER( int defaultValue ) const
{
    int bytes = this->size();
    int offset = at();

    if(offset >= bytes)
	return defaultValue;
    else
	return readBER();
}

bool ConfigVar::readBool( bool defaultValue ) const
{
    int tmp = readBER( defaultValue ? 1 : 0 );
    return (tmp != 0);
}

ConfigVar & operator << (ConfigVar &src, bool value)
{
    src.writeBER( value ? 1 : 0 );
    return src;
}

ConfigVar & operator << (ConfigVar &src, int var)
{
    src.writeBER( var );
    return src;
}

ConfigVar & operator << (ConfigVar &src, const std::string &str)
{
    src.writeString( str.data(), str.length() );
    return src;
}

const ConfigVar & operator >> (const ConfigVar &src, bool &result)
{
    int tmp = src.readBER();
    result = (tmp != 0);
    return src;
}

const ConfigVar & operator >> (const ConfigVar &src, int &result)
{
    result = src.readBER();
    return src;
}

const ConfigVar & operator >> (const ConfigVar &src, std::string &result)
{
    int length = src.readBER();
    //rAssert(length > 0);

    int readLen;

    unsigned char tmpBuf[32];
    if(length > (int)sizeof(tmpBuf))
    {
	unsigned char *ptr = new unsigned char[length];
	readLen = src.read( ptr, length );
	result.assign( (char*)ptr, length );
	delete[] ptr;
    } else
    {
	readLen = src.read( tmpBuf, length );
	result.assign( (char*)tmpBuf, length );
    }

    if(readLen != length)
    {
	rDebug("string encoded as size %i bytes, read %i", length, readLen );
    }
    rAssert(readLen == length);

    return src;
}

