/*
 * Buffer class
 *
 * Copyright (C) 2001 Barnaby Gray <barnaby@beedesign.co.uk>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 */

#include <libicq2000/buffer.h>

#include <algorithm>
#include <ctype.h>

using std::string;
using std::endl;
using std::ostream;

using ICQ2000::Translator;

Buffer::Buffer(Translator *translator) : m_data(), m_endn(BIG), m_out_pos(0), 
  m_translator(translator) { }

Buffer::Buffer(const unsigned char* d, unsigned int size, Translator *translator) 
  : m_data(d, d+size), m_endn(BIG), m_out_pos(0) { }

Buffer::Buffer(Buffer& b, unsigned int start, unsigned int data_len) 
  : m_data(b.m_data.begin()+start, b.m_data.begin()+start+data_len), 
  m_endn(BIG), m_out_pos(0), m_translator(b.m_translator) { }

unsigned char& Buffer::operator[](unsigned int p) {
  return m_data[p];
}

void Buffer::clear() {
  m_data.clear();
  m_out_pos = 0;
}

bool Buffer::empty() {
  return m_data.empty();
}

void Buffer::chopOffBuffer(Buffer& b, unsigned int sz) {
  copy( m_data.begin(), m_data.begin()+sz, back_inserter(b.m_data) );
  m_data.erase( m_data.begin(), m_data.begin()+sz );
  m_out_pos = 0;
}

void Buffer::Pack(const unsigned char *d, unsigned int size) {
  copy(d, d+size, back_inserter(m_data));
}

void Buffer::PackUint16StringNull(const string& s) {
  (*this) << (unsigned short)(s.size()+1);
  Pack(s);
  (*this) << (unsigned char)0x00;
}

void Buffer::PackUint16TranslatedNull(const string& s) {
  PackUint16StringNull( m_translator->ClientToServerCC( s ) );
}

void Buffer::PackByteString(const string& s) {
  (*this) << (unsigned char)(s.size());
  Pack(s);
}

void Buffer::UnpackCRLFString(string& s) {
  iterator i;

  i = find(m_data.begin()+m_out_pos, m_data.end(), '\n');

  if (i != m_data.end() ) {
    Unpack(s, i-m_data.begin()-m_out_pos+1);
  }
}

void Buffer::Pack(const string& s) {
  copy(s.begin(), s.end(), back_inserter(m_data));
}

unsigned char Buffer::UnpackChar() {
  if (m_out_pos + 1 > m_data.size()) return 0;
  else return m_data[m_out_pos++];
}

void Buffer::UnpackUint32String(string& s) {
  unsigned int l;
  (*this) >> l;
  Unpack(s, l);
}

void Buffer::UnpackUint16StringNull(string& s) {
  unsigned short sh;
  (*this) >> sh;
  if (sh > 0) {
    Unpack(s, sh-1);
    (*this).advance(1);
  }
}

void Buffer::UnpackUint16TranslatedNull(string& s) {
  UnpackUint16StringNull( s );
  ServerToClient(s);
}

void Buffer::UnpackByteString(string& s) {
  unsigned char c;
  (*this) >> c;
  Unpack(s, c);
}

void Buffer::Unpack(string& s, unsigned int size) {
  if (m_out_pos >= m_data.size()) return;

  if (m_out_pos+size > m_data.size()) size = m_data.size()-m_out_pos;

  iterator i = m_data.begin()+m_out_pos;
  iterator end = m_data.begin()+m_out_pos+size;

  while (i != end) {
    s += *i;
    ++i;
  }

  m_out_pos += size;
}

void Buffer::Unpack(unsigned char *const d, unsigned int size) {
  if (m_out_pos+size > m_data.size()) size = m_data.size()-m_out_pos;
  copy(m_data.begin()+m_out_pos, m_data.begin()+m_out_pos+size, d);
  m_out_pos += size;
}

Buffer::marker Buffer::getAutoSizeShortMarker() 
{
  // reserve a short
  (*this) << (unsigned short)0;

  marker m;
  m.position = size();
  m.endianness = m_endn;
  m.size = 2;
  return m;
}

Buffer::marker Buffer::getAutoSizeIntMarker() 
{
  // reserve an int
  (*this) << (unsigned int)0;

  marker m;
  m.position = size();
  m.endianness = m_endn;
  m.size = 4;
  return m;
}

void Buffer::setAutoSizeMarker(const marker& m)
{
  unsigned int autosize = size() - m.position;

  if (m.size == 2) {
    if (m.endianness == BIG) {
      m_data[ m.position - 2 ] = ((autosize >> 8) & 0xff);
      m_data[ m.position - 1 ] = ((autosize >> 0) & 0xff);
    } else {
      m_data[ m.position - 2 ] = ((autosize >> 0) & 0xff);
      m_data[ m.position - 1 ] = ((autosize >> 8) & 0xff);
    }
  } else if (m.size == 4) {
    if (m.endianness == BIG) {
      m_data[ m.position - 4 ] = ((autosize >> 24) & 0xff);
      m_data[ m.position - 3 ] = ((autosize >> 16) & 0xff);
      m_data[ m.position - 2 ] = ((autosize >> 8) & 0xff);
      m_data[ m.position - 1 ] = ((autosize >> 0) & 0xff);
    } else {
      m_data[ m.position - 4 ] = ((autosize >> 0) & 0xff);
      m_data[ m.position - 3 ] = ((autosize >> 8) & 0xff);
      m_data[ m.position - 2 ] = ((autosize >> 16) & 0xff);
      m_data[ m.position - 1 ] = ((autosize >> 24) & 0xff);
    }
  }
}

// -- Input stream methods --

Buffer& Buffer::operator<<(unsigned char l) {
  m_data.push_back(l);
  return (*this);
}

Buffer& Buffer::operator<<(unsigned short l) {
  if (m_endn == BIG) {
    m_data.push_back((l>>8) & 0xFF);
    m_data.push_back(l & 0xFF);
  } else {
    m_data.push_back(l & 0xFF);
    m_data.push_back((l>>8) & 0xFF);
  }    
  return (*this);
}

Buffer& Buffer::operator<<(unsigned int l) {
  if (m_endn == BIG) {
    m_data.push_back((l >> 24) & 0xFF);
    m_data.push_back((l >> 16) & 0xFF);
    m_data.push_back((l >> 8) & 0xFF);
    m_data.push_back(l & 0xFF);
  } else {
    m_data.push_back(l & 0xFF);
    m_data.push_back((l >> 8) & 0xFF);
    m_data.push_back((l >> 16) & 0xFF);
    m_data.push_back((l >> 24) & 0xFF);
  }
  return (*this);
}

// strings stored as length (2 bytes), string m_data, _not_ null-terminated
Buffer& Buffer::operator<<(const string& s) {
  unsigned short sz = s.size();
  m_data.push_back((sz>>8) & 0xFF);
  m_data.push_back(sz & 0xFF);
  Pack(s);
  return (*this);
}

// -- Output stream methods --

Buffer& Buffer::operator>>(unsigned char& l) {
  if (m_out_pos + 1 > m_data.size()) l = 0;
  else l = m_data[m_out_pos++];
  return (*this);
}

Buffer& Buffer::operator>>(unsigned short& l) {
  if (m_out_pos + 2 > m_data.size()) {
    l = 0;
    m_out_pos += 2;
  } else {
    if (m_endn == BIG) {
      l = ((unsigned short)m_data[m_out_pos++] << 8)
	+ ((unsigned short)m_data[m_out_pos++]);
    } else {
      l = ((unsigned short)m_data[m_out_pos++])
	+ ((unsigned short)m_data[m_out_pos++] << 8);
    }
  }
  return (*this);
}

Buffer& Buffer::operator>>(unsigned int& l) {
  if (m_out_pos + 4 > m_data.size()) {
    l = 0;
    m_out_pos += 4;
  } else {
    if (m_endn == BIG) {
      l = ((unsigned int)m_data[m_out_pos++] << 24)
	+ ((unsigned int)m_data[m_out_pos++] << 16)
	+ ((unsigned int)m_data[m_out_pos++] << 8)
	+ ((unsigned int)m_data[m_out_pos++]);
    } else {
      l = ((unsigned int)m_data[m_out_pos++])
	+ ((unsigned int)m_data[m_out_pos++] << 8)
	+ ((unsigned int)m_data[m_out_pos++] << 16)
	+ ((unsigned int)m_data[m_out_pos++] << 24);
    }
  }
  return (*this);
}

// strings stored as length (2 bytes), string data, _not_ null-terminated
Buffer& Buffer::operator>>(string& s) {
  if (m_out_pos + 2 > m_data.size()) {
    s = ""; // clear() method doesn't seem to exist!
    m_out_pos += 2;
  } else {
    unsigned short sz;
    (*this) >> sz;
    Unpack(s, sz);
  }
  return (*this);
}

void Buffer::setEndianness(endian e) {
  m_endn = e;
}

void Buffer::setBigEndian() 
{
  m_endn = BIG;
}

void Buffer::setLittleEndian()
{
  m_endn = LITTLE;
}

void Buffer::dump(ostream& out) {
  char d[] = "123456789abcdef0";
  out << std::hex << std::setfill('0');
  unsigned int m = ((m_data.size()+15)/16)*16;
  for (unsigned int a = 0; a < m; a++) {
    if (a % 16 == 0) out << std::setw(4) << a << "  ";
    if (a < m_data.size()) {
      out << std::setw(2) << (int)m_data[a] << " ";
      d[a%16] = isprint(m_data[a]) ? m_data[a] : '.';
    } else {
      out << "   ";
      d[a%16] = ' ';
    }
    if (a % 16 == 15) out << " " << d << endl;
  }
}

ostream& operator<<(ostream& out, Buffer& b)
{
  b.dump(out);
  return out;
}

void Buffer::setTranslator(Translator *translator){
  m_translator=translator;
} 
void Buffer::ServerToClient(string& szString){
  m_translator->ServerToClient(szString);
}
void Buffer::ClientToServer(string& szString){
  m_translator->ClientToServer(szString);
}
string Buffer::ServerToClientCC(const string& szString){
  return m_translator->ServerToClientCC(szString);
}
string Buffer::ClientToServerCC(const string& szString){
  return m_translator->ClientToServerCC(szString);
}
void Buffer::ServerToClient(char &_cChar){
  m_translator->ServerToClient(_cChar);
}
void Buffer::ClientToServer(char &_cChar){
  m_translator->ClientToServer(_cChar);
}

