/**
 * PANDA 3D SOFTWARE
 * Copyright (c) Carnegie Mellon University.  All rights reserved.
 *
 * All use of this software is subject to the terms of the revised BSD
 * license.  You should have received a copy of this license along
 * with this source code in a file named "LICENSE."
 *
 * @file stringStreamBuf.cxx
 * @author drose
 * @date 2007-07-02
 */

#include "stringStreamBuf.h"
#include "pnotify.h"
#include "config_express.h"

/**
 *
 */
StringStreamBuf::
StringStreamBuf() {
#ifdef PHAVE_IOSTREAM
  _buffer = (char *)PANDA_MALLOC_ARRAY(2048);
  char *ebuf = _buffer + 2048;
  char *mbuf = _buffer + 1024;
  setg(_buffer, mbuf, mbuf);
  setp(mbuf, ebuf);

#else
  allocate();
  // Chop the buffer in half.  The bottom half goes to the get buffer; the top
  // half goes to the put buffer.
  char *b = base();
  char *t = ebuf();
  char *m = b + (t - b) / 2;
  setg(b, m, m);
  setp(b, m);
#endif

  _gpos = 0;
  _ppos = 0;
}

/**
 *
 */
StringStreamBuf::
~StringStreamBuf() {
#ifdef PHAVE_IOSTREAM
  PANDA_FREE_ARRAY(_buffer);
#endif
}

/**
 * Empties the buffer.
 */
void StringStreamBuf::
clear() {
  _data.clear();
  _gpos = 0;
  _ppos = 0;

  pbump(pbase() - pptr());
  gbump(egptr() - gptr());
}

/**
 * Attempts to extract the indicated number of characters from the current
 * file position.  Returns the number of characters extracted.
 */
size_t StringStreamBuf::
read_chars(char *start, size_t length) {
  if (length == 0) {
    return 0;
  }

  // Make sure the write buffer is flushed.
  sync();

  if (_data.size() <= _gpos) {
    return 0;
  }

  length = min(length, _data.size() - _gpos);
  memcpy(start, &_data[_gpos], length);
  _gpos += length;
  return length;
}

/**
 * Appends the indicated stream of characters to the current file position.
 */
void StringStreamBuf::
write_chars(const char *start, size_t length) {
  if (length != 0) {
    // Make sure the read buffer is flushed.
    size_t n = egptr() - gptr();
    gbump(n);
    _gpos -= n;

    if (_data.size() > _ppos) {
      // We are overwriting some data.
      size_t remaining_length = _data.size() - _ppos;
      size_t overwrite_length = min(remaining_length, length);
      memcpy(&_data[_ppos], start, overwrite_length);
      length -= overwrite_length;
      _ppos += overwrite_length;
      start += overwrite_length;
    }

    if (_data.size() < _ppos) {
      // We need to append some zeroes.
      _data.insert(_data.end(), _ppos - _data.size(), (unsigned char)0);
    }

    if (length != 0) {
      // We are appending some data.
      _data.insert(_data.begin() + _ppos, (const unsigned char *)start, (const unsigned char *)start + length);
      _ppos += length;
    }
  }
}

/**
 * Implements seeking within the stream.
 */
streampos StringStreamBuf::
seekoff(streamoff off, ios_seekdir dir, ios_openmode which) {
  streampos result = -1;

  // Sync the iostream buffer first.
  sync();

  if (which & ios::in) {
    // Determine the current file position.
    size_t n = egptr() - gptr();
    gbump(n);
    _gpos -= n;
    size_t cur_pos = _gpos;
    size_t new_pos = cur_pos;

    // Now adjust the data pointer appropriately.
    switch (dir) {
    case ios::beg:
      new_pos = (size_t)off;
      break;

    case ios::cur:
      new_pos = (size_t)((int)cur_pos + off);
      break;

    case ios::end:
      new_pos = (size_t)((int)_data.size() + off);
      break;

    default:
      // Shouldn't get here.
      break;
    }

    _gpos = new_pos;
    result = new_pos;
  }

  if (which & ios::out) {
    // Determine the current file position.
    size_t n = pptr() - pbase();
    size_t cur_pos = _ppos + n;
    size_t new_pos = cur_pos;

    // Now adjust the data pointer appropriately.
    switch (dir) {
    case ios::beg:
      new_pos = (size_t)off;
      break;

    case ios::cur:
      new_pos = (size_t)((int)cur_pos + off);
      break;

    case ios::end:
      new_pos = (size_t)((int)_data.size() + off);
      break;

    default:
      // Shouldn't get here.
      break;
    }

    _ppos = new_pos;
    result = new_pos;
  }

  return result;
}

/**
 * A variant on seekoff() to implement seeking within a stream.
 *
 * The MSDN Library claims that it is only necessary to redefine seekoff(),
 * and not seekpos() as well, as the default implementation of seekpos() is
 * supposed to map to seekoff() exactly as I am doing here; but in fact it
 * must do something else, because seeking didn't work on Windows until I
 * redefined this function as well.
 */
streampos StringStreamBuf::
seekpos(streampos pos, ios_openmode which) {
  return seekoff(pos, ios::beg, which);
}

/**
 * Called by the system ostream implementation when its internal buffer is
 * filled, plus one character.
 */
int StringStreamBuf::
overflow(int ch) {
  size_t n = pptr() - pbase();
  if (n != 0) {
    write_chars(pbase(), n);
    pbump(-(int)n);
  }

  if (ch != EOF) {
    // Write one more character.
    char c = ch;
    write_chars(&c, 1);
  }

  return 0;
}

/**
 * Called by the system iostream implementation to implement a flush
 * operation.
 */
int StringStreamBuf::
sync() {
  size_t n = pptr() - pbase();

  write_chars(pbase(), n);
  pbump(-(int)n);

  return 0;
}

/**
 * Called by the system istream implementation when its internal buffer needs
 * more characters.
 */
int StringStreamBuf::
underflow() {
  // Sometimes underflow() is called even if the buffer is not empty.
  if (gptr() >= egptr()) {
    // Mark the buffer filled (with buffer_size bytes).
    size_t buffer_size = egptr() - eback();
    gbump(-(int)buffer_size);

    size_t num_bytes = buffer_size;
    size_t read_count = read_chars(gptr(), buffer_size);

    if (read_count != num_bytes) {
      // Oops, we didn't read what we thought we would.
      if (read_count == 0) {
        gbump(num_bytes);
        return EOF;
      }

      // Slide what we did read to the top of the buffer.
      nassertr(read_count < num_bytes, EOF);
      size_t delta = num_bytes - read_count;
      memmove(gptr() + delta, gptr(), read_count);
      gbump(delta);
    }
  }

  return (unsigned char)*gptr();
}
