//============================================================================
//
//  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 GPSZcount.hpp
 * gpstk::GPSZcount - encapsulates time according to full GPS Week and Z-count.
 */

#ifndef GPSTK_GPSZCOUNT_HPP
#define GPSTK_GPSZCOUNT_HPP

// system includes
#include <ostream>

// GPSTK includes
#include "Exception.hpp"

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

      /**
       * A representation class for GPS Time consisting of
       * a short GPS week, and a long GPS Z-count.
       * A valid GPS week is in the following range:
       *   0 <= week <= numerical_limits<short>::max()
       * A valid GPS Z-count is in the following range:
       *   0 <= zcount < 403200
       *
       * In Z-count addition, if a specified Z-count value is above or below
       * the given range of valid Z-counts, the appropriate number of GPS
       * weeks-worth of Z-counts are added or subtracted from the given value
       * so that it falls within the acceptable range.  These extra weeks are
       * then accounted for in the weeks data member.  If the addition or
       * subtraction of these additional weeks cause the GPS week value to go
       * out-of-bounds, an InvalidRequest exception is thrown.
       */
   class GPSZcount
   {
   public:
         /// Z-counts per minute (40)
      static const long ZCOUNT_MINUTE;
         /// Z-counts per hour (2400)
      static const long ZCOUNT_HOUR;
         /// Z-counts per day (57600)
      static const long ZCOUNT_DAY;
         /// Z-counts per whole GPS week. (403200)
      static const long ZCOUNT_WEEK;

         /** Separate Week and Z-count Constructor.
          * @param inWeek the GPS week to use in this object.
          * @param inZcount the GPS Z-count to use in this object.
          * @throws gpstk::InvalidParameter if either inWeek or inZcount
          *  are invalid values for their respective types.
          */
      GPSZcount(short inWeek,
                long inZcount);

         /** Full Z-count Constructor.
          * @param inFullZcount the Full 29-bit GPS Z-count to use in
          *  this object. (default=0 => week == 0; Z-count == 0)
          * @throws gpstk::InvalidParameter if either inFullZcount is
          *  an invalid value for a full Z-count.
          */
      GPSZcount(long inFullZcount = 0);

         /// Destructor.  Does nothing.
      ~GPSZcount() {}

         /** Copy Constructor.
          * @param right the GPSZcount object to copy
          */
      GPSZcount(const GPSZcount& right);

         /// GPS week accessor
      short getWeek() const
      { return week ; }

         /// GPS Z-count accessor
      long getZcount() const
      { return zcount ; }

         /** Construct, from the data members, the Z-count which could have
          *  been broadcast from an SV.
          * @return a Full Z-count (3 MSB unused, next 10 bits = week,
          *  19 LSB = Z-count)
          */
      long getFullZcount() const
      { return (long(getWeek() & 0x3FF) << 19) + getZcount() ; }

         /** Calculate the total number of Z-counts in this object.
          * @return weeks * ZCOUNT_WEEK + zcount
          */
      double getTotalZcounts() const
      { return double(getWeek()) * ZCOUNT_WEEK + getZcount() ; }

         /** Set the GPS week.
          * @param inWeek the new value for GPS Week.
          * @return a reference to this object.
          * @throws gpstk::InvalidParameter if inWeek is an invalid value
          * for a GPS week.
          */
      GPSZcount& setWeek(short inWeek);

         /** Set the GPS Z-count.
          * @param inZcount the new value for GPS Z-count.
          * @return a reference to this object.
          * @throws gpstk::InvalidParameter if inZcount is an invalid value
          *  for a GPS Z-count.
          */
      GPSZcount& setZcount(long inZcount);

         /** Set the GPS Week and Z-count using the value broadcast from an SV.
          * @param inZcount A full Z-count (3 MSB unused, next 10 bits = week,
          *  19 LSB = Z-count)
          * @throws gpstk::InvalidParameter if the extracted Z-count is
          *  an invalid value for a GPS Z-count.
          */
      GPSZcount& setFullZcount(long inZcount);

         /** Add the given number of weeks to the current value.
          * @param inWeeks the number of weeks to add to the current value.
          * @return a reference to this object
          * @throws gpstk::InvalidRequest if adding inWeeks would render
          *  this object invalid.
          */
      GPSZcount& addWeeks(short inWeeks);

         /** Add the given number of Z-counts to the current value.
          *  This may cause a roll-(over/under) of the Z-count and appropriate
          *  modification of the week.
          * @param inZcounts the number of Z-counts to add to the current
          *  value.
          * @return a reference to this object
          * @throws gpstk::InvalidRequest if adding inZcounts would render
          *  this object invalid.
          */
      GPSZcount& addZcounts(long inZcounts);

         /** Postfix Increment the Z-count in this object (x++).  This may also
          * cause the roll-over of the Z-count and incrementing of the week.
          * @return a GPSZcount with the value of this object before the
          *  increment
          * @throws gpstk::InvalidRequest if Z-count increment would render
          *  this object invalid.
          */
      GPSZcount operator++(int);

         /** Prefix Increment the Z-count in this object (++x).  This may also
          * cause the roll-over of the Z-count and incrementing of the week.
          * @return a reference to this object.
          * @throws gpstk::InvalidRequest if Z-count increment would render
          *  this object invalid.
          */
      GPSZcount& operator++();

         /** Postfix Decrement the Z-count in this object (x--).  This may also
          * cause the roll-under of the Z-count and decrementing of the week.
          * @return a GPSZcount with the value of this object before the
          *  decrement
          * @throws gpstk::InvalidRequest if a Z-count decrement would render
          *  this object invalid.
          */
      GPSZcount operator--(int);

         /** Prefix Decrement the Z-count in this object (--x).  This may also
          * cause the roll-under of the Z-count and decrementing of the week.
          * @return a reference to this object.
          * @throws gpstk::InvalidRequest if a Z-count decrement would render
          *  this object invalid.
          */
      GPSZcount& operator--();

         /** Add the given number of Z-counts to the current value.
          *  This may cause a roll-(over/under) of the Z-count and appropriate
          *  modification of the week.
          * @param inZcounts the number of Z-counts to add to the current
          *  value.
          * @return a modified GPSZcount object
          * @throws gpstk::InvalidRequest if adding inZcounts would render
          *  this object invalid.
          */
      GPSZcount operator+(long inZcounts) const;

         /** Subtract the given number of Z-counts from the current value.
          *  This may cause a roll-(over/under) of the Z-count and appropriate
          *  modification of the week.
          * @param inZcounts the number of Z-counts to subtract from the
          *  current value.
          * @return a modified GPSZcount object
          * @throws gpstk::InvalidRequest if subtracting inZcounts would
          *  render this object invalid.
          */
      GPSZcount operator-(long inZcounts) const;

         /** Compute the time differenct between this object and \a right.
          * @param right the GPSZcount to subtract from this object
          * @return the number of Z-counts between this object and \a right
          * Note that this returns a double as opposed to a long. This is to
          * avoid any overflow issues.
          */
      double operator-(const GPSZcount& right) const;

         /** Compute the remainder of the ztime
          * @param right the number to
          * @return z counts % \a right
          */
      long operator%(const long right) const;

         /** Add the given number of Z-counts to the current value.
          *  This may cause a roll-(over/under) of the Z-count and appropriate
          *  modification of the week.
          * @param inZcounts the number of Z-counts to add to the current
          *  value.
          * @return a reference to this object
          * @throws gpstk::InvalidRequest if adding inZcounts would render
          *  this object invalid.
          */
      GPSZcount& operator+=(long inZcounts);

         /** Subtract the given number of Z-counts from the current value.
          *  This may cause a roll-(over/under) of the Z-count and appropriate
          *  modification of the week.
          * @param inZcounts the number of Z-counts to subtract from the
          *  current value.
          * @return a reference to this object
          * @throws gpstk::InvalidRequest if subtracting inZcounts would
          *  render this object invalid.
          */
      GPSZcount& operator-=(long inZcounts);

         /// Assignment operator.
      GPSZcount& operator=(const GPSZcount& right);

         /// Comparison operator (less-than).
      bool operator<(const GPSZcount& right) const;
         /// Comparison operator (greater-than).
      bool operator>(const GPSZcount& right) const;
         /// Equality operator.
      bool operator==(const GPSZcount& right) const;
         /// Inequality operator.
      bool operator!=(const GPSZcount& right) const;
         /// Comparison operator (less-than or equal-to).
      bool operator<=(const GPSZcount& right) const;
         /// Comparison operator (greater-than or equal-to).
      bool operator>=(const GPSZcount& right) const;

         /// Convert this object to a string.
      operator std::string() const;

         /**
          * This is a test of whether or not this object and the  given
          * GPSZcount object are within the same time-block.  Say you need to
          * find out if the two GPSZcounts are:
          * ... in the same day:     inZcountBlock == ZCOUNT_DAY
          * ... or the same minute:  inZcountBlock == ZCOUNT_MINUTE   etc.
          * For inZcountBlock < ZCOUNT_WEEK, blocks start at the beginning of
          *  the week.
          * For inZcountBlock >= ZCOUNT_WEEK, blocks start at the beginning of
          *  GPS Week 0.
          * inZcountOffset allows checking of times off of the usual boundaries
          * i.e. in the same day where a day is defined as starting at noon
          *  instead of at midnight, or in the same minute where a minute
          *  starts at 23 seconds instead of zero.
          * @param other the other GPSZcount object
          * @param inZcountBlock the number of Z-counts in a time-block
          * @param inZcountOffset the number of Z-counts to offset the
          *  time-block (default = 0)
          * @return whether this object and /a other are in the same time-block
          */
      bool inSameTimeBlock(const GPSZcount& other,
                           unsigned long inZcountBlock,
                           unsigned long inZcountOffset = 0);

         /** Dump the contents of this object to the given output stream.
          * @param out the output stream to write to
          * @param level the level of verbosity to use (default=0)
          */
      void dump(std::ostream& out,
                short level = 0) const;

   protected:
      short week;    ///< GPS full week. (0 <= week)
      long zcount;   ///< GPS Z-count.  (0 <= zcount <= 403199)

         /** Check the validity of the given Z-count.
          * @param z the Z-count to verify
          * @return 0 if valid, negative if below zero,
          *  positive if above 403199
          */
      static long validZcount(long z);

   };

      /** Stream output operator for GPSZcount objects.
       * @param s stream to append formatted GPSZcount to.
       * @param z GPSZcount to append to stream \c s.
       * @return a reference to \c s.
       */
   std::ostream& operator<<(std::ostream& s,
                            const gpstk::GPSZcount& z);

      //@}

} // namespace gpstk

#endif // GPSTK_GPSZCOUNT_HPP
