// $Id: OutStream.h 1250 2021-12-14 10:48:13Z ge $
/// \file OutStream
/// \brief creates files on-demand
/// Changes to std::ostream such that a file is only created on first write
/// Includes provision for file compression and file versioning
/// \author Gerald Weber <gweberbh@gmail.com>
/// $Revision: 1250 $
#ifndef OUTSTREAM_H
#define OUTSTREAM_H "$Id: OutStream.h 1250 2021-12-14 10:48:13Z ge $"
#include <fstream>      // std::ifstream, std::ofstream
#include <sstream>
#include <iostream>
#include <iomanip>
#include <boost/regex.hpp>
#include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include "boost/filesystem.hpp"
#include "ErrorCodes.h"

namespace gbc  
{
//https://www.cplusplus.com/reference/ostream/ostream/ostream/
class OutStream: public std::ostream
  {
  public:
    std::string BaseName;
    std::string Extension;
    std::string SuppExtension; //a second extension such as .best in test.par.best
    std::string FormattedName;
    std::string VersionString;
    std::ofstream AuxilaryStream; //necessary for gzip filtering
    unsigned int Version;
    bool IgnoreOpen;
    bool Compression;
    std::list<std::string> AllOpenedFiles; //registers the names of all files
    std::filebuf fb;  //We need a fielbuffer as we are using ostream for compression
    boost::iostreams::filtering_streambuf< boost::iostreams::output> gz_filter;//for gz compression https://www.boost.org/doc/libs/1_46_1/libs/iostreams/doc/classes/filtering_streambuf.html
    
    OutStream(void): std::ostream(&fb), BaseName(), Extension(), SuppExtension(), FormattedName(), VersionString(), Version(), IgnoreOpen(false), Compression(false) {};
    
    OutStream(std::string bname, unsigned int v=0)
      : std::ostream(&fb), BaseName(), Extension(), SuppExtension(), FormattedName(), VersionString(), Version(), IgnoreOpen(false), Compression(false)
      {
      set_basename(bname,v);  
      };
    
    inline void set_basename(std::string bname, unsigned int v=0)
      {
      const boost::regex name_ext("^([^\\|]*)\\|([^\\|]*)(|\\|?(.*))$");
      boost::smatch found;
      
      std::string ext, sup;
      if (boost::regex_search(bname,found,name_ext))
        {
        bname = found[1];
        ext   = found[2];
        if (found.size() > 4) sup = found[4];
        }
      
      if (not bname.empty()) BaseName=bname;
      if (not ext.empty())   Extension=ext;
      if (not sup.empty())   SuppExtension=sup;
      if (v > 0) Version=v;
      build_filename();
      }
      
    inline void clear_name(void)
      {
      BaseName=Extension=SuppExtension=FormattedName=VersionString=std::string();
      Version=0;
      }
    
    inline void build_filename(void)
      {
      FormattedName = BaseName + "." + Extension;
      if (Version > 0)
        {
        std::stringstream ver;
        ver << "." << std::setw(5) << std::setfill('0') << Version;
        VersionString=ver.str();
        FormattedName += VersionString;
        }
      if (not SuppExtension.empty()) FormattedName += "." + SuppExtension;
      if (Compression) FormattedName += ".gz";
      CERR_DEBUG(DOUS_BUILDFL) << " BaseName = \""      << BaseName      << "\" Extension = \"" << Extension 
                                      << "\" SuppExtension = \"" << SuppExtension << "\" VersionString = \"" << VersionString
                                      << "\" FormattedName = \"" << FormattedName << "\"" << std::endl;
      }
      
    inline std::string formatted_name(void) {return FormattedName;}
      
    inline bool stream_is_open(void) const
      {
      if (Compression) return AuxilaryStream.is_open();
      else             return fb.is_open();
      }
    
    inline void stream_open(void)
      {
      if (IgnoreOpen) return;
      build_filename();
      if (Compression)
        {
        gz_filter.reset();
        this->rdbuf(&gz_filter);
        AuxilaryStream.open(FormattedName.c_str(),std::ofstream::binary);
        gz_filter.push(boost::iostreams::gzip_compressor());//needs to be pushed before file
        gz_filter.push(AuxilaryStream);
        }
      else 
        {
        fb.open(FormattedName.c_str(),std::ios::out);
        }
      if (not stream_is_open())
        {
        CERR_IERROR(IERROSNO) << " opening output file \""<< FormattedName << "\" failed"  << std::endl;
        CERR_TERM
        }
      AllOpenedFiles.push_back(FormattedName);
      CERR_DEBUG(DOUS_FLOPN) << " opened file \"" << FormattedName << "\" for writing"  << std::endl;
      }
      
    inline void open(std::string generic_name)
      {
      stream_close(); //just in case
      if (not generic_name.empty()) set_basename(generic_name);
      stream_open();
      }
      
      
    void remove_current(void)
      {
      if (stream_is_open()) stream_close();
      if (boost::filesystem::exists(FormattedName))
        {
        CERR_DEBUG(DOUS_FLDEL) << " deleting file \"" << FormattedName << "\"" << std::endl;
        boost::filesystem::remove(FormattedName);
        }
      else
        {
        CERR_DEBUG(DOUS_FLNEX) << " file \"" << FormattedName << "\" does not exists" << std::endl;
        }
      }

    void remove_all(void)
      {
      if (stream_is_open()) stream_close();
      for(auto& file : AllOpenedFiles)
        if (boost::filesystem::exists(file)) 
          {
          CERR_DEBUG(DOUS_FLDEL) << " deleting file \"" << file << "\"" << std::endl;
          boost::filesystem::remove(file);
          }
      AllOpenedFiles.clear();
      }
  
      
    inline void stream_open_if_not(void)
      {
      if (not stream_is_open()) stream_open();
      }
      
    inline void stream_open_version(unsigned int v)
      {
      Version=v;
      stream_open();
      } 
      
    inline void stream_close(void)
      {
      if (stream_is_open()) 
        {
        CERR_DEBUG(DOUS_FLCLS) << " closed file \"" << FormattedName << "\"" << std::endl;
        if (Compression) boost::iostreams::close(gz_filter);
        fb.close();
        }
      }
      
    inline void stream_restart(void)
      {
      stream_close(); stream_open();
      }
  
    template<class _Tp>
    friend OutStream& operator<<(OutStream& out,const _Tp& tp)
      {
      out.stream_open_if_not(); //here we ensure that the file is opened on first write operation
      std::ostream tmp_out(out.rdbuf());
      tmp_out << tp;
      return out;
      }

    };//end of class OutStream
}
#endif
