//
// bufin.cpp ("buffered input")
//
// Written by John A deLaubenfels, https://www.adaptune.com/steg.htm .
// Permission granted to use freely in any non-fraudulent way.
//
//
// purpose:
//    provide text input w/ 1-line look-ahead.
//
// details:
//    constructor takes a file-name; opens the file.
//    get_line() returns cp to line; no worries about length.
//       (null returned at eof).
//       // new call to get_line() invalidates prev cp returned.  // out 03-08-05
//       performance specification: no extra copies.
//    next_line() returns cp to line without advancing.
//       (null returned at eof).
//       calling next_line does NOT invalidate the prev get_line()
//          result!  We specify that we want this feature!
//       performance specification: no extra copies.
//    destructor closes the file.
//
//
// qxl: disable copy constructor?
// how can a constructor indicate failure by returning NULL?
//
// words:
//    each call to get_line() pre-fetches the following line, so that
//    a call to next_line() merely returns a pointer already known.
//
//    // To avoid extra copying, we alternate reading into two buffers
//    // in more fixed location (but still allocated): cp_a and cp_b
//
//    // When get_line() is called, we re-point cp_main to where cp_next
//    // previously pointed (it may be cp_a or cp_b); cp_next points to
//    // where cp_main previously pointed, and this value is now
//    // OVERWRITTEN by the next line from the file.
//
//    // Stinky: what if we need to call lengthen()?  It makes sense to
//    // lengthen both cp_a and cp_b to the same values.  Then, we can
//    // just arbitrarily assign cp_main to cp_a and cp_next to cp_b...
//
//    What happens at the end?  We set cp_next to NULL when we first
//    encounter EOF; cp_main will point to the previously SUCCESSFULLY
//    read last line that was in cp_next.  The next time we come to
//    get_line(), uhhh, we swap the pointers...: now cp_main points to
//    NULL and cp_next points to a stale line; we can simply say, if
//    the swap causes cp_main to be set to null, just set cp_next to
//    NULL as well...
//
//
//
//
//

// apparently we CAN'T have any precompiler directives above this:

#ifdef NEED_PCH
   #include "pch.h"
#else
   #include "stdafx.h"
#endif // !NEED_PCH




#include <sys/stat.h>
#include <vector>
#include "bufin.h"


#ifndef _WIN32
#define _stat64 stat64
#endif // !_WIN32




// constructor:
Bufin::Bufin(const char *file_to_read, int trimTrSp, int nullReplaceChar) {
   // int i;
   char *cp;
   const char *cpc;
   struct _stat64  stbu64;    // for big file sizes (>2^31).

   // init stuff to NULL:
   m_file_name = NULL;
   cp_a = cp_b = cp_main = cp_next = NULL;
   m_fp = NULL;

   if(file_to_read == NULL)
      return;  // caller should find via call to is_active()...

   if(_stat64(file_to_read, &stbu64) == -1) {
      return;  // caller should find via call to is_active()...
   }
   if((stbu64.st_mode & S_IFMT) != S_IFDIR) {
      // qprintf(fp_dbg, "%s is NOT a directory.\n", fileToRead);
   } else {
      return;  // caller should find via call to is_active()...
   }

   long long totLen = (long long)stbu64.st_size;

   cp_a = new char[totLen+1];

   // we open "rb" to explicitly discard cr's:
#ifdef _WIN32   
   if(fopen_s(&m_fp, file_to_read, "rb") != 0) {
#else   
   if((m_fp = fopen(file_to_read, "rb")) == NULL) {   
#endif // !_WIN32   
      // jdl 07-31-97: init stuff to NULL:
      delete [] cp_a;  cp_a = NULL;
      return;  // caller should find via call to is_active()...
   }

   long long ll;
   if((ll = fread((void *)(cp_a), sizeof(char), totLen, m_fp)) != totLen) {
      fclose(m_fp);    m_fp = NULL;
      delete [] cp_a;  cp_a = NULL;
      return;
   } // if((i = fread((void *)(&char1), sizeof(char), totLen, m_fp)) != totLen)

   // all done reading, so:
   fclose(m_fp); m_fp=NULL;

   // 11-09-05 (jdl): possibly replace null char(s):
   if(nullReplaceChar < 0) {
      // default: no replacement, and don't even bother counting them:
      m_nNull = -1;    // signal in case anyone calls nNull()...
   } else {
      m_nNull = 0;
      for(ll=0, cp=cp_a; ll<totLen; ll++, cp++) {
         // if(cp_a[ll] == '\0') {
         if((*cp)  == '\0') {
            m_nNull++;
            // cp_a[ll] = (char)nullReplaceChar;
            *cp = (char)nullReplaceChar;
         } // if(cp_a[ll] == '\0')
      } // for(ll=0; ll<totLen; ll++)
   } // !if(nullReplaceChar < 0)
   // 11-09-05 (jdl)...

   cp_endd= cp_a + totLen;
   *cp_endd = '\0';            // works because we allocated one extra byte.

   m_file_name = new char[strlen(file_to_read) + 1];
#ifdef _WIN32   
   sprintf_s(m_file_name, (strlen(file_to_read) + 1), "%s", file_to_read);
#else
   sprintf(m_file_name, "%s", file_to_read);
#endif   

   m_do_fclose = 1;
   m_trimTrSp  = trimTrSp;
   m_alloc_len = totLen;

   // a starting point:
   cp_main = cp_next = NULL;
   cp_edge = cp_a;

   // special code for an empty file: DON'T show one blank line:
   if(totLen == 0) cp_edge = NULL;

   // get the first "next" line:
   getLineOld();

   // 12-18-09: get pointers into the vector for operator[]:
   m_nlines = 0;
   while((cpc=getLineOld()) != NULL) {
      m_vecOfLines.push_back(cpc);
      m_nlines++;
   } // while((cpc=get_line()) != NULL)

   m_il = 0;
   // 12-18-09...

} // end of Bufin::Bufin(const char *file_to_read)








// destructor:
Bufin::~Bufin() {

   bfclose();
} // end of Bufin::~Bufin()





// qxis_active()

int Bufin::is_active() const {
   return (cp_a != NULL);
}






// qxbfclose()

int Bufin::bfclose() {

   // closit();

   if(cp_a) {delete [] cp_a; cp_a = NULL;}
   if(cp_b) {delete [] cp_b; cp_b = NULL;}
   if(m_file_name) {delete [] m_file_name; m_file_name = NULL;}
   cp_main = cp_next = NULL;

   return 0;
} // end of Bufin::bfclose()







//
// new 10-09-02
//
//
//
//
//
//
// qxsetTrSp()
//

int Bufin::setTrSp(int trimTrSp) {

   if(trimTrSp == 0) {
      m_trimTrSp = 0;
   } else {
      m_trimTrSp = 1;
   } // !if(trimTrSp == 0)

   return 0;
} // end of setTrSp()...






//
//
//
//
// 12-27-08 (jdl): make return value const.
//
// Bufin::getLineOld()
//

const char *Bufin::getLineOld() {
   int n_this = 0;
   int done   = 0;          // done with this line
   char *cp_walk;

   if(!is_active()) return NULL;

   int doTrim = this->m_trimTrSp;


   cp_main = cp_next;
   cp_next = cp_edge;

   if(cp_next == NULL) return cp_main;  // no more searching to do!

   cp_walk = cp_next;
	char *cp_first_sp = cp_walk;
   while(1) {
      if(cp_walk == cp_endd) {
         cp_edge = NULL;               // no more searching.
         break;
      } // if(cp_walk == cp_endd)

      switch(*cp_walk) {
         case CRR:
            // change cr to string terminator:
            *cp_walk = '\0';
            break;
         case LF:
            // change lf to string terminator:
            *cp_walk = '\0';
            done = 1;
            cp_edge = cp_walk + 1;     // the beginning of the next string to return.
            if(cp_edge == cp_endd) cp_edge = NULL;
            break;
         case ' ':
            break;
         default:
            cp_first_sp = cp_walk + 1;
            break;
      } // switch(*cp_walk)

      if(done) break;

      cp_walk++;
   } // while(1)

   if(doTrim) *(cp_first_sp) = '\0';

   return cp_main;
} // end of char *Bufin::getLineOld()




// qxget_line()

const char *Bufin::get_line() {
   if(!is_active()) return NULL;

   const char *cpc = (*this)[m_il];
   m_il++;

   return cpc;
} // end of Bufin::get_line()...




//
//
//
//
//
//
// qxnext_line()
//

const char *Bufin::next_line() {
   if(!is_active()) return NULL;

   const char *cpc = (*this)[m_il];

   return cpc;
} // end of Bufin::next_line()...





// qxoperator[]()

const char *Bufin::operator[](int iline) const {
   if(!is_active())      return NULL;
   if(iline < 0)         return NULL;
   if(iline >= m_nlines) return NULL;

   const char *cpc = m_vecOfLines[iline];

   return cpc;
} // end of Bufin::operator[]()...




// qxfillVcStr()

int Bufin::fillVcStr(std::vector<std::string> &vcFillMe) {
   vcFillMe.clear();

   for(int i=0; i<m_nlines; i++) {
      vcFillMe.push_back(m_vecOfLines[i]);
   } // for(int i=0; i<m_nlines; i++)

   return m_nlines;
} // int Bufin::fillVcStr(std::vector<std::string> &vcFillMe)





