//============================================================================
//
//  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 FileFilterFrameWithHeader.hpp
 * Wrapper for gpstk::FileHunter and gpstk::FileFilter that also
 * handles header data
 */

#ifndef GPSTK_FILEFILTERFRAMEWITHHEADER_HPP
#define GPSTK_FILEFILTERFRAMEWITHHEADER_HPP

#include "Rinex3ObsData.hpp"
#include "FileFilterFrame.hpp"
#include <math.h>

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

      /**
       * This is just like FileFilterFrame but it can also handle header
       * data.  The header data is stored in an internal list that can be
       * accessed with the *Header() methods below.  Certain classes (like
       * RINEX Obs and Met) have filter functions that can be used with
       * touch() to combine header data from various files. The merge utilities
       * in wonky use this ability, and other file types with header data
       * can benefit from using this class's ability to store and write
       * header data - see the RINEX and FIC GFW classes for more examples.
       *
       * When initializing, this uses the FileFilterFrame::init() to read
       * the FileData into the filter, then does a second pass with its own
       * init() function to read the headers from those files. This
       * is a little inefficient, but the goal of these classes was never
       * efficiency.
       */
   template <class FileStream, class FileData, class FileHeader>
   class FileFilterFrameWithHeader :
      public FileFilterFrame<FileStream, FileData>
   {
   public:
         /// Default constructor
      FileFilterFrameWithHeader(const gpstk::CommonTime& start = 
                                gpstk::CommonTime::BEGINNING_OF_TIME,
                                const gpstk::CommonTime& end = 
                                gpstk::CommonTime::END_OF_TIME)
            throw(gpstk::Exception) :
            FileFilterFrame<FileStream, FileData>(start, end)
      {}

         /// Takes a list of files to open in lieu of day times
      FileFilterFrameWithHeader(const std::vector<std::string>& fileList,
                                const gpstk::CommonTime& start = 
                                gpstk::CommonTime::BEGINNING_OF_TIME,
                                const gpstk::CommonTime& end = 
                                gpstk::CommonTime::END_OF_TIME)
            throw(gpstk::Exception) :
            FileFilterFrame<FileStream, FileData>(fileList, start, end)
      {
         std::vector<std::string>::const_iterator itr = fileList.begin();
         while (itr != fileList.end())
         {
            this->fs.newSpec(*itr);
            init();
            itr++;
         }
      }

         /// Takes a file name for a single file filter.
         /// This can throw an exception when there's a file error.
      FileFilterFrameWithHeader(const std::string& filename, 
                                const gpstk::CommonTime& start = 
                                gpstk::CommonTime::BEGINNING_OF_TIME,
                                const gpstk::CommonTime& end = 
                                gpstk::CommonTime::END_OF_TIME)
            throw(gpstk::Exception) :
            FileFilterFrame<FileStream, FileData>(filename, start, end)
      {init();}

         /// Uses the FileSpec to retrieve files.  Use filter like you would
         /// in FileHunter, to filter FOR stations, receivers, etc.
         /// This can throw an exception when there's a file error.
      FileFilterFrameWithHeader(const FileSpec& spec, 
                                const gpstk::CommonTime& start = 
                                gpstk::CommonTime::BEGINNING_OF_TIME,
                                const gpstk::CommonTime& end = 
                                gpstk::CommonTime::END_OF_TIME,
                                const std::vector<FileHunter::FilterPair>& filter = 
                                std::vector<FileHunter::FilterPair>())
            throw(gpstk::Exception) :
            FileFilterFrame<FileStream, FileData>(spec, start, end, filter)
      {init(filter);}

         /// Gets the files from the file spec and the time, then adds
         /// the data to the filter. Use filter like you would
         /// in FileHunter, to filter FOR stations, receivers, etc.
      FileFilterFrameWithHeader& 
      newSource(const FileSpec& filespec, 
                const gpstk::CommonTime& start = 
                gpstk::CommonTime::BEGINNING_OF_TIME,
                const gpstk::CommonTime& end = 
                gpstk::CommonTime::END_OF_TIME,
                const std::vector<FileHunter::FilterPair>& filter = 
                std::vector<FileHunter::FilterPair>())
         throw(gpstk::Exception)
      {
         FileFilterFrame<FileStream, FileData>::newSource(filespec, start,
                                                          end, filter);
         init(filter);
         return *this;
      }

         /// Reads in the file and adds the data to the filter.
      FileFilterFrameWithHeader& 
      newSource(const std::string& filename, 
                const gpstk::CommonTime& start = 
                gpstk::CommonTime::BEGINNING_OF_TIME,
                const gpstk::CommonTime& end = 
                gpstk::CommonTime::END_OF_TIME)
         throw(gpstk::Exception)
      {
         FileFilterFrame<FileStream, FileData>::newSource(filename, start,
                                                          end);
         init();
         return *this;
      }

         /// Reads in the file and adds the data to the filter.
      FileFilterFrameWithHeader& 
      newSource(const std::vector<std::string>& fileList, 
                const gpstk::CommonTime& start = 
                gpstk::CommonTime::BEGINNING_OF_TIME,
                const gpstk::CommonTime& end = 
                gpstk::CommonTime::END_OF_TIME)
         throw(gpstk::Exception)
      {
         FileFilterFrame<FileStream, FileData>::newSource(fileList, start,
                                                          end);
         typename std::vector<std::string>::const_iterator itr;
         for (itr = fileList.begin(); itr != fileList.end(); itr++)
         {
            this->fs.newSpec(*itr);
            init();
         }
         return *this;
      }

      virtual ~FileFilterFrameWithHeader() {}

         /**
          * Writes the data to the file outputFile with the given header.
          * This will overwrite any existing file with the same name.
          * This can throw an exception when there's a file error.
          * @return true when it works.
          * @warning This will not write out headers for files that need them.
          */
      bool writeFile(const std::string& outputFile,
                     const FileHeader& fh) const
         throw(gpstk::Exception);

      /// Returns a list of the data in *this that isn't in r.
      template <class BinaryPredicate>
      std::list<FileData>
      halfDiff(const FileFilterFrameWithHeader<FileStream,FileData,FileHeader>& r,
               BinaryPredicate p,
               int precision) const
      {
         long double epsilon = 1 / std::pow((long double)10,precision);
         std::list<FileData> toReturn;

         typename std::list<FileData>::const_iterator dvIt = this->dataVec.begin();
         typename std::list<FileData>::const_iterator rdvIt = r.dataVec.begin();
         while(dvIt != this->dataVec.end())
         {
            if (rdvIt == r.dataVec.end() ||
                p( *dvIt, this->headerList.front(),
                   *rdvIt, r.headerList.front(),
                   epsilon)) //dv less than
            {
               toReturn.push_back(*dvIt);
               dvIt++;
            }
            else if (p(*rdvIt, r.headerList.front(),
                       *dvIt, this->headerList.front(),
                       epsilon)) //rdv less than
            {
               rdvIt++;
            }
            else //equal
            {
               dvIt++;
               rdvIt++;
            }
         }
         return toReturn;
      }

         /** performs the operation op on the header list. */
      template <class Operation>
      FileFilterFrameWithHeader& touchHeader(Operation& op)
      {
         typename std::list<FileHeader>::iterator itr = headerList.begin();

         while (itr != headerList.end())
         {
            op(*itr);
            itr++;
         }

         return *this;
      }

         /// Returns the contents of the header data list.
      std::list<FileHeader>& getHeaderData(void) {return headerList;}

         /// Returns the contents of the header data list, const.
      std::list<FileHeader> getHeaderData(void) const {return headerList;}

         /// Returns the number of data items in the header list.
      typename std::list<FileHeader>::size_type getHeaderCount(void) const 
      { return headerList.size(); }

      typename std::list<FileHeader>::const_iterator beginHeader() const
         throw(gpstk::InvalidRequest);

      typename std::list<FileHeader>::const_iterator endHeader() const
         throw(gpstk::InvalidRequest);

      typename std::list<FileHeader>::iterator beginHeader() 
         throw(gpstk::InvalidRequest);

      typename std::list<FileHeader>::iterator endHeader() 
         throw(gpstk::InvalidRequest);

      bool emptyHeader() const
      { return headerList.empty(); }

      typename std::list<FileHeader>::size_type sizeHeader()
      { return headerList.size(); }

      FileHeader& frontHeader()
         throw(gpstk::InvalidRequest);

      const FileHeader& frontHeader() const
         throw(gpstk::InvalidRequest);

      FileHeader& backHeader()
         throw(gpstk::InvalidRequest);

      const FileHeader& backHeader() const
         throw(gpstk::InvalidRequest);

   protected:
         ///  Run init() to load the data into the filter.  
      void init(const std::vector<FileHunter::FilterPair>& filter= 
                std::vector<FileHunter::FilterPair>()) 
         throw(gpstk::Exception);

         /// Check to make sure headerList is empty
         /// @throw InvalidRequest if headerList is empty
      inline void chl(const std::string& req) throw(gpstk::InvalidRequest)
      {
         gpstk::InvalidRequest exc("Header list is empty attempting to"
                                   " satisfy "+req+" request.");
         if (headerList.empty())
         {
            GPSTK_THROW(exc);
         }
      }

   protected:   
      std::list<FileHeader> headerList;
   };

      //@}

   template <class FileStream, class FileData, class FileHeader>
   bool FileFilterFrameWithHeader<FileStream,FileData,FileHeader>::
   writeFile(const std::string& outputFile,
             const FileHeader& fh) const
      throw(gpstk::Exception)
   {
         // make the directory (if needed)
      std::string::size_type pos = outputFile.rfind('/');

      if (pos != std::string::npos)
         gpstk::FileUtils::makeDir(outputFile.substr(0,pos).c_str(), 0755);
      
      FileStream stream(outputFile.c_str(), std::ios::out|std::ios::trunc);
      stream.exceptions(std::ios::failbit);

      stream << fh;

      typename std::list<FileData>::const_iterator index;
      for(index = this->dataVec.begin(); index != this->dataVec.end(); index++)
         stream << (*index);
      return true;
   }

   template <class FileStream, class FileData, class FileHeader>
   typename std::list<FileHeader>::const_iterator
   FileFilterFrameWithHeader<FileStream,FileData,FileHeader>::beginHeader()
      const throw(gpstk::InvalidRequest)
   {
      try { chl("beginHeader"); }
      catch(gpstk::InvalidRequest exc)
      { GPSTK_RETHROW(exc); }
      return headerList.begin();
   }

   template <class FileStream, class FileData, class FileHeader>
   typename std::list<FileHeader>::const_iterator
   FileFilterFrameWithHeader<FileStream,FileData,FileHeader>::endHeader()
      const throw(gpstk::InvalidRequest)
   {
      try { chl("endHeader"); }
      catch(gpstk::InvalidRequest exc)
      { GPSTK_RETHROW(exc); }
      return headerList.end();
   }

   template <class FileStream, class FileData, class FileHeader>
   typename std::list<FileHeader>::iterator
   FileFilterFrameWithHeader<FileStream,FileData,FileHeader>::beginHeader()
      throw(gpstk::InvalidRequest)
   {
      try { chl("beginHeader"); }
      catch(gpstk::InvalidRequest exc)
      { GPSTK_RETHROW(exc); }
      return headerList.begin();
   }


   template <class FileStream, class FileData, class FileHeader>
   typename std::list<FileHeader>::iterator
   FileFilterFrameWithHeader<FileStream,FileData,FileHeader>::endHeader()
      throw(gpstk::InvalidRequest)
   {
      try { chl("endHeader"); }
      catch(gpstk::InvalidRequest exc)
      { GPSTK_RETHROW(exc); }
      return headerList.end();
   }

   template <class FileStream, class FileData, class FileHeader>
   FileHeader&
   FileFilterFrameWithHeader<FileStream,FileData,FileHeader>::frontHeader()
      throw(gpstk::InvalidRequest)
   {
      try { chl("frontHeader"); }
      catch(gpstk::InvalidRequest exc)
      { GPSTK_RETHROW(exc); }
      return headerList.front();
   }


   template <class FileStream, class FileData, class FileHeader>
   const FileHeader&
   FileFilterFrameWithHeader<FileStream,FileData,FileHeader>::frontHeader()
      const throw(gpstk::InvalidRequest)
   {
      try { chl("frontHeader"); }
      catch(gpstk::InvalidRequest exc)
      { GPSTK_RETHROW(exc); }
      return headerList.front();
   }


   template <class FileStream, class FileData, class FileHeader>
   FileHeader&
   FileFilterFrameWithHeader<FileStream,FileData,FileHeader>::backHeader()
      throw(gpstk::InvalidRequest)
   {
      try { chl("backHeader"); }
      catch(gpstk::InvalidRequest exc)
      { GPSTK_RETHROW(exc); }
      return headerList.back();
   }


   template <class FileStream, class FileData, class FileHeader>
   const FileHeader&
   FileFilterFrameWithHeader<FileStream,FileData,FileHeader>::backHeader()
      const throw(gpstk::InvalidRequest)
   {
      try { chl("backHeader"); }
      catch(gpstk::InvalidRequest exc)
      { GPSTK_RETHROW(exc); }
      return headerList.back();
   }

   template <class FileStream, class FileData, class FileHeader>
   void
   FileFilterFrameWithHeader<FileStream,FileData,FileHeader> :: 
   init(const std::vector<FileHunter::FilterPair>& filter)
      throw(gpstk::Exception)
   {
         // find the files
      FileHunter fh(this->fs);
   
      typename std::vector<FileHunter::FilterPair>::const_iterator itr = 
         filter.begin();
      while (itr != filter.end())
      {
         fh.setFilter((*itr).first, (*itr).second);
         itr++;
      }

      std::vector<std::string> listOfFiles = 
         fh.find(this->startTime, this->endTime, FileSpec::ascending);

         // for each file, just read the header
      typename std::vector<std::string>::iterator i;
      for(i = listOfFiles.begin(); i != listOfFiles.end(); i++)
      {
         FileStream s((*i).c_str());

         if (s.good())
         {
            s.exceptions(std::ios::failbit);

            FileHeader header;
            s >> header;
            headerList.push_back(header);
         }
      }
   }

} // namespace gpstk

#endif //GPSTK_FILEFILTERFRAMEWITHHEADER_HPP
