//============================================================================
//
//  This file is part of GPSTk, the GPS Toolkit.
//
//  The GPSTk is free software; you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published
//  by the Free Software Foundation; either version 3.0 of the License, or
//  any later version.
//
//  The GPSTk is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with GPSTk; if not, write to the Free Software Foundation,
//  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
//  
//  Copyright 2004, The University of Texas at Austin
//
//============================================================================

//============================================================================
//
//This software developed by Applied Research Laboratories at the University of
//Texas at Austin, under contract to an agency or agencies within the U.S. 
//Department of Defense. The U.S. Government retains all rights to use,
//duplicate, distribute, disclose, or release this software. 
//
//Pursuant to DoD Directive 523024 
//
// DISTRIBUTION STATEMENT A: This software has been approved for public 
//                           release, distribution is unlimited.
//
//=============================================================================

/**
 * @file RTFileFrame.hpp
 * Realtime file frame
 */

#ifndef GPSTK_RTFILEFRAME_HPP
#define GPSTK_RTFILEFRAME_HPP

#include <sys/types.h>
#include <sys/stat.h>
#include <iterator>
#include "FileSpec.hpp"
#include "FileFilter.hpp"

namespace gpstk
{
      /// @ingroup FileDirProc
      //@{

      /// specifying internal enums and classes of a template class can be 
      /// problematic, so these are declared outside RTFileFrame
   class RTFileFrameHelper
   {
   public:
      enum GetRecordMode
      {
         Dumb  = 0, ///< only tries to read once from current file
         Smart = 1  ///< if cannot read, rolls files or waits for data
      };
      
         /**
          * Whether additional file data can be read from the end of a file
          * or not.  
          */
      enum FileReadingMode
      {
         AppendedData = 0,     ///< read only appended data.
         FromTheBeginning = 1  ///< reread from the beginning every time.
      };
   };

      // forward declaration of the RTFileFrame class
   template <class FileStream, class FileData>
   class RTFileFrame;

      /**
       * An iterator class for the RTFileFrame.  Note how much it looks
       * like std::istream_iterator except for a couple of differences..
       */
   template <class FileStream, class FileData>
   class RTFileFrameIterator
   {
   public:
         /// default constructor
      RTFileFrameIterator() 
            : rtff(NULL)
      {}

         /// copy constructor
      RTFileFrameIterator(const RTFileFrameIterator& right)
            : rtff(right.rtff)
      {}

         /// destructor
      virtual ~RTFileFrameIterator() {}

         /// assignment operator
      RTFileFrameIterator& operator=(const RTFileFrameIterator& r)
      { 
         rtff = r.rtff; 
         return *this; 
      }

         /// prefix increment of internal iterator
      RTFileFrameIterator operator++()
      {
         rtff->getRecord();
         return *this;
      }

         /// postfix increment of internal iterator
      RTFileFrameIterator operator++(int)
      {
         RTFileFrameIterator<FileStream, FileData> temp = *this;
         read();
         return temp;
      }

         /// dereference object for current object
      const FileData& operator*() const
      {
         return rtff->lastData;
      }

         /// dereference pointer for current object
      const FileData* operator->() const
      {
         return &(rtff->lastData);
      }
      
         /// equality operator
      bool operator==(const RTFileFrameIterator& right) const
      {
         if (rtff == right.rtff)
         {
            if (rtff == NULL)
               return true;
               
            if ((rtff->isOK == right.rtff->isOK) ||
                (!rtff->isOK && (right.rtff->fileStream == rtff->fileStream)))
               return true;
            return false;
         }
         else if ((rtff == NULL) && (right.rtff))
            return !right.rtff->isOK;
         else if ((right.rtff == NULL) && (rtff))
            return !rtff->isOK;
         else
            return false;
      }

         /// inequality operator
      bool operator!=(const RTFileFrameIterator& right) const
      { return !(*this == right);}

         /// give RTFileFrame access to the 
      friend class RTFileFrame<FileStream, FileData>;
      
   protected:
         /// gets the next record
      void read() 
      { rtff->getRecord(); }

   private:
         /// constructor for use by gpstk::RTFileFrame<FileStream, FileData>.
      RTFileFrameIterator(gpstk::RTFileFrame<FileStream, FileData>& r)
            : rtff(&r)
      { rtff->getRecord(); }

         /// gives access to the RTFileFrame being used
      gpstk::RTFileFrame<FileStream, FileData>* rtff;
   };

      /**
       * This class supports two iteration semantics.
       * @code
       * RTFileFrameIterator<SMODFStream, SMODFData> itr = rtf.begin();
       * while(itr != rtf.end())
       * {
       *       // do something
       *    cout << *itr << endl;
       *    itr++;
       * }
       * rtf.waitForData(10);
       * @endcode
       * and the legacy interface
       * @code
       * while(rtf.getRecord())
       * {
       *      // use data() to get the last read record
       *    cout << rtf.data() << endl;
       * }
       * rtf.waitForData(10);
       * @endcode
       * The waitForData() call will ready the internal file stream 
       * for the next read.  Remember that RTFileFrameIterator is a
       * past-the-end style iterator, so you must reset it to begin() when
       * it has finished otherwise you'll get no data. In Dumb mode, 
       * you will need to use the openNextDay() function to advance
       * to tne next day.  
       * When you reach the end of a data set, there are three ways
       * to restore the valid state of the RTFileFrame iteration:
       * @code rtf.waitForData(900) @endcode
       * This is the best option for either Smart or Dumb mode, ensuring
       * that the file gets reopened correctly.
       * @code rtf.openCurrentFile() @endcode
       * Reopens the current file, destroying the current read position.
       * In other words, don't use this in Smart mode.
       * @code rtf.openNextDay() @endcode
       * This will open the next day's file, obviously resetting
       * the read position for the stream.
       * @warning In Smart mode, you could potentially miss data when
       * you pass to the start of a new day if data gets written to the
       * file of the previous day.  
       * @warning DO NOT MIX THE TWO ITERATOR SEMANTICS. Doing so, you will
       * definately miss data.
       * 
       *
       */
   template <class FileStream, class FileData>
   class RTFileFrame : public RTFileFrameHelper
   {
   public:
         /**
          * constructor
          * @param fnFormat string defining how the files names are formatted
          * @param beginning time of data to start streaming from
          * @param ending time of data to stop streaming at
          * @param frm mode used to read file (new data or all)
          * @param grm mode used when end of file is reached (new data or all)
          * @throw Exception an error ocurred
          */
      RTFileFrame(const gpstk::FileSpec& fnFormat,
                  const gpstk::CommonTime& beginning = gpstk::CommonTime::BEGINNING_OF_TIME,
                  const gpstk::CommonTime& ending = gpstk::CommonTime::END_OF_TIME, 
                  const FileReadingMode frm = AppendedData,
                  const GetRecordMode grm = Dumb)
         throw(gpstk::Exception);
      
         /// destructor
      ~RTFileFrame();
      
         /// Allows changing of the FileReadingMode
      RTFileFrame& setFileReadingMode(const FileReadingMode f);

         /// Allows changing of the GetRecordMode
      RTFileFrame& setGetRecordMode(const GetRecordMode g);

         /**
          * Waits \a wait number of seconds, then reopens the file
          * and sets the next read as appropriate for the FileReadingMode.
          * @param wait number of seconds to sleep.
          */
      void waitForData(unsigned wait = 0);

         /// returns true if the file currently being read from has
         /// changed since its last read
      bool hasFileChanged();
      
         /// Returns true if all data for the given time has been read.
      bool endOfDataSet();

         /// end of data set iterator, useful for loops
      RTFileFrameIterator<FileStream, FileData> end();

         /// start of data set, set appropriately for the 
         /// given FileReadingMode.  if in AppendedData mode, begin will
         /// start at the next record that hasn't been read.  if in
         /// FromTheBeginning mode, begin starts at the start of the 
         /// current file.
      RTFileFrameIterator<FileStream, FileData> begin();

         /// reads the next record in the file
         /// @return true if a record was read, otherwise false.
      bool getRecord();

         /// when in Dumb mode, use this function to advance to the next day.
      void openNextDay();
      
         /// returns the last read data
      const FileData& data() const { return lastData;}

         /// this traverses the RTFileFrame from begin() to end(), adding
         /// each item to a list that gets returned. Using this will
         /// change the file position, so getRecord and any iterators
         /// will not be the same after this call.
      std::list<FileData> getList() const;

         /// this traverses the RTFileFrame from begin() to end(), adding
         /// each item to a FileFilter that gets returned. Using this will
         /// change the file position, so getRecord and any iterators
         /// will not be the same after this call.
      FileFilter<FileData> getFileFilter() const;

         /// returns the name of the current file begin used
      std::string getCurrentFile() const {return currentFileName;}

         /// returns the current time used for finding files
      gpstk::CommonTime getCurrentTime() const {return currentTime;}

         /// let the iterator see this class's insides
      friend class RTFileFrameIterator<FileStream, FileData>;

         /// reopens the file \a currentFileName
      bool openCurrentFile();

         /// close the file \a currentFileName
      void closeCurrentFile();

   protected:
         /// the internal file stream for the internal iterator
      FileStream* fileStream;

      FileData lastData;
         /// the last get position from the file
      std::streampos lastPosition;
         /// the last file to be read
      std::string currentFileName;
         /// the file spec for determining file names
      gpstk::FileSpec fs;
      
      gpstk::CommonTime startTime, ///< start time for file searching
         currentTime,            ///< time last used for finding a file
         endTime;                ///< end time for file searching
         /// the FileReadingMode for the reader
      FileReadingMode readMode;
         /// the GetRecordMode for the reader
      GetRecordMode getMode;
         /// keeps track of the last modify date for the last file read
      struct stat fileInfo;
         /// isOK is only false when the user needs to take action
         /// to continue reading - by calling waitForData(),
         /// openNextDay(), or openCurrentFile()
      bool isOK;
   };

      //@}

   template <class FileStream, class FileData>
   RTFileFrame<FileStream, FileData>::
   RTFileFrame<FileStream, FileData>(const gpstk::FileSpec& fnFormat,
                                     const gpstk::CommonTime& beginning,
                                     const gpstk::CommonTime& ending, 
                                     const RTFileFrameHelper::FileReadingMode frm,
                                     const RTFileFrameHelper::GetRecordMode grm)
   throw(gpstk::Exception)
      : fileStream(NULL), fs(fnFormat), startTime(beginning), 
      currentTime(beginning), endTime(ending), readMode(frm), getMode(grm)
   {
         // zero out seconds
      startTime = MJD(floor(MJD(startTime).mjd));
      endTime = MJD(floor(MJD(endTime).mjd));
      currentTime = MJD(floor(MJD(curretnTime).mjd));

         // set up the stream
      openCurrentFile();
   }

   template <class FileStream, class FileData>
   RTFileFrame<FileStream, FileData>::~RTFileFrame()
   {
      if (fileStream)
      {
         fileStream->close();
         delete fileStream;
      }
   }

   template <class FileStream, class FileData>
   RTFileFrame<FileStream, FileData>& 
   RTFileFrame<FileStream, FileData> :: 
   setFileReadingMode(const RTFileFrameHelper::FileReadingMode f)
   { 
      readMode = f; 
      return *this; 
   }

      /// Allows changing of the GetRecordMode
   template <class FileStream, class FileData>
   RTFileFrame<FileStream, FileData>& 
   RTFileFrame<FileStream, FileData> :: 
   setGetRecordMode(const RTFileFrameHelper::GetRecordMode g)
   { 
      getMode = g; 
      return *this;
   }

   template <class FileStream, class FileData>
   void
   RTFileFrame<FileStream, FileData>::waitForData(unsigned wait)
   {
      sleep(wait);
      if(readMode == AppendedData)
      {  
            // reopen the file and skip to where we left off - openCurrentFile
            // will overwrite lastPosition...
         std::streampos cachedPos = lastPosition;
         if(openCurrentFile())
            fileStream->seekg(cachedPos);
         lastPosition = cachedPos;
      }
      else
      {
            // start from the beginning
         openCurrentFile();
      }
   }

   template <class FileStream, class FileData>
   bool
   RTFileFrame<FileStream, FileData>::hasFileChanged()
   {
      struct stat tempStat;
      if (stat(currentFileName.c_str(), &tempStat))
         return false;
      if (tempStat.st_mtime != fileInfo.st_mtime)
         return true;
      else 
         return false;
   }

   template <class FileStream, class FileData>
   bool
   RTFileFrame<FileStream, FileData>::endOfDataSet()
   {
      if (currentTime > endTime)
         return true;
      else
         return false;
   }

   template <class FileStream, class FileData>
   RTFileFrameIterator<FileStream, FileData>
   RTFileFrame<FileStream, FileData>::end()
   {
      return RTFileFrameIterator<FileStream, FileData>();
   }

   template <class FileStream, class FileData>
   RTFileFrameIterator<FileStream, FileData> 
   RTFileFrame<FileStream, FileData>::begin()
   {
      if (readMode == AppendedData)
      {
         ;
      }
      else // if (readMode == FromTheBeginning)
      {
         openCurrentFile();
      }
         // this constructor does not read in the first record, so force it
         // to do so before returning
      RTFileFrameIterator<FileStream, FileData> toReturn(*this);
      return toReturn;
   }

   template <class FileStream, class FileData>
   bool
   RTFileFrame<FileStream, FileData>::getRecord()
   {
      if (!isOK)
         return false;

         // is the stream still good to read?
      if (*fileStream >> lastData)
      {
         lastPosition = fileStream->tellg();
         return true;
      }
         // the last read failed - try opening the next file until
         // today's file is reched or else return false
      else
      {
            // read the next file or not?
         if (getMode == Smart)
         {
            if (!endOfDataSet())
            {
                  // still before today?
               gpstk::CommonTime today;
               today.setYDoySod(today.year(), today.DOY(), 0);
               
               if (currentTime < today)
               {
                  openNextDay();
                  return getRecord();
               }
                  // we've read through today, so just wait until tomorrow's
                  // data comes in - the user needs to call waitForData()
                  // to continue
               else
                  isOK = false;
            }
            else
            {
               isOK = false;
            }
         } // if (getmode == smart)

            // in dumb mode, the user needs to call openNextDay or
            // openCurrentFile to continue.
         else
         {
            isOK = false;
         }
      } // if (isOK)
      return isOK;
   }

   template <class FileStream, class FileData>
   void
   RTFileFrame<FileStream, FileData>::openNextDay()
   {
         // open a new file for another day, if any.
      currentTime += gpstk::CommonTime::SEC_DAY;
      if (!endOfDataSet())
         openCurrentFile();
   }
   
   template <class FileStream, class FileData>
   std::list<FileData>
   RTFileFrame<FileStream, FileData>::getList() const
   {
      RTFileFrameIterator<FileStream, FileData> inputStream;
      std::list<FileData> toReturn;
      inputStream = begin();
      while (inputStream != end())
      {
         toReturn.push_back(data());
      }
      return toReturn;
   }

   template <class FileStream, class FileData>
   FileFilter<FileData>
   RTFileFrame<FileStream, FileData>::getFileFilter() const
   {
      RTFileFrameIterator<FileStream, FileData> inputStream;
      FileFilter<FileData> toReturn;
      inputStream = begin();
      while (inputStream != end())
      {
         toReturn.addData(data());
      }
      return toReturn;
   }

   template <class FileStream, class FileData>
   bool
   RTFileFrame<FileStream, FileData>::openCurrentFile()
   {
      isOK = false;
      currentFileName = fs.toString(currentTime);
      stat(currentFileName.c_str(), &fileInfo);
      lastPosition = 0;

      if(fileStream)
      {
         fileStream->close();
         delete fileStream;
         fileStream = NULL;
      }

      fileStream = new FileStream;
      fileStream->open(currentFileName.c_str(), std::ios::in);
      if (!fileStream->fail())
         isOK = true;
      return isOK;
   }

   template <class FileStream, class FileData>
   void
   RTFileFrame<FileStream, FileData>::closeCurrentFile()
   {
      if(fileStream)
      {
         fileStream->close();
         delete fileStream;
         fileStream = NULL;
      }
      isOK = false;
   }

} // namespace gpstk

#endif  // GPSTK_RTFILEFRAME_HPP
