//============================================================================
//
//  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 Week.hpp Pure virtual class used to derive (with WeekSecond)
/// XXXWeekSecond for systems XXX (GPS QZS BDS GAL) and GPSWeek (for
/// GPSWeekZcount).

#ifndef GPSTK_WEEK_HPP
#define GPSTK_WEEK_HPP

#define timeSystemCheck if(timeSystem != right.timeSystem && \
   timeSystem != TimeSystem::Any && right.timeSystem != TimeSystem::Any) \
   GPSTK_THROW(InvalidRequest( \
   "CommonTime objects not in same time system, cannot be compared"))

#include "TimeTag.hpp"
#include "TimeConverters.hpp"

namespace gpstk
{
      /// @ingroup TimeHandling
      //@{

      /** This class is an abstract base class inherited by other
       * time-related classes that use a "week number + seconds of
       * week" formulation; this includes GPS, GAL, BDS and QZS. It
       * handles only the week number, including rollover and the
       * modulo operation; class WeekSecond inherits this class and
       * handles the seconds-of-week.
       *
       * The class is abstract because it does not fulfill the
       * abstract methods of the TimeTag class, and because the
       * routines defining the number of bits in the "ModWeek" and the
       * starting epoch are not defined.
       *
       * The Week class inherits from TimeTag and handles the epoch and
       * N-bit week special cases:
       *  getEpoch() returns int (week / rollover) or the number of rollovers,
       *  getWeek() returns full week
       *  getModWeek() returns week % rollover or the "short week"
       *  getEpochModWeek(int& e, int& w) e = getEpoch(); w = getModWeek();
       *  setEpoch(int e) sets week = (week & bitmask) | (e << Nbits);
       *  setModWeek(int w) sets week = (week & ~bitmask) | (w & bitmask);
       *  setEpochModWeek(int e, int w) calls setEpoch(e); setWeek(w);
       */
   class Week : public TimeTag
   {
   public:

         /** Return the number of bits in the bitmask used to get the
          * ModWeek from the full week.  This is pure virtual and must
          * be implemented in the derived class;
          * e.g. GPSWeek::Nbits(void) { static const int n=10; return n; }
          */
      virtual int Nbits(void) const = 0;

         /** Return the bitmask used to get the ModWeek from the full
          * week.  This is pure virtual and must be implemented in the
          * derived class;
          * e.g. GPSWeek::bitmask(void) { static const int bm=0x3FF; return bm; }
          */
      virtual int bitmask(void) const = 0;

         /** Return the maximum Nbit-week-number minus 1, i.e. the
          * week number at which rollover occurs. */
      virtual int rollover(void) const
      { return bitmask()+1; }

         /** Return the Modified Julian Date (MJD) of epoch for this
          * system.  This is pure virtual and must be implemented in
          * the derived class;
          * e.g. long GPSWeek::MJDEpoch(void)
          *      { static const long e=GPS_EPOCH_MJD; return e; }
          * @note this assumes the epoch(MJD) is integer,
          * i.e. H:M:S=0:0:0; true (so far). */
      virtual long MJDEpoch(void) const = 0;

         /** Return the greatest week value for which a conversion to or
          * from CommonTime would work. */
      int MAXWEEK(void) const
      {
         static const int mw=(CommonTime::END_LIMIT_JDAY - MJD_JDAY - MJDEpoch()) / 7;
         return mw;
      }

         /// Constructor.
      Week(int w = 0, TimeSystem ts = TimeSystem::Unknown )
            : week(w)
      { timeSystem = ts; }
      
         /// Virtual Destructor.
      virtual ~Week()
      {}
      
         /// Assignment Operator.
      Week& operator=(const Week& right);
      
         /// @name Comparison Operators.
         //@{
      inline bool operator==(const Week& right) const
      {
            // Any (wildcard) type exception allowed, otherwise must be same time systems
         if(timeSystem != right.timeSystem &&
            timeSystem != TimeSystem::Any && right.timeSystem != TimeSystem::Any)
            return false;

         return (week == right.week);
      }

      inline bool operator!=(const Week& right) const
      {
         timeSystemCheck;
         return (week != right.week);
      }
      
      inline bool operator<(const Week& right) const
      {
         timeSystemCheck;
         return week < right.week;
      }
      
      inline bool operator<=(const Week& right) const
      {
         timeSystemCheck;
         return week <= right.week;
      }
      
      inline bool operator>(const Week& right) const
      {
         timeSystemCheck;
         return week > right.week;
      }
      
      inline bool operator>=(const Week& right) const
      {
         timeSystemCheck;
         return week >= right.week;
      }
         //@}

         /// @name Special Epoch and Nbit Week Methods.
         //@{
      inline virtual unsigned int getWeek() const
      {
         return week;
      }

      inline virtual unsigned int getModWeek() const
      {
         return (week & bitmask());
      }
      
      inline virtual unsigned int getEpoch() const
      {
         return (week >> Nbits());
      }
      
      inline virtual void getEpochModWeek(unsigned int& e,
                                          unsigned int& w) const
      {
         e = getEpoch();
         w = getModWeek();
      }
      
      inline virtual void setEpoch(unsigned int e)
      {
         week &= bitmask();
         week |= e << Nbits();
      }

      inline virtual void setModWeek(unsigned int w)
      {
         week &= ~bitmask();
         week |= w & bitmask();
      }
      
      inline virtual void setEpochModWeek(unsigned int e,
                                          unsigned int w)
      {
         setEpoch(e);
         setModWeek(w);
      }

      inline virtual void adjustToYear(unsigned int y)
      {
         long jd1,jd2;
         int ep1,ep2;
         jd1 = convertCalendarToJD(y,1,1);
         ep1 = (jd1 - MJD_JDAY - MJDEpoch())/7/rollover();
         jd2 = convertCalendarToJD(y,12,31);
         ep2 = (jd2 - MJD_JDAY - MJDEpoch())/7/rollover();
         unsigned int halfroll = rollover()/2;
         unsigned int mw = getModWeek();

         if(ep1 == ep2)                      // no rollover in given year
            setEpoch(ep1);
         else if(mw <= halfroll)
            setEpoch(ep2);                   // rollover happened before mw -> use ep2
         else
            setEpoch(ep1);                   // rollover happened after mw
      }

         //@}
      
         /** Return a string containing the characters that this class
          * understands when printing times. */
      inline virtual std::string getPrintChars() const
      {
         return "EFGP";
      }
         
         /// Return a string containing the default format to use in printing.
      inline virtual std::string getDefaultFormat() const
      {
         return "%04F";
      }

      virtual bool isValid() const
      {
         return ((week >= 0) && (week <= MAXWEEK()));
      }
      
      inline virtual void reset()
      {
         week = 0;
      }
      
         /// Force this interface on this classes descendants.
      virtual unsigned int getDayOfWeek() const = 0;

         // member data
      int week;      ///< Full week number
   };

      //@}

} // namespace

#undef timeSystemCheck

#endif // GPSTK_WEEK_HPP
