// Filename: pfstreamBuf.cxx
// Created by:  cary (12Dec00)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, 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://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////

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

PipeStreamBuf::PipeStreamBuf(PipeStreamBuf::Direction dir) : 
  _dir(dir)
{
  init_pipe();

#ifndef HAVE_IOSTREAM
  // These lines, which are essential on older implementations of the
  // iostream library, are not understood by more recent versions.
  allocate();
  assert((dir == Input) || (dir == Output));
  if (dir == Input) {
    setg(base(), ebuf(), ebuf());
  } else {
    setp(base(), ebuf());
  }
#endif /* HAVE_IOSTREAM */
}

PipeStreamBuf::
~PipeStreamBuf(void) {
  if (is_open()) {
    sync();
    flush();
    close_pipe();
  }
}

void PipeStreamBuf::flush(void) {
  assert(is_open());
  if (_dir == Output) {
    write_chars("", 0, true);
  }
}

void PipeStreamBuf::command(const string cmd) {
  assert(!is_open());
  open_pipe(cmd);
}

int PipeStreamBuf::overflow(int c) {
  assert(is_open());
  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(is_open());
  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
#ifndef NDEBUG
      cerr << "pfstream tossed out " << n << " bytes" << endl;
#endif
    }
  }
  return 0;
}

int PipeStreamBuf::underflow(void) {
  assert(_dir == Input);
  if ((eback() == (char*)0L) || (gptr() == (char*)0L) ||
      (egptr() == (char*)0L)) {
    // must be new-style iostream library
    char* buf = new char[4096];
    char* ebuf = &(buf[4096]);
    setg(buf, ebuf, ebuf);
  }
  if (gptr() < egptr()) {
    char c = *(gptr());
    return c;
  }
  if (eof_pipe()) {
    return EOF;
  }
#ifdef HAVE_IOSTREAM
  size_t len = 4096;
#else /* HAVE_IOSTREAM */
  size_t len = ebuf() - base();
#endif /* HAVE_IOSTREAM */
  char* buf = new char[len];
  size_t n = read_pipe(buf, len);
  int ret = buf[0];
  if (n == 0)
    ret = EOF;
  else {
#ifdef HAVE_IOSTREAM
    memcpy(eback()+(len-n), buf, n);
#else /* HAVE_IOSTREAM */
    memcpy(base()+(len-n), buf, n);
#endif /* HAVE_IOSTREAM */
    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 = write_pipe(line.c_str(), line.length());
#ifndef NDEBUG
  if (wrote != line.length())
    cerr << "wrote only " << wrote << " of " << line.length()
         << " bytes to pipe" << endl;
#endif
}

#ifndef WIN_PIPE_CALLS

////////////////////////////////////////////////////////////////////
//     Function: PipeStreamBuf::init_pipe
//       Access: Private
//  Description: Initializes whatever data structures store the child
//               process information.  This function is only called
//               once at startup, by the constructor.
////////////////////////////////////////////////////////////////////
void PipeStreamBuf::
init_pipe() {
  _pipe = NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: PipeStreamBuf::is_open
//       Access: Private
//  Description: Returns true if the pipe has been opened, false
//               otherwise.
////////////////////////////////////////////////////////////////////
bool PipeStreamBuf::
is_open() const {
  return _pipe != NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: PipeStreamBuf::eof_pipe
//       Access: Private
//  Description: Returns true if there is an end-of-file condition on
//               the input, or if the pipe was never opened.
////////////////////////////////////////////////////////////////////
bool PipeStreamBuf::
eof_pipe() const {
  return (_pipe == NULL) && feof(_pipe);
}

////////////////////////////////////////////////////////////////////
//     Function: PipeStreamBuf::open_pipe
//       Access: Private
//  Description: Forks a child to run the indicated command, and
//               according to the setting of _dir, binds either its
//               input or output to this process for writing or
//               reading.
//
//               Returns true on success, false on failure.
////////////////////////////////////////////////////////////////////
bool PipeStreamBuf::
open_pipe(const string &cmd) {
  const char *typ = (_dir == Output)?"w":"r";
  _pipe = popen(cmd.c_str(), typ);
  return (_pipe != NULL);
}

////////////////////////////////////////////////////////////////////
//     Function: PipeStreamBuf::close_pipe
//       Access: Private
//  Description: Closes the pipe opened previously.
////////////////////////////////////////////////////////////////////
void PipeStreamBuf::
close_pipe() {
  if (_pipe != NULL) {
    fclose(_pipe);
    _pipe = NULL;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: PipeStreamBuf::write_pipe
//       Access: Private
//  Description: Writes the indicated data out to the child process
//               opened previously.  Returns the number of bytes read.
////////////////////////////////////////////////////////////////////
size_t PipeStreamBuf::
write_pipe(const char *data, size_t len) {
  size_t wrote_count = fwrite(data, 1, len, _pipe);
  fflush(_pipe);
  return wrote_count;
}

////////////////////////////////////////////////////////////////////
//     Function: PipeStreamBuf::read_pipe
//       Access: Private
//  Description: Reads the indicated amount of data from the child
//               process opened previously.  Returns the number of
//               bytes read.
////////////////////////////////////////////////////////////////////
size_t PipeStreamBuf::
read_pipe(char *data, size_t len) {
  return fread(data, 1, len, _pipe);
}

#else  // WIN_PIPE_CALLS

// The official Windows way of reading from a child process, without
// using a Unix-style convenience function like popen(), is similar in
// principle to the Unix pipe() method.  We have to first redirect our
// own stdout to an anonymous pipe, then we spawn a child, who
// inherits this new stdout.  Then we can restore our own stdout, and
// read from the other end of the pipe.

////////////////////////////////////////////////////////////////////
//     Function: PipeStreamBuf::init_pipe
//       Access: Private
//  Description: Initializes whatever data structures store the child
//               process information.  This function is only called
//               once at startup, by the constructor.
////////////////////////////////////////////////////////////////////
void PipeStreamBuf::
init_pipe() {
  _child_out = 0;
}

////////////////////////////////////////////////////////////////////
//     Function: PipeStreamBuf::is_open
//       Access: Private
//  Description: Returns true if the pipe has been opened, false
//               otherwise.
////////////////////////////////////////////////////////////////////
bool PipeStreamBuf::
is_open() const {
  return (_child_out != 0);
}

////////////////////////////////////////////////////////////////////
//     Function: PipeStreamBuf::eof_pipe
//       Access: Private
//  Description: Returns true if there is an end-of-file condition on
//               the input, or if the pipe was never opened.
////////////////////////////////////////////////////////////////////
bool PipeStreamBuf::
eof_pipe() const {
  return (_child_out == 0);
}

////////////////////////////////////////////////////////////////////
//     Function: PipeStreamBuf::open_pipe
//       Access: Private
//  Description: Forks a child to run the indicated command, and
//               according to the setting of _dir, binds either its
//               input or output to this process for writing or
//               reading.
//
//               Returns true on success, false on failure.
////////////////////////////////////////////////////////////////////
bool PipeStreamBuf::
open_pipe(const string &cmd) {
  close_pipe();

  // At the present, this only works for input pipes.  We can add code
  // to support output pipes later if anyone cares.
  if (_dir == Output) {
    return false;
  }

  // First, save our current stdout, so we can restore it after all of
  // this nonsense.
  HANDLE hSaveStdout = GetStdHandle(STD_OUTPUT_HANDLE); 
 
  // Now create a pipe to accept the child processes' output.
  HANDLE hChildStdoutRd, hChildStdoutWr;
  
  // Set the bInheritHandle flag so pipe handles are inherited. 
  SECURITY_ATTRIBUTES saAttr; 
  saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
  saAttr.bInheritHandle = TRUE; 
  saAttr.lpSecurityDescriptor = NULL; 
  if (!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) {
#ifndef NDEBUG
    cerr << "Unable to create output pipe\n";
#endif
    return false;
  }
 
  // Remap stdout to the "write" end of this pipe.
  if (!SetStdHandle(STD_OUTPUT_HANDLE, hChildStdoutWr)) {
#ifndef NDEBUG
    cerr << "Unable to redirect stdout\n";
#endif
    CloseHandle(hChildStdoutRd);
    CloseHandle(hChildStdoutWr);
    return false;
  }
 
  // Create noninheritable read handle and close the inheritable read 
  // handle. 

  BOOL fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdoutRd,
                                  GetCurrentProcess(), &_child_out,
                                  0, FALSE, DUPLICATE_SAME_ACCESS);
  
  if (!fSuccess) {
#ifndef NDEBUG
    cerr << "DuplicateHandle failed\n";
#endif
    CloseHandle(hChildStdoutRd);
    CloseHandle(hChildStdoutWr);
    return false;
  }
  CloseHandle(hChildStdoutRd);
 
  // Now spawn the child process.
  
  // Both WinExec() and CreateProcess() want a non-const char pointer.
  // Maybe they change it, and maybe they don't.  I'm not taking
  // chances.
  char *cmdline = new char[cmd.length() + 1];
  strcpy(cmdline, cmd.c_str());

  // We should be using CreateProcess() instead of WinExec(), but that
  // seems to be likely to crash Win98.  WinExec() seems better
  // behaved, and it's all we need anyway.
  if (!WinExec(cmdline, 0)) {
#ifndef NDEBUG
    cerr << "Unable to spawn process.\n";
#endif
    close_pipe();
    // Don't return yet, since we still need to clean up.
  }

  delete[] cmdline;
 
  // Now restore our own stdout, up here in the parent process.
  if (!SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout)) {
#ifndef NDEBUG
    cerr << "Unable to restore stdout\n";
#endif
  }

  // Close the write end of the pipe before reading from the 
  // read end of the pipe. 
  if (!CloseHandle(hChildStdoutWr)) {
#ifndef NDEBUG
    cerr << "Unable to close write end of pipe\n";
#endif
  }

  return (_child_out != 0);
}

////////////////////////////////////////////////////////////////////
//     Function: PipeStreamBuf::close_pipe
//       Access: Private
//  Description: Closes the pipe opened previously.
////////////////////////////////////////////////////////////////////
void PipeStreamBuf::
close_pipe() {
  if (_child_out != 0) {
    CloseHandle(_child_out);
    _child_out = 0;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: PipeStreamBuf::write_pipe
//       Access: Private
//  Description: Writes the indicated data out to the child process
//               opened previously.  Returns the number of bytes read.
////////////////////////////////////////////////////////////////////
size_t PipeStreamBuf::
write_pipe(const char *data, size_t len) {
  return 0;
}

////////////////////////////////////////////////////////////////////
//     Function: PipeStreamBuf::read_pipe
//       Access: Private
//  Description: Reads the indicated amount of data from the child
//               process opened previously.  Returns the number of
//               bytes read.
////////////////////////////////////////////////////////////////////
size_t PipeStreamBuf::
read_pipe(char *data, size_t len) {
  if (_child_out == 0) {
    return 0;
  }
  DWORD dwRead; 
  if (!ReadFile(_child_out, data, len, &dwRead, NULL)) {
    close_pipe();
    return 0;
  }

  return dwRead;
}


#endif  // WIN_PIPE_CALLS
