/******
* fileio.h
* Tom Prince and John Bowman 2004/05/10
*
* Handle input/output
******/

#ifndef FILEIO_H
#define FILEIO_H

#include <fstream>
#include <iostream>
#include <sstream>
#include <zlib.h>

#include "common.h"

#ifdef HAVE_LIBTIRPC
#include "xstream.h"
#endif

#include "pair.h"
#include "triple.h"
#include "guide.h"
#include "pen.h"

#include "camperror.h"
#include "interact.h"
#include "errormsg.h"
#include "util.h"
#include "asyprocess.h"
#include "locate.h"
#include "asyparser.h"

namespace vm {
extern bool indebugger;
}

namespace camp {

extern string tab;
extern string newline;

enum Mode {NOMODE,INPUT,OUTPUT,UPDATE,BINPUT,BOUTPUT,BUPDATE,XINPUT,XINPUTGZ,
          XOUTPUT,XUPDATE,OPIPE};

static const string FileModes[]=
{"none","input","output","output(update)",
"input(binary)","output(binary)","output(binary,update)",
"input(xdr)","input(xdrgz)","output(xdr)","output(xdr,update)","output(pipe)"};

extern FILE *pipeout;

void openpipeout();
string locatefile(string name);

class file : public gc {
protected:
 string name;
 bool check;      // Check whether input file exists.
 Mode type;

 Int nx,ny,nz;    // Array dimensions
 bool linemode;   // Array reads will stop at eol instead of eof.
 bool csvmode;    // Read comma-separated values.
 bool wordmode;   // Delimit strings by white space instead of eol.
 bool singlereal; // Read/write single-precision XDR/binary reals.
 bool singleint;  // Read/write single-precision XDR/binary ints.
 bool signedint;  // Read/write signed XDR/binary ints.

 bool closed;     // File has been closed.
 bool standard;   // Standard input/output
 bool binary;     // Read in binary mode.

 bool nullfield;  // Used to detect a null field in cvs mode.
 string whitespace;
 size_t index;    // Terminator index.

public:

 bool Standard();
 bool enabled() {return !standard || settings::verbose > 1 ||
     interact::interactive || !settings::getSetting<bool>("quiet");}

 void standardEOF();

 template<class T>
 void purgeStandard(T&) {
   if(standard) {
     int c;
     if(cin.eof())
       standardEOF();
     else {
       cin.clear();
       while((c=cin.peek()) != EOF) {
         cin.ignore();
         if(c == '\n') break;
       }
     }
   }
 }

 void purgeStandard(string&);

 void dimension(Int Nx=-1, Int Ny=-1, Int Nz=-1);

 file(const string& name, bool check=true, Mode type=NOMODE,
      bool binary=false, bool closed=false);

 virtual bool isBinary() {return false;}

 virtual bool isXDR() {return false;}

 virtual void open() {}

 void Check();

 virtual ~file();

 bool isOpen();

 string filename() {return name;}
 virtual bool eol() {return false;}
 virtual bool nexteol() {return false;}
 virtual bool text() {return false;}
 virtual bool eof() {return true;}
 virtual bool error() {return true;}
 virtual void close() {}
 virtual void clear() {}
 virtual Int precision(Int) {return 0;}
 virtual void flush() {}
 virtual size_t tell() {return 0;}
 virtual void seek(Int, bool=true) {}

 string FileMode() {return FileModes[type];}

 void unsupported(const char *rw, const char *type);

 void noread(const char *type) {unsupported("Read",type);}
 void nowrite(const char *type) {unsupported("Write",type);}

 virtual void Read(bool&) {noread("bool");}
 virtual void Read(Int&) {noread("int");}
 virtual void Read(double&) {noread("real");}
 virtual void Read(float&) {noread("real");}
 virtual void Read(pair&) {noread("pair");}
 virtual void Read(triple&) {noread("triple");}
 virtual void Read(char&) {noread("char");}
 virtual void Read(string&) {noread("string");}
 virtual void readwhite(string&) {noread("string");}

 virtual void write(bool) {nowrite("bool");}
 virtual void write(char) {nowrite("char");}
 virtual void write(Int) {nowrite("int");}
 virtual void write(double) {nowrite("real");}
 virtual void write(const pair&) {nowrite("pair");}
 virtual void write(const triple&) {nowrite("triple");}
 virtual void write(const string&) {nowrite("string");}
 virtual void write(const pen&) {nowrite("pen");}
 virtual void write(guide *) {nowrite("guide");}
 virtual void write(const transform&) {nowrite("transform");}
 virtual void writeline() {nowrite("string");}

 virtual void ignoreComment() {};
 virtual void csv() {};

 template<class T>
 void ignoreComment(T&) {
   ignoreComment();
 }

 void ignoreComment(string&) {}
 void ignoreComment(char&) {}

 template<class T>
 void setDefault(T& val) {
   val=T();
 }

#if COMPACT
 void setDefault(Int& val) {
   val=vm::Undefined;
 }
#endif

 template<class T>
 void read(T& val) {
   if(binary) Read(val);
   else {
     if(standard) clear();
     if(errorstream::interrupt) throw interrupted();
     else {
       ignoreComment(val);
       setDefault(val);
       if(!nullfield)
         Read(val);
       csv();
       whitespace="";
     }
   }
 }

 Int Nx() {return nx;}
 Int Ny() {return ny;}
 Int Nz() {return nz;}

 void Nx(Int n) {nx=n;}
 void Ny(Int n) {ny=n;}
 void Nz(Int n) {nz=n;}

 void LineMode(bool b) {linemode=b;}
 bool LineMode() {return linemode;}

 void CSVMode(bool b) {csvmode=b; if(b) wordmode=false;}
 bool CSVMode() {return csvmode;}

 void WordMode(bool b) {wordmode=b; if(b) csvmode=false;}
 bool WordMode() {return wordmode;}

 void SingleReal(bool b) {singlereal=b;}
 bool SingleReal() {return singlereal;}

 void SingleInt(bool b) {singleint=b;}
 bool SingleInt() {return singleint;}

 void SignedInt(bool b) {signedint=b;}
 bool SignedInt() {return signedint;}
};

class opipe : public file {
public:
 opipe(const string& name) : file(name,false,OPIPE) {standard=false;}

 void open() {
   openpipeout();
 }

 bool text() {return true;}
 bool eof() {return pipeout ? feof(pipeout) : true;}
 bool error() {return pipeout ? ferror(pipeout) : true;}
 void clear() {if(pipeout) clearerr(pipeout);}
 void flush();

 void seek(Int pos, bool begin=true) {
   if(!standard && pipeout) {
     clear();
     fseek(pipeout,pos,begin ? SEEK_SET : SEEK_END);
   }
 }

 size_t tell() {
   return pipeout ? ftell(pipeout) : 0;
 }

 void write(const string& val);

 void write(bool val) {
   ostringstream s;
   s << val;
   write(s.str());
 }

 void write(Int val) {
   ostringstream s;
   s << val;
   write(s.str());
 }
 void write(double val) {
   ostringstream s;
   s << val;
   write(s.str());
 }
 void write(const pair& val) {
   ostringstream s;
   s << val;
   write(s.str());
 }
 void write(const triple& val) {
   ostringstream s;
   s << val;
   write(s.str());
 }

 void write(const pen &val) {
   ostringstream s;
   s << val;
   write(s.str());
 }

 void write(guide *val) {
   ostringstream s;
   s << *val;
   write(s.str());
 }

 void write(const transform& val) {
   ostringstream s;
   s << val;
   write(s.str());
 }

 void writeline() {
   fprintf(pipeout,"\n");
   if(errorstream::interrupt) throw interrupted();
 }
};

class ifile : public file {
protected:
 istream *stream;
 std::fstream *fstream;
 stringstream buf;
 char comment;
 std::ios::openmode mode;
 bool comma;

public:
 ifile(const string& name, char comment, bool check=true, Mode type=INPUT,
       std::ios::openmode mode=std::ios::in) :
   file(name,check,type), stream(&cin), fstream(NULL),
   comment(comment), mode(mode), comma(false) {}

 // Binary file
 ifile(const string& name, bool check=true, Mode type=BINPUT,
       std::ios::openmode mode=std::ios::in) :
   file(name,check,type,true), mode(mode) {}

 ~ifile() {close();}

 void open();
 bool eol();
 bool nexteol();

 bool text() {return true;}
 bool eof() {return stream->eof();}
 bool error() {return stream->fail();}

 void close() {
   if(!standard && fstream) {
     fstream->close();
     closed=true;
     delete fstream;
     fstream=NULL;
     processData().ifile.remove(index);
   }
 }

 void clear() {stream->clear();}

 void seek(Int pos, bool begin=true) {
   if(!standard && fstream) {
     clear();
     fstream->seekg(pos,begin ? std::ios::beg : std::ios::end);
   }
 }

 size_t tell() {
   if(fstream)
     return fstream->tellg();
   else
     return 0;
 }

 void csv();

 virtual void ignoreComment();

 // Skip over white space
 void readwhite(string& val) {val=string(); *stream >> val;}

 void Read(bool &val) {string t; readwhite(t); val=(t == "true");}
 void Read(Int& val) {*stream >> val;}
 void Read(double& val);
 void Read(pair& val) {*stream >> val;}
 void Read(triple& val) {*stream >> val;}
 void Read(char& val) {stream->get(val);}
 void Read(string& val);
};

class iofile : public ifile {
public:
 iofile(const string& name, char comment=0) :
   ifile(name,comment,true,UPDATE,std::ios::in | std::ios::out) {}

 Int precision(Int p) {
   return p == 0 ? stream->precision(settings::getSetting<Int>("digits")) :
     stream->precision(p);
 }
 void flush() {if(fstream) fstream->flush();}

 void write(bool val) {*fstream << (val ? "true " : "false ");}
 void write(Int val) {*fstream << val;}
 void write(double val) {*fstream << val;}
 void write(const pair& val) {*fstream << val;}
 void write(const triple& val) {*fstream << val;}
 void write(const string& val) {*fstream << val;}
 void write(const pen& val) {*fstream << val;}
 void write(guide *val) {*fstream << *val;}
 void write(const transform& val) {*fstream << val;}

 void writeline();
};

class ofile : public file {
protected:
 ostream *stream;
 std::ofstream *fstream;
 std::ios::openmode mode;
public:
 ofile(const string& name, Mode type=OUTPUT,
       std::ios::openmode mode=std::ios::trunc) :
   file(name,true,type), stream(&cout), fstream(NULL), mode(mode) {}

 ~ofile() {close();}

 void open();

 bool text() {return true;}
 bool eof() {return stream->eof();}
 bool error() {return stream->fail();}

 void close();
 void clear() {stream->clear();}
 Int precision(Int p);
 void flush() {stream->flush();}

 void seek(Int pos, bool begin=true);

 size_t tell();

 bool enabled();

 void write(bool val) {*stream << (val ? "true " : "false ");}
 void write(Int val) {*stream << val;}
 void write(double val) {*stream << val;}
 void write(const pair& val) {*stream << val;}
 void write(const triple& val) {*stream << val;}
 void write(const string& val) {*stream << val;}
 void write(const pen& val) {*stream << val;}
 void write(guide *val) {*stream << *val;}
 void write(const transform& val) {*stream << val;}

 void writeline();
};

class ibfile : public ifile {
public:
 ibfile(const string& name, bool check=true, Mode type=BINPUT,
        std::ios::openmode mode=std::ios::in) :
   ifile(name,check,type,mode | std::ios::binary) {}

 bool isBinary() {return true;}

 template<class T>
 void iread(T& val) {
   val=T();
   if(fstream) fstream->read((char *) &val,sizeof(T));
 }

 void Read(bool& val) {iread(val);}
 void Read(Int& val) {
   if(signedint) {
     if(singleint) {int ival; iread(ival); val=ival;}
     else iread(val);
   } else {
     if(singleint) {unsigned ival; iread(ival); val=Intcast(ival);}
     else {unsignedInt ival; iread(ival); val=Intcast(ival);}
   }
 }
 void Read(char& val) {iread(val);}
 void Read(string& val) {
   size_t n=0;
   if(wordmode)
     iread(n);
   else
     n=SIZE_MAX;
   string s;
   for(size_t i=0; i < n; ++i) {
     char c;
     Read(c);
     if(eof() || error())
       return;
     s += c;
   }
   val=s;
 }

 void Read(double& val) {
   if(singlereal) {float fval; iread(fval); val=fval;}
   else iread(val);
 }
};

class iobfile : public ibfile {
public:
 iobfile(const string& name) :
   ibfile(name,true,BUPDATE,std::ios::in | std::ios::out) {}

 bool isBinary() {return true;}

 void flush() {if(fstream) fstream->flush();}

 template<class T>
 void iwrite(T val) {
   if(fstream) fstream->write((char *) &val,sizeof(T));
 }

 void write(bool val) {iwrite(val);}
 void write(Int val) {
   if(signedint) {
     if(singleint) iwrite(intcast(val));
     else iwrite(val);
   } else {
     if(singleint) iwrite(unsignedcast(val));
     else iwrite(unsignedIntcast(val));
   }
 }
 void write(const string& val) {
   size_t n=val.size();
   if(wordmode)
     iwrite(n);
   for(size_t i=0; i < n; ++i)
     fstream->write((char *) &val[i],1);
 }
 void write(const pen& val) {iwrite(val);}
 void write(guide *val) {iwrite(val);}
 void write(const transform& val) {iwrite(val);}
 void write(double val) {
   if(singlereal) iwrite((float) val);
   else iwrite(val);
 }
 void write(const pair& val) {
   write(val.getx());
   write(val.gety());
 }
 void write(const triple& val) {
   write(val.getx());
   write(val.gety());
   write(val.getz());
 }
 void writeline() {}
};

class obfile : public ofile {
public:
 obfile(const string& name) : ofile(name,BOUTPUT,std::ios::binary) {}

 bool isBinary() {return true;}

 template<class T>
 void iwrite(T val) {
   if(fstream) fstream->write((char *) &val,sizeof(T));
 }

 void write(bool val) {iwrite(val);}
 void write(Int val) {
   if(signedint) {
     if(singleint) iwrite(intcast(val));
     else iwrite(val);
   } else {
     if(singleint) iwrite(unsignedcast(val));
     else iwrite(unsignedIntcast(val));
   }
 }
 void write(const string& val) {
   size_t n=val.size();
   if(wordmode)
     iwrite(n);
   for(size_t i=0; i < n; ++i)
     fstream->write((char *) &val[i],1);
 }
 void write(const pen& val) {iwrite(val);}
 void write(guide *val) {iwrite(val);}
 void write(const transform& val) {iwrite(val);}
 void write(double val) {
   if(singlereal) iwrite((float) val);
   else iwrite(val);
 }
 void write(const pair& val) {
   write(val.getx());
   write(val.gety());
 }
 void write(const triple& val) {
   write(val.getx());
   write(val.gety());
   write(val.getz());
 }

 void writeline() {}
};

#ifdef HAVE_LIBTIRPC

class ixfile : public file {
protected:
 xdr::ixstream *fstream;
 xdr::xios::open_mode mode;
public:
 ixfile(const string& name, bool check=true, Mode type=XINPUT,
        xdr::xios::open_mode mode=xdr::xios::in) :
   file(name,check,type,true), fstream(NULL), mode(mode) {}

 bool isXDR() override {return true;}

 void open() override {
   name=locatefile(inpath(name));
   fstream=new xdr::ixstream(name.c_str(),mode);
   index=processData().ixfile.add(fstream);
   if(check) Check();
 }

 void close() override {
   if(fstream) {
     fstream->close();
     closed=true;
     delete fstream;
     fstream=NULL;
     processData().ixfile.remove(index);
   }
 }

 ~ixfile() {close();}

 bool eof() override {return fstream ? fstream->eof() : true;}
 bool error() override {return fstream ? fstream->fail() : true;}

 void clear() override {if(fstream) fstream->clear();}

 void seek(Int pos, bool begin=true) override {
   if(!standard && fstream) {
     clear();
     fstream->seek(pos,begin ? xdr::xios::beg : xdr::xios::end);
   }
 }

 size_t tell() override {
   if(fstream)
     return fstream->tell();
   else
     return 0;
 }

 void Read(char& val) override {
   xdr::xbyte b;
   *fstream >> b;
   val=b;
 }

 void Read(string& val) override {
   size_t n=0;
   if(wordmode)
     *fstream >> n;
   else
     n=SIZE_MAX;
   val="";
   string s;
   for(size_t i=0; i < n; ++i) {
     char c;
     Read(c);
     if(eof() || error())
       return;
     s += c;
   }
   val=s;
 }

 void Read(Int& val) override {
   if(signedint) {
     if(singleint) {int ival=0; *fstream >> ival; val=ival;}
     else {val=0; *fstream >> val;}
   } else {
     if(singleint) {unsigned ival=0; *fstream >> ival; val=Intcast(ival);}
     else {unsignedInt ival=0; *fstream >> ival; val=Intcast(ival);}
   }
 }
 void Read(double& val) override {
   if(singlereal) {float fval=0.0; *fstream >> fval; val=fval;}
   else {
     val=0.0;
     *fstream >> val;
   }
 }
 void Read(pair& val) override {
   double x,y;
   Read(x);
   Read(y);
   val=pair(x,y);
 }
 void Read(triple& val) override {
   double x,y,z;
   Read(x);
   Read(y);
   Read(z);
   val=triple(x,y,z);
 }
};

class igzxfile : public ixfile {
protected:
 std::vector<char> readData;
 size_t const readSize;
 gzFile gzfile;
public:
 igzxfile(const string& name, bool check=true, Mode type=XINPUT,
        xdr::xios::open_mode mode=xdr::xios::in, size_t readSize=32768) :
        ixfile(name,check,type,mode), readSize(readSize){}

 bool error() override {return !gzfile;}

 void open() override;

 void close() override {
   closeFile();
 }

 ~igzxfile() override {closeFile();}


protected:
 void closeFile();
};

class ioxfile : public ixfile {
public:
 ioxfile(const string& name) :
   ixfile(outpath(name),true,XUPDATE,xdr::xios::out) {}

  void open() override {
   name=locatefile(inpath(name));
   ioxfstreamRef=new xdr::ioxstream(name.c_str(),mode);
   fstream=static_cast<xdr::ixstream*>(ioxfstreamRef);
   index=processData().ixfile.add(fstream);
   if(check) Check();
 }

 void flush() override {if(fstream) ioxfstreamRef->flush();}

 void write(const string& val) override {
   size_t n=val.size();
   if(wordmode)
     *ioxfstreamRef << n;
   for(size_t i=0; i < n; ++i)
     *ioxfstreamRef << (xdr::xbyte) val[i];
 }

 void write(Int val) override {
   if(signedint) {
     if(singleint) *ioxfstreamRef << intcast(val);
     else *ioxfstreamRef << val;
   } else {
     if(singleint) *ioxfstreamRef << unsignedcast(val);
     else *ioxfstreamRef << unsignedIntcast(val);
   }
 }
 void write(double val) override {
   if(singlereal) *ioxfstreamRef << (float) val;
   else *ioxfstreamRef << val;
 }
 void write(const pair& val) override {
   write(val.getx());
   write(val.gety());
 }
 void write(const triple& val) override {
   write(val.getx());
   write(val.gety());
   write(val.getz());
 }

private:
 xdr::ioxstream* ioxfstreamRef;
};

class oxfile : public file {
 xdr::oxstream *fstream;
public:
 oxfile(const string& name) : file(name,true,XOUTPUT), fstream(NULL) {}

 bool isXDR() override {return true;}

 void open() override {
   fstream=new xdr::oxstream(outpath(name).c_str(),xdr::xios::trunc);
   index=processData().oxfile.add(fstream);
   Check();
 }

 void close() override {
   if(fstream) {
     fstream->close();
     closed=true;
     delete fstream;
     fstream=NULL;
     processData().oxfile.remove(index);
   }
 }

 ~oxfile() {close();}

 bool eof() override {return fstream ? fstream->eof() : true;}
 bool error() override {return fstream ? fstream->fail() : true;}
 void clear() override {if(fstream) fstream->clear();}
 void flush() override {if(fstream) fstream->flush();}

 void seek(Int pos, bool begin=true) override {
   if(!standard && fstream) {
     clear();
     fstream->seek(pos,begin ? xdr::xios::beg : xdr::xios::end);
   }
 }

 size_t tell() override {
   if(fstream)
     return fstream->tell();
   else
     return 0;
 }

 void write(const string& val) override {
   size_t n=val.size();
   if(wordmode)
     *fstream << n;
   for(size_t i=0; i < n; ++i)
     *fstream << (xdr::xbyte) val[i];
 }

 void write(Int val) override {
   if(signedint) {
     if(singleint) *fstream << intcast(val);
     else *fstream << val;
   } else {
     if(singleint) *fstream << unsignedcast(val);
     else *fstream << unsignedIntcast(val);
   }
 }
 void write(double val) override {
   if(singlereal) *fstream << (float) val;
   else *fstream << val;
 }
 void write(const pair& val) override {
   write(val.getx());
   write(val.gety());
 }
 void write(const triple& val) override {
   write(val.getx());
   write(val.gety());
   write(val.getz());
 }
};

#endif

extern ofile Stdout;
extern file nullfile;

} // namespace camp

#endif // FILEIO_H