#include "AftrConfig.h"
#include <fstream>
#ifdef AFTR_CONFIG_USE_BOOST
#include <boost/interprocess/streams/bufferstream.hpp>
#include <boost/timer/timer.hpp>
#include "NetMessengerStreamBuffer.h"

template< Aftr::SensorDatumConcept T >
std::vector<T> Aftr::SensorDatumFileIO::fromFile( const std::string& fileName )
{
   std::vector< T > d;
   std::ifstream fin;
   size_t linesProcessed = 0;

   fin.open( fileName, std::ios::in | std::ios::binary );
   if( fin )
   {
      boost::timer::auto_cpu_timer t{}; //time how long loading takes
      fin.seekg( 0, std::ios::end );
      std::streamoff len = fin.tellg(); //length in bytes
      fin.seekg( 0, std::ios::beg );

      len = len + std::streamoff( 1 ); //add one extra byte for null terminator
      char* contents = new char[len]; //ownership is given to the boost::interprocess::ibufferstream
      contents[len-1] = '\0'; //null terminate very last byte to ensure very last ascii text number is fully read before eof is encountered by stream
      fin.read( contents, len ); //ownership is given to the boost::interprocess::ibufferstream

      size_t numDataPoints = std::count( &contents[0], &contents[0] + len, '\n' );
      d.reserve( numDataPoints + 1 ); //in case last line doesn't have a newline we reserve one extra spot

      //now let's turn this back into a stream that gets used to create each datum
      boost::interprocess::ibufferstream finTxt( contents, len ); /// OWNERSHIP is transferred to the ibufferstream
      finTxt.clear(); //reset the file stream's state so it can operate on the new char buffer already in memory
      finTxt.seekg( 0, std::ios::beg );

      std::cout << "Loading Datum from " << fileName << "...\n";
      while( !finTxt.eof() && finTxt.peek() != std::char_traits<wchar_t>::eof() )
      {
         std::streampos pos = finTxt.tellg(); //save current position within stream
         if( pos >= len )
            break; //we reached the end of the file

         if( auto datum = T::fromStreamOneDatum( finTxt ); datum )
            d.push_back( *datum );
         else
         {
            //we are at the end of last successfully parsed line (or beginning of a file w/ an invalid first line).
            //We find the next \n and begin processing from there. We have to do that without walking past the end of file.
            //So let's use the original char* buffer and march until the next newline, seekg to that newline and begin processing the next line from there
            long long idx = pos;
            while( idx < len-1 )
            {
               if( contents[idx] == '\n' ) { ++idx; break; }
               ++idx;
            }
            if( idx == len - 1 )
               break; //we've reached the end of the file. We exit here and we don't have to tell the user that this datum "failed" since EOF isn't a datum to parse!

            finTxt.clear(); //clear out the error flags in the stream
            finTxt.seekg( idx ); //seek to the next \n and process from there

            //notify the user that a particular datum wasn't successfully processed
            std::cout << "   WARNING: " << AFTR_FILE_LINE_STR << "...\n"
               << "      Couldn't parse a datum around byte offset " << pos << " (approx " << static_cast<float>( pos ) / static_cast<float>( len ) << " through data set)...\n"
               << "      Continuing with next datum...\n";
         }
         ++linesProcessed;
         if( linesProcessed % 1000 == 0 )
            std::cout << ".";
         if( linesProcessed % 100000 == 0 )
            std::cout << linesProcessed;

         //std::cout << "Peek is " << (int)finTxt.peek() << "\n";
      }
      std::cout << "Loaded " << fileName << " Parsed " << d.size() << " datum across " << linesProcessed << " lines...\n"
                << "   Time taken:   ";
   }
   else
   {
      std::cout << "Failed to open file " << fileName << ", 0 datum loaded...\n";
   }
   return std::move( d );
}

template< Aftr::SensorDatumConcept T >
Aftr::NetMessengerStreamBuffer Aftr::SensorDatumFileIO::fromFileAsNetBuffer( const std::string& fileName )
{
   std::ifstream fin;
   fin.open( fileName, std::ios::in | std::ios::binary );
   if( fin )
   {
      boost::timer::auto_cpu_timer t{}; //time how long loading takes
      fin.seekg( 0, std::ios::end );
      std::streamoff len = fin.tellg(); //length in bytes
      fin.seekg( 0, std::ios::beg );

      char* contents = new char[len]; //ownership is given to the boost::interprocess::ibufferstream
      fin.read( contents, len ); //ownership is given to the NetMessengerStreamBuffer

      Aftr::NetMessengerStreamBuffer buff( contents, static_cast<unsigned int>( len ), true );
      
      std::cout << "Loaded " << fileName << "   Time taken:   ";
      buff.enableHostNetworkEndianAutoConversion( false ); //disable endian auto conversion
      return std::move( buff );
   }
   else
   {
      std::cout << "Failed to open file " << fileName << ", 0 datum loaded...\n";
   }
   return std::move( Aftr::NetMessengerStreamBuffer( nullptr, 0, true ) );
}

template< Aftr::SensorDatumConcept T>
bool Aftr::SensorDatumFileIO::toFile( const std::string& fileName, const std::vector<T>& dat )
{
   std::ofstream fout;
   fout.open( fileName.c_str(), std::ios::app );

   if( fout )
   {
      for( size_t i = 0; i < dat.size(); ++i )
         dat[i].toStreamOneDatum( fout ); //each datum should end with a newline inside its insertion (toStream()... method).
   }
   else
   {
      std::cout << "Failed to open file " << fileName << " to append output of " << dat.size() << " datums...\n";
      return false;
   }

   return false;
}

#endif
