#ifndef _PYFSTREAM_H
#define _PYFSTREAM_H

#include <istream>
#include <ostream>
#include <streambuf>
#include <cstdio>
#include <cstring>

#include "Python.h"

// pyofstream
// - a stream that writes on a Python file object

class pyoutbuf : public std::streambuf {
 protected:
  PyFileObject * fo;    // Python file object
 public:
  // constructor
  pyoutbuf (PyFileObject * _fo) : fo(_fo) {}

 protected:
  // write one character
  virtual int_type overflow (int_type c) {
    if (c != EOF) {
      char z[2];
      z[0] = c;
      z[1] = '\0';
      if (PyFile_WriteString(z, (PyObject *)fo) < 0) {
	return EOF;
      }
    }
    return c;
  }

  // write multiple characters
  virtual std::streamsize xsputn (const char* s, std::streamsize num) {
    char * buf = new char[num + 1];
    std::strncpy(buf, s, num);
    buf[num] = '\0';
    if (PyFile_WriteString(buf, (PyObject *)fo) < 0)
      num = 0;
    delete[] buf;
    return num;
  }
};

class pyofstream : public std::ostream {
 protected:
  pyoutbuf buf;
 public:
  pyofstream (PyFileObject * fo) : std::ostream(0), buf(fo) {
    rdbuf(&buf);
  }
};

// pyifstream
// - a stream that reads on a file descriptor

class pyinbuf : public std::streambuf {
 protected:
  PyFileObject * fo;    // Python file object
 protected:
  /* data buffer:
   * - at most, pbSize characters in putback area plus
   * - at most, bufSize characters in ordinary read buffer
   */
  static const int pbSize  = 4;	   // size of putback area
  static const int bufSize = 1024; // size of the data buffer
  char buffer[bufSize + pbSize];   // data buffer

 public:
  /* constructor
   * - initialize file descriptor
   * - initialize empty data buffer
   * - no putback area
   * => force underflow()
   */
  pyinbuf (PyFileObject * _fo) : fo(_fo) {
    setg (buffer+pbSize,     // beginning of putback area
	  buffer+pbSize,     // read position
	  buffer+pbSize);    // end position
  }

 protected:
  // insert new characters into the buffer
  virtual int_type underflow () {
#ifndef _MSC_VER
    using std::memmove;
#endif

    // is read position before end of buffer?
    if (gptr() < egptr()) {
      return traits_type::to_int_type(*gptr());
    }

    /* process size of putback area
     * - use number of characters read
     * - but at most size of putback area
     */
    int numPutback;
    numPutback = gptr() - eback();
    if (numPutback > pbSize) {
      numPutback = pbSize;
    }

    /* copy up to pbSize characters previously read into
     * the putback area
     */
    memmove (buffer+(pbSize-numPutback), gptr()-numPutback,
	     numPutback);

    // read at most bufSize new characters
    int num;
    PyObject *line = PyFile_GetLine((PyObject *)fo, bufSize);
    if (! line || ! PyString_Check(line)) {
      // ERROR or EOF
      return EOF;
    }

    num = PyString_Size(line);
    if (num == 0)
      return EOF;

    memmove (buffer+pbSize, PyString_AsString(line), num);

    // reset buffer pointers
    setg (buffer+(pbSize-numPutback),   // beginning of putback area
	  buffer+pbSize,                // read position
	  buffer+pbSize+num);           // end of buffer

    // return next character
    return traits_type::to_int_type(*gptr());
  }
};

class pyifstream : public std::istream {
 protected:
  pyinbuf buf;
 public:
  pyifstream (PyFileObject * fo) : std::istream(0), buf(fo) {
    rdbuf(&buf);
  }
};

#endif // _PYFSTREAM_H