/*
 *  fdstream.h
 *
 *  This file is part of NEST.
 *
 *  Copyright (C) 2004 The NEST Initiative
 *
 *  NEST is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  NEST 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with NEST.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef FDSTREAM_H
#define FDSTREAM_H

/** @file fdstream.h
 *  An implementation of C++ filestreams that supports an interface to
 *  the file descriptor. 
 */

#include "config.h"
#ifndef HAVE_ISTREAM
#include <iostream>
#include <fstream>

typedef std::ifstream ifdstream;
typedef std::ofstream ofdstream;
typedef std::iostream fdstream;
#else

#include <unistd.h>
#include <streambuf>
#include <istream>
#include <ostream>
#include <string>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <iostream>
#include <fstream>
#include <cassert>

#include <cstdio> // for the parallel FILE* workaround.

// The Compaq C++ compiler V6.5-014 surpresses all non standard
// names if compilation is performed with -std strict_ansi.
// However, fdopen() is POSIX and guaranteed to be available in
// <stdio.h>.
// The flag __PURE_CNAME is specific for the Compaq compiler.
// We need to add a workaround flag HAVE_FDOPEN_IGNORED here.
// 24.4.03, Diesmann
//
// Compaq C++ V6.5-040 required fdopen without :: here.  In fdstream.cc,
// ::fdopen is fine then. 
// 28.6.04, Plesser
#ifdef __PURE_CNAME
  extern "C" std::FILE* fdopen(int , const char *);
#endif


class fdbuf: public std::streambuf
{
  static std::streamsize const s_bufsiz=1024;
  
public:
  
  fdbuf():
    m_fd(-1), m_isopen(false)
  { 
    setp(m_outbuf, m_outbuf + s_bufsiz);
  }

  fdbuf(int fd):
    m_fd(fd), m_isopen(true)
  {
    setp(m_outbuf, m_outbuf + s_bufsiz);
  }
  
  ~fdbuf()
  {
    //sync();
    close();
  }


  bool is_open() const
  {
    return m_isopen;
  }

  fdbuf* open(const char*, std::ios_base::openmode);

  fdbuf* close();

  /** Return the underlying file descriptor.
   *  Now this is why we are doing all this!!
   */
  int fd()
  {
    return m_fd;
  }
  
protected:
  
  int_type underflow()
  {
    if (gptr() == egptr())
      {
        int size = ::read(m_fd, m_inbuf, s_bufsiz);
        if (size < 1)
          return traits_type::eof();
        setg(m_inbuf, m_inbuf, m_inbuf + size);
      }
    return traits_type::to_int_type(*gptr());
  }
  
  int_type overflow(int_type c)
  {
    if (sync() == -1)
      {
	// std::cerr<<"sync failed!"<<std::endl;
	return traits_type::eof();}
    
    if (!traits_type::eq_int_type(c, traits_type::eof()))
      {
        *pptr() = traits_type::to_char_type(c);
        pbump(1);
        return c;
      } 
    return traits_type::not_eof(c);
  }
  
  int sync()
  {
    std::streamsize size = pptr() - pbase();
    if (size > 0 && ::write(m_fd, m_outbuf, size) != size)
      return -1;
    setp(m_outbuf, m_outbuf + s_bufsiz);
    return 0;
  }

private:

  int  m_fd;
  bool m_isopen;
  char m_inbuf[s_bufsiz];
  char m_outbuf[s_bufsiz];

};




class ofdstream: public std::ostream
{
public:
  
  /**
   * @note In this an all other constructors, we initialize the stream with
   *       a 0-pointer to a stream buffer first and then set the pointer through
   *       a call to rdbuf() in the constructor body. This is necessary because
   *       the stream buffer sb is constructed after the stream object, which
   *       is inherited from the base class. This causes problems at least when
   *       using the PGI compiler.
   */

  ofdstream():
  std::ostream(0), sb()
  {
    std::ostream::rdbuf(&sb);
    init(&sb);
  }

  explicit ofdstream(const char* s, std::ios_base::openmode mode = std::ios_base::out):
  std::ostream(0), sb()
  {
    std::ostream::rdbuf(&sb);
    init(&sb);
    assert(good());
    open(s, mode);
  }

  explicit ofdstream(int fd):
  std::ostream(0), sb(fd)
  {
    std::ostream::rdbuf(&sb);
    init(&sb);
  }

  fdbuf* rdbuf() const
  {
    return const_cast<fdbuf*>(&sb); // return type is non-const, member is const, by C++ specs!
  }

  bool is_open()
  {
    return rdbuf()->is_open();
  }

  void open(const char* s, std::ios_base::openmode mode = std::ios_base::out)
  {
    if (rdbuf()->open(s, mode|std::ios_base::out) == NULL) 
      {
        setstate(failbit);
      }
  }
  
  void close();

private:
  
  fdbuf sb;
};

class ifdstream: public std::istream
{
public:
  
  ifdstream():
    std::istream(0), sb()
  {
    std::istream::rdbuf(&sb);
    init(&sb);
  }

  explicit ifdstream(const char* s, std::ios_base::openmode mode = std::ios_base::in):
  std::istream(0), sb()
  {
    std::istream::rdbuf(&sb);
    init(&sb);
    open(s, mode);
  }

  explicit ifdstream(int fd):
  std::istream(0), sb(fd)
  {
    std::istream::rdbuf(&sb);
    init(&sb);
  }


  fdbuf* rdbuf() const
  {
    return const_cast<fdbuf*>(&sb); // return type is non-const, member is const, by C++ specs!
  }

  bool is_open()
  {
    return rdbuf()->is_open();
  }

  void open(const char* s, std::ios_base::openmode mode = std::ios_base::in)
  {
    if (rdbuf()->open(s, mode|std::ios_base::in) == NULL) 
      {
        setstate(failbit);
      }
  }
  
  void close();
  
private:
  
  fdbuf sb;
};

class fdstream: public std::iostream
{
public:
  
  fdstream():
    std::iostream(0), sb() // See Constructor comment above.
  {
    std::iostream::rdbuf(&sb);
    init(&sb); // See Constructor comment above.
  }

  explicit fdstream(const char* s, std::ios_base::openmode mode):
    std::iostream(0), sb() // See Constructor comment above.
  {
    std::iostream::rdbuf(&sb);
    init(&sb); // See Constructor comment above.
    open(s, mode);
  }

  explicit fdstream(int fd): // See Constructor comment above.
    std::iostream(0), sb(fd)
  {    
    std::iostream::rdbuf(&sb);
    init(&sb); // See Constructor comment above.
  }


  fdbuf* rdbuf() const
  {
    return const_cast<fdbuf*>(&sb); // return type is non-const, member is const, by C++ specs!
  }

  bool is_open()
  {
    return rdbuf()->is_open();
  }

  void open(const char* s, std::ios_base::openmode mode)
  {
    if (rdbuf()->open(s, mode) == NULL) 
      {
        setstate(failbit);
      }
  }
  
  void close();

private:
  
  fdbuf sb;
};
#endif

#endif