/*  Copyright (C) 2004 Garrett A. Kajmowicz
 *
 * This file is part of the uClibc++ Library.
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifndef __STD_HEADER_FSTREAM 
#define __STD_HEADER_FSTREAM 1

#include<basic_definitions>

#include <cstdio>
#include <cstdlib>
#include <streambuf>
#include <istream>
#include <ostream>
#include <char_traits>

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#ifdef __UCLIBCXX_HAS_WCHAR__
#include <cwchar>
#include <cwctype>
#endif //__UCLIBCXX_HAS_WCHAR__

#pragma GCC visibility push(default)

extern "C++"
{
namespace std
{
  template <class C, class T> class basic_filebuf;

  typedef basic_filebuf<char>    filebuf;
#ifdef __UCLIBCXX_HAS_WCHAR__
  typedef basic_filebuf<wchar_t> wfilebuf;
#endif

  template <class charT, class traits> class _UCXXEXPORT basic_filebuf
    : public basic_streambuf<charT,traits>
  {
#ifdef __UCLIBCXX_SUPPORT_CDIR__
    friend ios_base::Init::Init();  // Needed for cout/cin stuff
#endif
  public:
    // Types (inherited from basic_streambuf:

    typedef typename basic_streambuf<charT, traits>::char_type  char_type;
    typedef typename basic_streambuf<charT, traits>::int_type   int_type;
    typedef typename basic_streambuf<charT, traits>::pos_type  pos_type;
    typedef typename basic_streambuf<charT, traits>::off_type  off_type;
    typedef traits              traits_type;

    // Constructors/destructor:

    _UCXXEXPORT basic_filebuf()  : basic_streambuf<charT, traits>(), fp(0), pbuffer(0), gbuffer(0)
    {
      append=false;
      pbuffer = new char_type[CONFIG_UCLIBCXX_IOSTREAM_BUFSIZE];
      gbuffer = new char_type[CONFIG_UCLIBCXX_IOSTREAM_BUFSIZE];

      this->setp(pbuffer, pbuffer + CONFIG_UCLIBCXX_IOSTREAM_BUFSIZE);

      //Position get buffer so that there is no data available

      this->setg(gbuffer, gbuffer + CONFIG_UCLIBCXX_IOSTREAM_BUFSIZE,
        gbuffer + CONFIG_UCLIBCXX_IOSTREAM_BUFSIZE);
    }

    _UCXXEXPORT virtual ~basic_filebuf(){
      sync();
      close();
      delete [] pbuffer;
      delete [] gbuffer;
      pbuffer = 0;
      gbuffer = 0;
    }

    // Members:

    _UCXXEXPORT bool is_open() const
    {
      if (fp == 0)
      {
        return false;
      }
      return true;
    }

    _UCXXEXPORT basic_filebuf<charT,traits>* open(const char* s, ios_base::openmode mode)
    {
      bool move_end = (mode & ios_base::ate) != 0;
      if (is_open() !=false)
      {
        // Must call close() first

        return 0;
      }

      basic_streambuf<charT,traits>::openedFor = mode;
      mode = mode & ~ios_base::ate;

      if (mode == ios_base::out || mode == (ios_base::out | ios_base::trunc))
      {
        fp = fopen(s, "w");
      }
      else if ((mode & ios_base::app) && ! (mode & ios_base::trunc))
      {
        if (mode & ios_base::binary)
        {
          if (mode & ios_base::in)
          {
            fp = fopen(s, "a+b");
          }
          else
          {
            fp = fopen(s, "ab");
          }
        }
        else
        {
          if (mode & ios_base::in)
          {
            fp = fopen(s, "a+");
          }
          else
          {
            fp = fopen(s, "a");
          }
        }
      }
      else if (mode == ios_base::in)
      {
        fp = fopen(s, "r");
      }
      else if (mode == (ios_base::in | ios_base::out))
      {
        fp = fopen(s, "r+");
      }
      else if (mode == (ios_base::in | ios_base::out | ios_base::trunc))
      {
        fp = fopen(s, "w+");
      }
      else if (mode == (ios_base::binary | ios_base::out))
      {
        fp = fopen(s, "wb");
      }
      else if (mode == (ios_base::in | ios_base::binary))
      {
        fp = fopen(s, "rb");
      }
      else if (mode == (ios_base::in | ios_base::binary | ios_base::out))
      {
        fp = fopen(s, "r+b");
      }
      else if (mode==(ios_base::binary | ios_base::out | ios_base::trunc))
      {
        fp = fopen(s, "w+b");
      }
      else if (mode == (ios_base::in | ios_base::binary | ios_base::out | ios_base::trunc))
      {
        fp = fopen(s, "w+b");
      }

      if (fp == 0)
      {
        return 0;
      }

      if (ferror(fp))
      {
        fclose(fp);
        fp = 0;
        return 0;
      }

      int retval = 0;

      // Check to make sure the stream is good

      if (move_end == true)
      {
        retval = fseek(fp, 0, SEEK_END);
      }
      else
      {
        retval = fseek(fp, 0, SEEK_SET);
      }

      if (retval != 0)
      {
        //Seek error

        fclose(fp);
        fp=0;
        return 0;
      }

      basic_streambuf<charT,traits>::mgnext = basic_streambuf<charT,traits>::mgend;

      return this;
    }

    _UCXXEXPORT basic_filebuf<charT,traits>* close()
    {
      if (fp != 0 && fp != stdin && fp != stdout && fp !=stderr)
      {
        overflow();
        sync();
        int retval = fclose(fp);
        if (retval !=0)
        {
          //Error of some sort

          return 0;
        }

        fp=0;
      }

      return this;
    }

  protected:
    _UCXXEXPORT basic_filebuf(const basic_filebuf<charT,traits> &){ }
    _UCXXEXPORT basic_filebuf<charT,traits> & operator=(const basic_filebuf<charT,traits> &){ return *this; }

    //Overridden virtual functions:

    virtual _UCXXEXPORT int showmanyc()
    {
      return basic_streambuf<charT,traits>::egptr() - basic_streambuf<charT,traits>::gptr();
    }

    virtual _UCXXEXPORT int_type underflow()
    {
      /* Some variables used internally:
         Buffer pointers:
         charT * mgbeg;
         charT * mgnext;
         charT * mgend;

         eback() returns mgbeg
         gptr()  returns mgnext
         egptr() returns mgend

         gbump(int n) mgnext+=n
      */

      if (!is_open())
      {
        return traits::eof();
      }

      if (basic_streambuf<charT,traits>::eback() == 0)
      {
        // No buffer, so...

        charT c;
        int retval;
        retval = fread(&c, sizeof(charT), 1, fp);

        if (retval == 0 || feof(fp) || ferror(fp))
        {
          return traits::eof();
        }

        return traits::to_int_type(c);
      }

      if (basic_streambuf<charT,traits>::eback() == basic_streambuf<charT,traits>::gptr())
      {
        // Buffer is full

        return traits::to_int_type(*basic_streambuf<charT,traits>::gptr());
      }

      // Shift entire buffer back to the begining

      size_t offset = basic_streambuf<charT,traits>::gptr() - basic_streambuf<charT,traits>::eback();
      size_t amountData = basic_streambuf<charT,traits>::egptr() - basic_streambuf<charT,traits>::gptr();

      for (charT * i = basic_streambuf<charT,traits>::gptr(); i < basic_streambuf<charT,traits>::egptr(); ++i)
      {
        *(i-offset) = *i;
      }

      size_t retval = 0;

      // Save operating flags from file descriptor

      int fcntl_flags = fcntl(fileno(fp), F_GETFL);
      retval = 0;

      // Set to non_blocking mode

      fcntl(fileno(fp), F_SETFL, fcntl_flags | O_NONBLOCK);

      // Fill rest of buffer

      retval = fread(basic_streambuf<charT,traits>::egptr() - 
        basic_streambuf<charT,traits>::gptr() + basic_streambuf<charT,traits>::eback(),
        sizeof(charT),
        offset,
        fp);

      // Clear problems where we didn't read in enough characters

      if (EAGAIN == errno)
      {
        clearerr(fp);
      }

      // Restore file descriptor clase

      fcntl(fileno(fp), F_SETFL, fcntl_flags);

      // Now we are going to make sure that we read in at least one character.  The hard way.

      if (retval == 0)
      {
        fcntl_flags = fcntl(fileno(fp), F_GETFL);

        // Set to blocking mode

        fcntl(fileno(fp), F_SETFL, fcntl_flags & ~O_NONBLOCK);

        retval = fread(basic_streambuf<charT,traits>::egptr() - 
          basic_streambuf<charT,traits>::gptr() + basic_streambuf<charT,traits>::eback(),
          sizeof(charT),
          1,
          fp);

        // Restore file descriptor clase

        fcntl(fileno(fp), F_SETFL, fcntl_flags);
      }

      if (retval !=offset)
      {
        // Slide buffer forward again

        for (size_t i = 0; i < amountData + retval; ++i)
        {
          *(basic_streambuf<charT,traits>::egptr() - i - 1) =
            *(basic_streambuf<charT,traits>::eback() + amountData + retval - i - 1);
        }
      }

      basic_streambuf<charT,traits>::mgnext -= retval;

      if ((retval <= 0 && feof(fp)) || ferror(fp))
      {
        return traits::eof();
      }

      return traits::to_int_type(*basic_streambuf<charT,traits>::gptr());
    }

    virtual _UCXXEXPORT int_type uflow()
    {
      bool dobump = (basic_streambuf<charT,traits>::gptr() != 0);
      int_type retval = underflow();
      if (dobump)
      {
        basic_streambuf<charT,traits>::gbump(1);
      }

      return retval;
    }

    virtual _UCXXEXPORT int_type pbackfail(int_type c = traits::eof())
    {
      if (is_open() == false || 
        basic_streambuf<charT,traits>::gptr() == basic_streambuf<charT,traits>::eback())
      {
        return traits::eof();
      }

      if (traits::eq_int_type(c,traits::eof()) == false)
      {
        if (traits::eq(traits::to_char_type(c), basic_streambuf<charT,traits>::gptr()[-1]) == true)
        {
          basic_streambuf<charT,traits>::gbump(-1);
        }
        else
        {
          basic_streambuf<charT,traits>::gbump(-1);
          basic_streambuf<charT,traits>::gptr()[0] = c;
        }

        return c;
      }
      else
      {
        basic_streambuf<charT,traits>::gbump(-1);
        return traits::not_eof(c);
      }
    }

    virtual _UCXXEXPORT int_type overflow(int_type c = traits::eof())
    {
      if (is_open() == false)
      {
        // Can't do much

        return traits::eof();
      }

      if (basic_streambuf<charT,traits>::pbase() == 0)
      {
        // Unbuffered - elliminate dupe code below

        if (fputc(c, fp) == EOF)
        {
          return traits::eof();
        }

        return c;
      }

      if (basic_streambuf<charT,traits>::pbase() == 0 && traits::eq_int_type(c,traits::eof()))
      {
        // Nothing to flush

        return traits::not_eof(c);
      }

      size_t r = basic_streambuf<charT,traits>::pptr() - basic_streambuf<charT,traits>::pbase();

      if (r == 0 && traits::eq_int_type(c,traits::eof()))
      {
        return traits::not_eof(c);
      }
      else if (r == 0)
      {
        if (fputc(c, fp) == EOF)
        {
          return traits::eof();
        }

        return c;
      }

      size_t totalChars = r;

      char_type *buffer = 0;
      if (traits::eq_int_type(c,traits::eof()))
      {
        buffer = new char_type[r];
      }
      else
      {
        buffer = new char_type[r+1];
        buffer[r] = c;
        totalChars++;
      }

      traits::copy(buffer, basic_streambuf<charT,traits>::pbase(), r);
//    memcpy(buffer, basic_streambuf<charT,traits>::pbase(), r);

      size_t retval = fwrite(buffer, sizeof(charT), totalChars, fp);
      if (retval !=totalChars)
      {
        if (retval == 0)
        {
          delete [] buffer;
          return traits::eof();
        }

        basic_streambuf<charT,traits>::pbump(-retval);
        fprintf(stderr, "***** Did not write the full buffer out.  Should be: %d, actually: %d\n",
           totalChars, retval);
      }
      else
      {
        basic_streambuf<charT,traits>::pbump(-r);
      }

      delete [] buffer;
      return traits::not_eof(c);
    }

    virtual _UCXXEXPORT basic_streambuf<charT,traits>* setbuf(char_type* s, streamsize n)
    {
      if (s == 0 && n == 0)
      {
        // Unbuffered

        if (pbuffer !=0)
        {
          delete [] pbuffer;
        }

        if (gbuffer !=0)
        {
          delete [] gbuffer;
        }

        pbuffer = 0;
        gbuffer = 0;
      }
      else if (basic_streambuf<charT,traits>::gptr() !=0 && 
        basic_streambuf<charT,traits>::gptr()==basic_streambuf<charT,traits>::egptr())
      {
        delete [] pbuffer;
        pbuffer = s;
      }

      return this;
    }

    virtual _UCXXEXPORT pos_type seekoff(off_type off, ios_base::seekdir way, 
      ios_base::openmode = ios_base::in | ios_base::out)
    {
      if (is_open() == false)
      {
        return -1;
      }

      int whence = SEEK_SET;  // if (way == basic_ios<charT>::beg)
      off_type position = off;

      if (way == basic_ios<charT>::cur)
      {
        whence = SEEK_CUR;
        position -= (basic_streambuf<charT,traits>::egptr() - basic_streambuf<charT,traits>::gptr());
      }
      else if (way == basic_ios<charT>::end)
      {
        whence = SEEK_END;
      }

      sync();

      int retval = fseek(
        fp,
        sizeof(charT)*(position),
        whence);

      // Invalidate read buffer

      basic_streambuf<charT,traits>::gbump(
        basic_streambuf<charT,traits>::egptr() - basic_streambuf<charT,traits>::gptr());

      if (-1 != retval)
      {
        retval = ftell(fp);
      }

      return retval;
    }

    virtual _UCXXEXPORT pos_type seekpos(pos_type sp, ios_base::openmode = ios_base::in | ios_base::out)
    {
      if (is_open() == false)
      {
        return -1;
      }

      sync();

      int retval = fseek(fp,sizeof(charT)* sp, SEEK_SET);

      // Invalidate read buffer

      basic_streambuf<charT,traits>::gbump(basic_streambuf<charT,traits>::egptr() - basic_streambuf<charT,traits>::gptr());
      if (retval > -1)
      {
        return sp;
      }

      return -1;
    }

    virtual _UCXXEXPORT int sync()
    {
      if (pbuffer !=0)
      {
        if (overflow() == traits::eof())
        {
          return -1;
        }
      }

      if (0 != fp && 0 != fflush(fp))
      {
        return -1;
      }

      return 0;
    }

    virtual _UCXXEXPORT void imbue(const locale&)
    {
      return;
    }

    virtual _UCXXEXPORT streamsize xsputn(const char_type* s, streamsize n)
    {
      if (is_open() == false)
      {
        return 0;
      }

      // Check to see if buffered

      // Check to see if we can buffer the data

      streamsize buffer_avail = basic_streambuf<charT,traits>::epptr() - basic_streambuf<charT,traits>::pptr();

      if (n > buffer_avail)
      {
        // Flush buffer and write directly

        overflow();  // Flush the buffer
        return fwrite(s, sizeof(charT), n, fp);
      }

      // Add to buffer to be written later

      traits::copy(basic_streambuf<charT,traits>::pptr(), s, n);
      basic_streambuf<charT,traits>::pbump(n);

      return n;
    }

    FILE * fp;
    char_type * pbuffer;
    char_type * gbuffer;
    bool append;
  };

#ifdef __UCLIBCXX_HAS_WCHAR__

template <> _UCXXEXPORT basic_filebuf<wchar_t, char_traits<wchar_t> >::int_type
  basic_filebuf<wchar_t, char_traits<wchar_t> >::overflow(int_type c);

template <> _UCXXEXPORT basic_filebuf<wchar_t, char_traits<wchar_t> >::int_type
  basic_filebuf<wchar_t, char_traits<wchar_t> >::underflow();

#endif //__UCLIBCXX_HAS_WCHAR__

#ifdef __UCLIBCXX_EXPAND_FSTREAM_CHAR__
#ifndef __UCLIBCXX_COMPILE_FSTREAM__

#ifdef __UCLIBCXX_EXPAND_CONSTRUCTORS_DESTRUCTORS__

  template <> _UCXXEXPORT filebuf::basic_filebuf();
  template <> _UCXXEXPORT filebuf::~basic_filebuf();

#endif // __UCLIBCXX_EXPAND_CONSTRUCTORS_DESTRUCTORS__

  template <> _UCXXEXPORT filebuf::int_type filebuf::pbackfail(filebuf::int_type c);
  template <> _UCXXEXPORT filebuf * filebuf::open(const char* s, ios_base::openmode mode);
  template <> _UCXXEXPORT filebuf * filebuf::close();
  template <> _UCXXEXPORT filebuf::int_type filebuf::overflow(filebuf::int_type c);
  template <> _UCXXEXPORT filebuf::int_type filebuf::underflow ();

  template <> _UCXXEXPORT basic_streambuf<char, char_traits<char> > * filebuf::setbuf(char * s, streamsize n);
  template <> _UCXXEXPORT streamsize filebuf::xsputn(const char* s, streamsize n);

#endif
#endif

  template <class charT, class traits> class _UCXXEXPORT basic_ifstream
    : public basic_istream<charT,traits>
  {
  public:
    typedef charT char_type;
    typedef typename traits::int_type int_type;
    typedef typename traits::pos_type pos_type;
    typedef typename traits::off_type off_type;

    _UCXXEXPORT basic_ifstream(): basic_ios<charT, traits>(&sb), basic_istream<charT,traits>(&sb){
      //Passing the address of sb
    }

    explicit _UCXXEXPORT basic_ifstream(const char* s, ios_base::openmode mode = ios_base::in)
      : basic_ios<charT, traits>(&sb), basic_istream<charT,traits>(&sb)
    {
      if (sb.open(s, mode) == 0)
      {
        basic_ios<charT,traits>::setstate(ios_base::failbit);
      }
    }

    virtual _UCXXEXPORT ~basic_ifstream()
    {
    }

    _UCXXEXPORT basic_filebuf<charT,traits>* rdbuf() const
    {
      return (basic_filebuf<charT,traits>*)&sb;
    }

    _UCXXEXPORT bool is_open() const
    {
      return sb.is_open();
    }

    _UCXXEXPORT void open(const char* s, ios_base::openmode mode = ios_base::in)
    {
      if (sb.open(s, mode) == 0)
      {
        basic_ios<charT,traits>::setstate(ios_base::failbit);
      }
    }

    _UCXXEXPORT void close()
    {
      sb.close();
    }

  private:
    basic_filebuf<charT,traits> sb;
  };

  template <class charT, class traits> class _UCXXEXPORT basic_ofstream
    : public basic_ostream<charT,traits>
  {
  public:
    typedef charT char_type;
    typedef typename traits::int_type int_type;
    typedef typename traits::pos_type pos_type;
    typedef typename traits::off_type off_type;

    _UCXXEXPORT basic_ofstream() : basic_ios<charT, traits>(&sb), basic_ostream<charT,traits>(&sb)
    {
    }

    virtual _UCXXEXPORT ~basic_ofstream();

    explicit _UCXXEXPORT basic_ofstream(const char* s, ios_base::openmode mode = ios_base::out | ios_base::trunc) :
      basic_ios<charT, traits>(&sb), basic_ostream<charT,traits>(&sb)
    {
      if (sb.open(s, mode | ios_base::out) == 0)
      {
        basic_ios<charT,traits>::setstate(ios_base::failbit);
      }
    }

    _UCXXEXPORT basic_filebuf<charT,traits>* rdbuf() const
    {
      return (basic_filebuf<charT,traits>*)&sb;
    }

    _UCXXEXPORT bool is_open() const
    {
      return sb.is_open();
    }

    _UCXXEXPORT void open(const char* s, ios_base::openmode mode = ios_base::out | ios_base::trunc)
    {
      if (sb.open(s, mode) == 0)
      {
        basic_ios<charT,traits>::setstate(ios_base::failbit);
      }
    }

    _UCXXEXPORT void close()
    {
      sb.close();
    }

  private:
    basic_filebuf<charT,traits> sb;
  };

  template <class charT, class traits> _UCXXEXPORT basic_ofstream<charT, traits>::~basic_ofstream()
  {
    basic_ostream<charT, traits>::flush();
  }

  template <class charT, class traits> class _UCXXEXPORT basic_fstream
    : public basic_iostream<charT,traits>
  {
  public:
    typedef charT char_type;
    typedef typename traits::int_type ins_type;
    typedef typename traits::pos_type pos_type;
    typedef typename traits::off_type off_type;

    _UCXXEXPORT basic_fstream(): basic_ios<charT, traits>(&sb), basic_iostream<charT,traits>(&sb){ }

    explicit _UCXXEXPORT basic_fstream(const char* s, ios_base::openmode mode = ios_base::in|ios_base::out):
      basic_ios<charT, traits>(&sb), basic_iostream<charT,traits>(&sb)
    {
      if (sb.open(s, mode) == 0)
      {
        basic_ios<charT,traits>::setstate(ios_base::failbit);
      }
    }

    _UCXXEXPORT basic_filebuf<charT,traits>* rdbuf() const
    {
      return (basic_filebuf<charT,traits>*)&sb;
    }

    _UCXXEXPORT bool is_open() const
    {
      return sb.is_open();
    }

    _UCXXEXPORT void open(const char* s, ios_base::openmode mode = ios_base::in|ios_base::out)
    {
      if (sb.open(s, mode) == 0)
      {
        basic_ios<charT,traits>::setstate(ios_base::failbit);
      }
    }

    _UCXXEXPORT void close()
    {
      sb.close();
    }

  private:
    basic_filebuf<charT,traits> sb;
  };

#ifdef __UCLIBCXX_EXPAND_FSTREAM_CHAR__
#ifndef __UCLIBCXX_COMPILE_FSTREAM__

#ifdef __UCLIBCXX_EXPAND_CONSTRUCTORS_DESTRUCTORS__

  template <> _UCXXEXPORT basic_ofstream<char, char_traits<char> >::basic_ofstream();
  template <> _UCXXEXPORT basic_ofstream<char, char_traits<char> >::basic_ofstream(const char* s, ios_base::openmode mode);
  template <> _UCXXEXPORT basic_ofstream<char, char_traits<char> >::~basic_ofstream();

  template <> _UCXXEXPORT basic_ifstream<char, char_traits<char> >::basic_ifstream();
  template <> _UCXXEXPORT basic_ifstream<char, char_traits<char> >::basic_ifstream(const char* s, ios_base::openmode mode);
  template <> _UCXXEXPORT basic_ifstream<char, char_traits<char> >::~basic_ifstream();

#endif // __UCLIBCXX_EXPAND_CONSTRUCTORS_DESTRUCTORS__

#endif
#endif

} // namespace
} // exteren "C++"

#pragma GCC visibility pop

#endif
