/******
* fileio.cc
* Tom Prince and John Bowman 2004/08/10
*
* Handle input/output
******/

#include "fileio.h"
#include "settings.h"

#if !defined(_WIN32)
#define _fdopen fdopen
#endif

namespace camp {

FILE *pipeout=NULL;

string tab="\t";
string newline="\n";

ofile Stdout("");
file nullfile("",false,NOMODE,false,true);

void openpipeout()
{
 int fd=intcast(settings::getSetting<Int>("outpipe"));
 if(!pipeout && fd >= 0) pipeout=_fdopen(fd,"w");
 if(!pipeout) {
   cerr << "Cannot open outpipe " << fd << endl;
   exit(-1);
 }
}

string locatefile(string name)
{
 string s=settings::locateFile(name,false,"");
 return s.empty() ? name : s;
}

bool file::Standard()
{
 return standard;
}

void file::standardEOF()
{
#if defined(HAVE_LIBREADLINE) && defined(HAVE_LIBCURSES)
 cout << endl;
#endif
}

void file::purgeStandard(string&)
{
 if(cin.eof())
   standardEOF();
}

void file::dimension(Int Nx, Int Ny, Int Nz)
{
 if(Nx < -2 || Ny < -2 || Nz < -2) {
   ostringstream buf;
   buf << "Invalid array dimensions: " << Nx << ", " << Ny << ", " << Nz;
   reportError(buf);
 }

 nx=Nx; ny=Ny; nz=Nz;
}

file::file(string const& name, bool check, Mode type, bool binary, bool closed) :
   name(name), check(check), type(type), linemode(false), csvmode(false),
   wordmode(false), singlereal(false), singleint(true), signedint(true),
   closed(closed), standard(name.empty()), binary(binary), nullfield(false)
{
 whitespace="";
 dimension();
}

void file::Check()
{
 if(error()) {
   ostringstream buf;
   buf << "Cannot open file \"" << name << "\"";
   reportError(buf);
 }
}

file::~file()
{

}

bool file::isOpen()
{
 if(closed) {
   ostringstream buf;
   buf << "I/O operation attempted on ";
   if(name != "") buf << "closed file \'" << name << "\'";
   else buf << "null file";
   reportError(buf);
 }
 return true;
}
void file::unsupported(char const* rw, char const* type)
{
 ostringstream buf;
 buf << rw << " of type " << type << " not supported in " << FileMode()
     << " mode";
 reportError(buf);
}

void ifile::open()
{
 if(standard) {
   if(mode & std::ios::binary)
     reportError("Cannot open standard input in binary mode");
   stream=&cin;
 } else {
   if(mode & std::ios::out)
     name=outpath(name);
   if(mode & std::ios::in) {
#ifdef HAVE_LIBCURL
     if(parser::isURL(name)) {
       parser::readURL(buf,name);
       stream=&buf;
     } else
#endif
     {
       name=locatefile(inpath(name));
       stream=fstream=new std::fstream(name.c_str(),mode);
     }
   }

   if(mode & std::ios::out) {
     if(error()) {
       delete fstream;
       std::ofstream f(name.c_str());
       f.close();
       stream=fstream=new std::fstream(name.c_str(),mode);
     }
   }
   index=processData().ifile.add(fstream);
   if(check) Check();
 }
}

void ifile::ignoreComment()
{
 if(comment == 0) return;
 int c=stream->peek();
 bool eol=c == '\n';
 if(csvmode && eol) {nullfield=true; return;}
 if(csvmode && c == ',') nullfield=true;
 for(;;) {
   while(isspace(c=stream->peek())) {
     stream->ignore();
     whitespace += (char) c;
   }
   if(c == comment) {
     whitespace="";
     while((c=stream->peek()) != '\n' && c != EOF)
       stream->ignore();
     if(c == '\n')
       stream->ignore();
   } else {if(c != EOF && eol) stream->unget(); return;}
 }
}

void ifile::Read(double& val) {
 char c;
 std::string str;
 bool neg;

 while(isspace(c=stream->peek()))
   stream->ignore();
 neg=stream->peek() == '-';
 // Try parsing the input as a number.
 if(*stream >> val)
   return;

 clear();

 switch(stream->peek()) {
   case 'I': case 'i': // inf
   case 'N': case 'n': // NaN
     for(Int i=0; i < 3 && stream->good(); i++)
       str += stream->get();
     break;
   default:
     stream->setstate(std::ios_base::failbit);
     return;
 }

 if(strcasecmp(str.c_str(),"inf") == 0)
   val=std::numeric_limits < double > ::infinity();
 else if(strcasecmp(str.c_str(),"nan") == 0)
   val=std::numeric_limits < double > ::quiet_NaN();
 else {
   for(auto it=str.rbegin(); it != str.rend(); ++it)
     stream->putback(*it);
   stream->setstate(std::ios_base::failbit);
   return;
 }

 if(neg)
   val=-val;
}

bool ifile::eol()
{
 int c;
 while(isspace(c=stream->peek())) {
   if(c == '\n') return true;
   else {
     stream->ignore();
     whitespace += (char) c;
   }
 }
 return false;
}

bool ifile::nexteol()
{
 int c;
 if(nullfield) {
   nullfield=false;
   return true;
 }

 while(isspace(c=stream->peek())) {
   if(c == '\n' && comma) {
     nullfield=true;
     return false;
   }
   stream->ignore();
   if(c == '\n') {
     while(isspace(c=stream->peek())) {
       if(c == '\n') {nullfield=true; return true;}
       else {
         stream->ignore();
         whitespace += (char) c;
       }
     }
     return true;
   }
   else whitespace += (char) c;
 }
 return false;
}

void ifile::csv()
{
 comma=false;
 nullfield=false;
 if(!csvmode || stream->eof()) return;
 std::ios::iostate rdstate=stream->rdstate();
 if(stream->fail()) stream->clear();
 int c=stream->peek();
 if(c == ',') stream->ignore();
 else if(c == '\n') {
   stream->ignore();
   if(linemode && stream->peek() != EOF) stream->unget();
 } else stream->clear(rdstate);
 if(c == ',') comma=true;
}

void ifile::Read(string& val)
{
 string s;
 if(wordmode) {
   whitespace="";
   while(isspace(stream->peek())) stream->ignore();
 }
 if(csvmode || wordmode) {
   bool quote=false;
   while(stream->good()) {
     int c=stream->peek();
     if(c == '"') {quote=!quote; stream->ignore(); continue;}
     if(!quote) {
       if(comment && c == comment) {
         while((c=stream->peek()) != '\n' && c != EOF)
           stream->ignore();
         if(wordmode && !linemode)
           while(isspace(stream->peek())) stream->ignore();
         if(stream->peek() == '"') {quote=!quote; stream->ignore(); continue;}
         if(s.empty() && c == '\n') {
           stream->ignore();
           continue;
         }
       }
       if(csvmode && (c == ',' || c == '\n'))
         break;
       if(wordmode && isspace(c)) {
         if(!linemode) while(isspace(stream->peek())) stream->ignore();
         break;
       }
     }
     s += (char) stream->get();
   }
 } else
   getline(*stream,s);

 if(comment) {
   size_t p=0;
   while((p=s.find(comment,p)) < string::npos) {
     if(p+1 < s.length() && s[p+1] == comment) {
       s.erase(p,1);
       ++p;
     } else {
       s.erase(p);
       break;
     }
   }
 }
 size_t n=s.length();
 if(n > 0) {
   size_t pos=n-1;
   if(s[pos] == '\r') s.erase(pos,1);
 }
 val=whitespace+s;
}

void ofile::writeline()
{
 if(standard && interact::query && !vm::indebugger) {
   Int scroll=settings::getScroll();
   if(scroll && interact::lines > 0 && interact::lines % scroll == 0) {
     for(;;) {
       if(!cin.good()) {
         *stream << newline;
         cin.clear();
         break;
       }
       int c=cin.get();
       if(c == '\n') break;
       // Discard any additional characters
       while(cin.good() && cin.get() != '\n');
       if(c == 's') {interact::query=false; break;}
       if(c == 'q') {interact::query=false; interact::lines=0; throw quit();}
     }
   } else *stream << newline;
   ++interact::lines;
 } else *stream << newline;
 if(errorstream::interrupt) {interact::lines=0; throw interrupted();}
}

void ofile::open()
{
 if(standard) {
   if(mode & std::ios::binary)
     reportError("Cannot open standard output in binary mode");
   stream=&cout;
 } else {
   name=outpath(name);
   stream=fstream=new std::ofstream(name.c_str(),mode | std::ios::trunc);
   stream->precision(settings::getSetting<Int>("digits"));
   index=processData().ofile.add(fstream);
   Check();
 }
}

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

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

void ofile::seek(Int pos, bool begin)
{
 if(!standard && fstream) {
   clear();
   fstream->seekp(pos,begin ? std::ios::beg : std::ios::end);
 }
}
size_t ofile::tell()
{
 if(fstream)
   return fstream->tellp();
 else
   return 0;
}

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

void opipe::write(const string& val) {
 if (fprintf(pipeout,"%s",val.c_str()) < 0)
 {
   reportError("Write failed to pipe");
 }
}

void opipe::flush()
{
 if(pipeout)
 {
   if (fflush(pipeout) == EOF)
   {
     reportError("Flushing pipe failed");
   }
 }
}

void iofile::writeline()
{
 *fstream << newline;
 if(errorstream::interrupt) throw interrupted();
}

#ifdef HAVE_LIBTIRPC

void igzxfile::open()
{
 name=locatefile(inpath(name));
 gzfile=gzopen(name.c_str(),"rb");
 Check();

 while(!gzeof(gzfile)) {
   std::vector<char> tmpBuf(readSize);
   auto filSz = gzread(gzfile,tmpBuf.data(),readSize);
   std::copy(tmpBuf.begin(),tmpBuf.begin()+filSz,std::back_inserter(readData));
 }
 gzclose(gzfile);

 fstream=new xdr::memixstream(readData);
 index=processData().ixfile.add(fstream);
}

void igzxfile::closeFile()
{
 if(fstream) {
   fstream->close();
   closed=true;
   delete fstream;
   processData().ixfile.remove(index);
 }
}
#endif

} // namespace camp