/*

    Copyright (C) 2000 Stefan Westerfeld
                       stefan@space.twc.de

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.
  
    This library 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
    Library General Public License for more details.
   
    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.

    */

#include "buffer.h"
#include <assert.h>
#include <string.h>

using namespace std;
using namespace Arts;

Buffer::Buffer() : rpos(0), _readError(false),d(0)
{
	contents.reserve(128);
}

Buffer::~Buffer()
{
}

long Buffer::size()
{
	return contents.size();
}

long Buffer::remaining()
{
	return size()-rpos;
}

bool Buffer::readError() {
	return _readError;
}

void Buffer::writeBool(bool b) {
	contents.push_back(b?1:0);
}

void Buffer::writeByte(mcopbyte b) {
	contents.push_back(b);
}

void Buffer::writeLong(long l) {
	contents.push_back((l >> 24) & 0xff);
	contents.push_back((l >> 16) & 0xff);
	contents.push_back((l >> 8) & 0xff);
	contents.push_back(l & 0xff);
}

void Buffer::writeBoolSeq(const vector<bool>& seq) {
	writeLong(seq.size());

	vector<bool>::const_iterator i;
	for(i = seq.begin(); i != seq.end(); i++) writeBool(*i);
}

void Buffer::writeByteSeq(const vector<mcopbyte>& seq) {
	writeLong(seq.size());	 // bytes are sent raw, so we can call read here
	write(seq);
}

void Buffer::writeLongSeq(const vector<long>& seq) {
	writeLong(seq.size());

	vector<long>::const_iterator i;
	for(i = seq.begin(); i != seq.end(); i++) writeLong(*i);
}

void Buffer::writeFloat(float f) {
	// FIXME: on some machines this may fail badly (there is explicit
	// float marshalling and demarshalling code in mico/orb/util.cc)
	union { float f; long l; } u = {f};
	writeLong(u.l);
}

void Buffer::writeFloatSeq(const std::vector<float>& seq) {
	writeLong(seq.size());

	vector<float>::const_iterator i;
	for(i = seq.begin(); i != seq.end(); i++) writeFloat(*i);
}

void Buffer::writeString(const string& s) {
	long len = s.size()+1;

	writeLong(len);
	contents.insert(contents.end(),reinterpret_cast<const unsigned char*>(s.c_str()),
		        reinterpret_cast<const unsigned char*>(s.c_str()+len));
}

void Buffer::writeStringSeq(const vector<string>& seq) {
	writeLong(seq.size());

	vector<string>::const_iterator i;
	for(i = seq.begin(); i != seq.end(); i++) writeString(*i);
}

void Buffer::write(void *data, long len) {
	unsigned char *c = (unsigned char *)data;

	contents.insert(contents.end(),c,c+len);
}

void Buffer::write(const vector<mcopbyte>& raw)
{
	contents.insert(contents.end(), raw.begin(), raw.end());
}

void Buffer::read(vector<mcopbyte>& raw, long l)
{
	if(l >= 0 && remaining() >= l) {
		raw.clear();
		raw.insert(raw.end(), contents.begin()+rpos, contents.begin()+rpos+l);
		rpos += l;
	} else {
		_readError = true;
	}
}

void *Buffer::read(long l) {
	void *result = 0;

	if(l >= 0 && remaining() >= l) {
		result = &contents[rpos];
		rpos += l;
	} else {
		_readError = true;
	}
	return result;
}

void *Buffer::peek(long l) {
	assert(l >= 0 && remaining() >= l);
	return &contents[rpos];
}

void Buffer::skip(long l) {
	if(l >= 0 && remaining() >= l) {
		rpos += l;
	} else {
		_readError = true;
	}
}

void Buffer::rewind() {
	rpos = 0;
}

bool Buffer::readBool()
{
	long result = false;
	if(remaining() >= 1) {
		if(contents[rpos] == 1)
			result = true;
		else
		{
			assert(contents[rpos] == 0);
		}
		rpos += 1;
	} else {
		_readError = true;
	}
	return result;
}

void Buffer::readBoolSeq(vector<bool>& result)
{
	// might be optimizable a bit
	long i,seqlen = readLong();

	result.clear();
	if(seqlen >= 0 && remaining() >= seqlen)
	{
		for(i=0;i<seqlen;i++) result.push_back(readBool());
	}
	else
	{
		_readError = true;
	}
}


mcopbyte Buffer::readByte()
{
	if(remaining() >= 1)
	{
		return contents[rpos++];
	}
	else
	{
		_readError = true;
		return 0;
	}
}

void Buffer::readByteSeq(vector<mcopbyte>& result)
{
	long seqlen = readLong(); // bytes are sent raw, so we can call read here
	read(result, seqlen);
}

long Buffer::readLong()
{
	long result = 0;
	if(remaining() >= 4) {
		result = (contents[rpos]   << 24)
			   + (contents[rpos+1] << 16)
			   + (contents[rpos+2] << 8)
	 		   +  contents[rpos+3];
		rpos += 4;
	} else {
		_readError = true;
	}
	return result;
}

void Buffer::readLongSeq(vector<long>& result)
{
	// might be optimizable a bit
	long i,seqlen = readLong();

	result.clear();
	if(seqlen * 4 >= 0 && remaining() >= seqlen * 4)
	{
		for(i=0;i<seqlen;i++) result.push_back(readLong());
	}
	else
	{
		_readError = true;
	}
}

float Buffer::readFloat()
{
	// FIXME: see writeFloat()
	union {float f; long l; } u;
	u.l = readLong();

	if(!_readError) return u.f;
	return 0.0;
}

void Buffer::readFloatSeq(vector<float>& result)
{
	// might be optimizable a bit
	long i,seqlen = readLong();

	result.clear();
	if(seqlen * 4 >= 0 && remaining() >= seqlen * 4)
	{
		for(i=0;i<seqlen;i++) result.push_back(readFloat());
	}
	else
	{
		_readError = true;
	}
}

void Buffer::readString(string& result)
{
	long len = readLong();
	char *data = (char *)read(len);

	if(data && len)
		result.assign(data,len-1);
	else
		result = "";
}

void Buffer::readStringSeq(vector<string>& result)
{
	// might be optimizable a bit

	long i,seqlen = readLong();

	result.clear();
	//result.reserve(seqlen);

	for(i=0;i<seqlen;i++) {
		string s;

		readString(s);
		if(_readError) return;

		result.push_back(s);
	}
}


void Buffer::patchLength()
{
	long len = size();
	assert(len >= 8);

	contents[4] = (len >> 24) & 0xff;
	contents[5] = (len >> 16) & 0xff;
	contents[6] = (len >> 8) & 0xff;
	contents[7] = len & 0xff;
}

void Buffer::patchLong(long position, long value)
{
	long len = size();
	assert(len >= position+4);

	contents[position]   = (value >> 24) & 0xff;
	contents[position+1] = (value >> 16) & 0xff;
	contents[position+2] = (value >> 8) & 0xff;
	contents[position+3] = value & 0xff;
}

string Buffer::toString(const string& name)
{
	string result;
	char hex[17] = "0123456789abcdef";

	vector<unsigned char>::iterator ci;
	for(ci = contents.begin(); ci != contents.end(); ci++)
	{
		result += hex[(*ci >> 4) & 0xf];
		result += hex[*ci & 0xf];
	}

	if(name.empty()) return result;
	return name + ":" + result;
}

unsigned char Buffer::fromHexNibble(char c)
{
	int uc = (unsigned char)c;

	if(uc >= '0' && uc <= '9') return uc - (unsigned char)'0';
	if(uc >= 'a' && uc <= 'f') return uc + 10 - (unsigned char)'a';
	if(uc >= 'A' && uc <= 'F') return uc + 10 - (unsigned char)'A';

	return 16;	// error
}

static int stringncmp(const string& s1, const string& s2, size_t n)
{
	// I don't know a way to implement that compliant to all STL string
	// implementations (compare seems to work differently sometimes)
	return strncmp(s1.c_str(),s2.c_str(),n);
}

bool Buffer::fromString(const string& data, const string& name)
{
	string start = name+":";
	if(name.empty()) start = "";

	if(stringncmp(data,start,start.size()) != 0) return false;
	contents.clear();

	string::const_iterator di = data.begin() + start.size();
	
	while(di != data.end())
	{
		unsigned char h = fromHexNibble(*di++);	// high nibble
		if(di == data.end()) return false;

		unsigned char l = fromHexNibble(*di++);	// low nibble

		if(h >= 16 || l >= 16) return false;	// no proper hex digit
		contents.push_back((h << 4) + l);
	}
	return true;
}