// Filename: pfstreamBuf.cxx
// Created by:  cary (12Dec00)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://www.panda3d.org/license.txt .
//
// To contact the maintainers of this program write to
// panda3d@yahoogroups.com .
//
////////////////////////////////////////////////////////////////////

#include "pfstreamBuf.h"
#include <assert.h>

#ifndef HAVE_STREAMSIZE
// Some compilers (notably SGI) don't define this for us
typedef int streamsize;
#endif /* HAVE_STREAMSIZE */

PipeStreamBuf::PipeStreamBuf(PipeStreamBuf::Direction dir) : _dir(dir),
                                                             _pipe((FILE*)0L) {
#ifndef WIN32_VC
  // taken from Dr. Ose.
  // These lines, which are essential on Irix and Linux, seem to be
  // unnecessary and not understood on Windows.
  allocate();
  assert((dir == Input) || (dir == Output));
  if (dir == Input) {
    //    cerr << "allocated reserve is " << blen() << " bytes long" << endl;
    setg(base(), ebuf(), ebuf());
  } else {
    setp(base(), ebuf());
  }
#endif /* WIN32_VC */
}

PipeStreamBuf::~PipeStreamBuf(void) {
  if (_pipe != (FILE*)0L) {
    sync();
    flush();
    // any other cleanup needed (pclose, etc)
    pclose(_pipe);
  }
}

void PipeStreamBuf::flush(void) {
  assert(_pipe != (FILE*)0L);
  if (_dir == Output) {
    write_chars("", 0, true);
  }
}

void PipeStreamBuf::command(const string cmd) {
  assert(_pipe == (FILE*)0L);
  const char *typ = (_dir == Output)?"w":"r";
  _pipe = popen(cmd.c_str(), typ);
}

int PipeStreamBuf::overflow(int c) {
  assert(_pipe != (FILE*)0L);
  assert(_dir == Output);
  streamsize n = pptr() - pbase();
  if (n != 0) {
    write_chars(pbase(), n, false);
    pbump(-n);  // reset pptr()
  }
  if (c != EOF) {
    // write one more character
    char ch = c;
    write_chars(&ch, 1, false);
  }
  return 0;
}

int PipeStreamBuf::sync(void) {
  assert(_pipe != (FILE*)0L);
  if (_dir == Output) {
    streamsize n = pptr() - pbase();
    write_chars(pbase(), n, false);
    pbump(-n);
  } else {
    streamsize n = egptr() - gptr();
    if (n != 0) {
      gbump(n);  // flush all our stored input away
      cerr << "pfstream tossed out " << n << " bytes" << endl;
    }
  }
  return 0;
}

int PipeStreamBuf::underflow(void) {
  assert(_pipe != (FILE*)0L);
  assert(_dir == Input);
  if ((eback() == (char*)0L) || (gptr() == (char*)0L) ||
      (egptr() == (char*)0L)) {
    // must be in win32
    char* buf = new char[4096];
    char* ebuf = &(buf[4096]);
    setg(buf, ebuf, ebuf);
  }
  if (gptr() < egptr()) {
    char c = *(gptr());
    return c;
  }
  if (feof(_pipe) != 0)
    return EOF;
#ifdef WIN32_VC
  size_t len = 4096;
#else /* WIN32_VC */
  size_t len = ebuf() - base();
#endif /* WIN32_VC */
  char* buf = new char[len];
  size_t n = fread(buf, 1, len, _pipe);
  int ret = buf[0];
  if (n == 0)
    ret = EOF;
  else {
#ifdef WIN32_VC
    memcpy(eback()+(len-n), buf, n);
#else /* WIN32_VC */
    memcpy(base()+(len-n), buf, n);
#endif /* WIN32_VC */
    gbump(-((int)n));
  }
  delete buf;
  return ret;
}

void PipeStreamBuf::write_chars(const char* start, int length, bool flush) {
  assert(_dir == Output);
  size_t orig = _line_buffer.length();
  string latest(start, length);
  string line;

  if (flush) {
    // if we're going to flush the stream now, we dump the whole thing
    // reguardless of whether we have reached end-of-line.
    line = _line_buffer + latest;
    _line_buffer = "";
  } else {
    // Otherwise, we check for the end-of-line character.
    _line_buffer += latest;
    size_t eol = _line_buffer.rfind('\n', orig);
    if (eol != string::npos) {
      line = _line_buffer.substr(0, eol+1);
      _line_buffer = _line_buffer.substr(eol+1);
    }
  }
  // now output 'line'
  size_t wrote = fwrite(line.c_str(), 1, line.length(), _pipe);
  if (wrote != line.length())
    cerr << "wrote only " << wrote << " of " << line.length()
         << " bytes to pipe" << endl;
  fflush(_pipe);
}
