//============================================================================
//
//  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 GNSSconstants.hpp
 * Constants as defined in the GPS-ICD-200D and by all RINEX GNSSs
 */

#ifndef GPSTK_GNSS_CONSTANTS_HPP
#define GPSTK_GNSS_CONSTANTS_HPP

#include "Exception.hpp"
#include "SatID.hpp"

namespace gpstk
{
      /**
       * @ingroup Geodetic
       * @name GNSS Constants
       * Time constants are in TimeConstants.hpp
       */
      //@{

      // ---------------- independent of GNSS ----------------------
      /// GPS value of PI; also specified by GAL
   const double PI        = 3.141592653589793238462643383280;
      /// GPS value of PI*2
   const double TWO_PI    = 6.283185307179586476925286766559;
      /// GPS value of PI**0.5
   const double SQRT_PI   = 1.772453850905516027298167483341;
      /// relativity constant (sec/sqrt(m))
   const double REL_CONST = -4.442807633e-10;
      /// m/s, speed of light; this value defined by GPS but applies to GAL and GLO.
   const double C_MPS = 2.99792458e8;
      /// Conversion Factor from degrees to radians (units: degrees^-1)
   static const double DEG_TO_RAD = 1.7453292519943e-2;
      /// Conversion Factor from radians to degrees (units: degrees)
   static const double RAD_TO_DEG = 57.295779513082;

      // ---------------- GPS --------------------------------------
      /// Hz, GPS Oscillator or chip frequency
   const double OSC_FREQ_GPS  = 10.23e6;
      /// Hz, GPS chip rate of the P & Y codes
   const double PY_CHIP_FREQ_GPS = OSC_FREQ_GPS;
      /// Hz, GPS chip rate of the C/A code
   const double CA_CHIP_FREQ_GPS = OSC_FREQ_GPS / 10.0;
      /// Hz, GPS Base freq w/o relativisitic effects
   const double RSVCLK_GPS    = 10.22999999543e6;
      /// GPS L1 carrier frequency in Hz
   const double L1_FREQ_GPS   = 1575.42e6;
      /// GPS L2 carrier frequency in Hz
   const double L2_FREQ_GPS   = 1227.60e6;
      /// GPS L5 carrier frequency in Hz.
   const double L5_FREQ_GPS   = 1176.45e6;
      /// GPS L1 carrier wavelength in meters
   const double L1_WAVELENGTH_GPS  = 0.190293672798;
      /// GPS L2 carrier wavelength in meters
   const double L2_WAVELENGTH_GPS  = 0.244210213425;
      /// GPS L5 carrier wavelength in meters.
   const double L5_WAVELENGTH_GPS  = 0.254828049;
      /// GPS L1 frequency in units of oscillator frequency
   const double L1_MULT_GPS   = 154.0;
      /// GPS L2 frequency in units of oscillator frequency
   const double L2_MULT_GPS   = 120.0;
      /// GPS L5 frequency in units of oscillator frequency.
   const double L5_MULT_GPS   = 115.0;
      /// GPS Gamma constant
   const double GAMMA_GPS = 1.646944444;
      /// Reference Semi-major axis. From IS-GPS-800 Table 3.5-2 in meters.
   const double A_REF_GPS = 26559710.0;
      /// Omega reference value from Table 30-I converted to radians
   const double OMEGADOT_REF_GPS = -2.6e-9 * PI;

   inline
   short getLegacyFitInterval(const short iodc, const short fiti)
      throw(gpstk::InvalidRequest )
   {
         /* check the IODC */
      if (iodc < 0 || iodc > 1023)
      {
            /* error in iodc, return minimum fit */
         return 4;
      }

      if ( ( ( (fiti == 0) && (iodc & 0xFF) < 240 )
             || (iodc & 0xFF) > 255 ) )
      {
            /* fit interval of 4 hours */
         return 4;
      }
      else if (fiti == 1)
      {
         if( ((iodc & 0xFF) < 240 || (iodc & 0xFF) > 255))
         {
               /* fit interval of 6 hours */
            return 6;
         }
         else if(iodc >=240 && iodc <=247)
         {
               /* fit interval of 8 hours */
            return 8;
         }
         else if( ( (iodc >= 248) && (iodc <= 255) ) || iodc == 496 )
         {
               /* fit interval of 14 hours */
            return 14;
         }
         else if((iodc >= 497 && iodc <=503) || (iodc >= 1021 && iodc <= 1023))
         {
               /* fit interval of 26 hours */
            return 26;
         }
         else if(iodc >= 504 && iodc <=510)
         {
               /* fit interval of 50 hours */
            return 50;
         }
         else if( iodc == 511 || ( (iodc >= 752) && (iodc <= 756) ) )
         {
               /* fit interval of 74 hours */
            return 74;
         }
         else if(iodc == 757)
         {
               /* fit interval of 98 hours */
            return 98;
         }
         else
         {
            InvalidRequest exc("Invalid IODC Value For sv Block");
            GPSTK_THROW(exc);
         }
      }
      else
      {
            /* error in ephemeris/iodc, return minimum fit */
         return 4;
      }

      return 0; // never reached
   }

      // ---------------- GLONASS ----------------------------------
      /// GLO Fundamental chip rate in Hz.
   const double OSC_FREQ_GLO  = 5.11e6;
      /// GLO Chip rate of the P & Y codes in Hz.
   const double PY_CHIP_FREQ_GLO = OSC_FREQ_GLO;
      /// GLO Chip rate of the C/A code in Hz.
   const double CA_CHIP_FREQ_GLO = OSC_FREQ_GLO / 10.0;

      /// GLO Fundamental oscillator freq in Hz.
   const double PSC_FREQ_GLO  = 5.00e6;
      /// GLO Base freq w/o relativisitic effects in Hz.
   const double RSVCLK_GLO    = 4.99999999782e6;

      // GLO Frequency(Hz)
      //   f1 is 1602.0e6 + n*562.5e3 Hz = 9 * (178 + n*0.0625) MHz
      //   f2    1246.0e6 + n*437.5e3 Hz = 7 * (178 + n*0.0625) MHz
      // where n is the time- and satellite-dependent 'frequency channel'
      // -7 <= n <= 7
      /// GLO L1 carrier base frequency in Hz.
   const double L1_FREQ_GLO       = 1602.0e6;
      /// GLO L1 carrier frequency step size in Hz.
   const double L1_FREQ_STEP_GLO  = 562.5e3;
      /// GLO L1 carrier wavelength in meters.
   const double L1_WAVELENGTH_GLO = 0.187136365793;
      /// GLO L2 carrier base frequency in Hz.
   const double L2_FREQ_GLO       = 1246.0e6;
      /// GLO L2 carrier frequency step size in Hz.
   const double L2_FREQ_STEP_GLO  = 437.5e3;
      /// GLO L2 carrier wavelength in meters.
   const double L2_WAVELENGTH_GLO = 0.240603898876;
      /// GLO L1 multiplier.
   const double L1_MULT_GLO   = 320.4;
      /// GLO L2 multiplier.
   const double L2_MULT_GLO   = 249.2;
      /// GLO L3 carrier frequency in Hz.
   const double L3_FREQ_GLO       = 1202.025e6;
      /// GLO L3 carrier wavelength in meters.
   const double L3_WAVELENGTH_GLO = 0.249406175412;

      /// Constant for the max array index in SV accuracy table.
   const int SV_ACCURACY_GLO_INDEX_MAX = 15;
      /// Map from SV accuracy/URA flag to NOMINAL accuracy values in m.
      /// Further details in ICD-GLO-v5.0, Table 4.4 in Section 4.4.
   const double SV_ACCURACY_GLO_INDEX[] = { 1.0,  2.0,   2.5,   4.0,   5.0,
                                            7.0, 10.0,  12.0,  14.0,  16.0,
                                            32.0, 64.0, 128.0, 256.0, 512.0,
                                            9.999999999999e99               };

      // ---------------- Galileo ----------------------------------
      /// GAL L1 (E1) carrier frequency in Hz
   const double L1_FREQ_GAL   = L1_FREQ_GPS;
      /// GAL L5 (E5a) carrier frequency in Hz.
   const double L5_FREQ_GAL   = L5_FREQ_GPS;
      /// GAL L6 (E6) carrier frequency in Hz.
   const double L6_FREQ_GAL   = 1278.75e6;
      /// GAL L7 (E5b) carrier frequency in Hz.
   const double L7_FREQ_GAL   = 1207.140e6;
      /// GAL L8 (E5a+E5b) carrier frequency in Hz.
   const double L8_FREQ_GAL   = 1191.795e6;

      /// GAL L1 carrier wavelength in meters
   const double L1_WAVELENGTH_GAL  = L1_WAVELENGTH_GPS;
      /// GAL L5 carrier wavelength in meters.
   const double L5_WAVELENGTH_GAL  = L5_WAVELENGTH_GPS;
      /// GAL L6 carrier wavelength in meters.
   const double L6_WAVELENGTH_GAL  = 0.234441805;
      /// GAL L7 carrier wavelength in meters.
   const double L7_WAVELENGTH_GAL  = 0.24834937;
      /// GAL L8 carrier wavelength in meters.
   const double L8_WAVELENGTH_GAL  = 0.251547001;

      // ---------------- Geostationary (SBAS) ---------------------
      /// GEO L1 carrier frequency in Hz
   const double L1_FREQ_GEO   = L1_FREQ_GPS;
      /// GEO L5 carrier frequency in Hz.
   const double L5_FREQ_GEO   = L5_FREQ_GPS;

      /// GEO L1 carrier wavelength in meters
   const double L1_WAVELENGTH_GEO  = L1_WAVELENGTH_GPS;
      /// GEO L5 carrier wavelength in meters.
   const double L5_WAVELENGTH_GEO  = L5_WAVELENGTH_GPS;

      // ---------------- BeiDou ----------------------------------
      /// The maximum number of active satellites in the Compass constellation.
   const long MAX_PRN_COM     = 30;
      /// BDS L1 (B1) carrier frequency in Hz.
   const double L1_FREQ_BDS   = 1561.098e6;
      /// BDS L2 (B2) carrier frequency in Hz.
   const double L2_FREQ_BDS   = L7_FREQ_GAL;
      /// BDS L3 (B3) carrier frequency in Hz.
   const double L3_FREQ_BDS   = 1268.52e6;

      /// BDS L1 carrier wavelength in meters.
   const double L1_WAVELENGTH_BDS  = 0.192039486310276;
      /// BDS L2 carrier wavelength in meters.
   const double L2_WAVELENGTH_BDS  = L7_WAVELENGTH_GAL;
      /// BDS L3 carrier wavelength in meters.
   const double L3_WAVELENGTH_BDS  = 0.236332464604421;

      // ---------------- QZSS ----------------------------------
      /// QZS L1 carrier frequency in Hz.
   const double L1_FREQ_QZS   = L1_FREQ_GPS;
      /// QZS L2 carrier frequency in Hz.
   const double L2_FREQ_QZS   = L2_FREQ_GPS;
      /// QZS L5 carrier frequency in Hz.
   const double L5_FREQ_QZS   = L5_FREQ_GPS;
      /// QZS LEX(6) carrier frequency in Hz.
   const double L6_FREQ_QZS   = L6_FREQ_GAL;

      /// QZS L1 carrier wavelength in meters.
   const double L1_WAVELENGTH_QZS   = L1_WAVELENGTH_GPS;
      /// QZS L2 carrier wavelength in meters.
   const double L2_WAVELENGTH_QZS   = L2_WAVELENGTH_GPS;
      /// QZS L5 carrier wavelength in meters.
   const double L5_WAVELENGTH_QZS   = L5_WAVELENGTH_GPS;
      /// QZS LEX(6) carrier wavelength in meters.
   const double L6_WAVELENGTH_QZS   = L6_WAVELENGTH_GAL;

      // ---------------- Convenience routines ---------------------

      /** Compute wavelength for the given satellite system (sat.id is
       * ignored) at the given RINEX frequency band
       * n(=1,2,5,6,7,8). Return 0 if the frequency n is not valid for
       * the system.
       * Calls for system GLO must include the frequency channel number N
       * (-7<=N<=7). */
   inline double getWavelength(const SatID& sat, const int& n, const int N=0)
      throw()
   {
      switch(sat.system)
      {
         case SatID::systemGPS:
            if(n == 1) return L1_WAVELENGTH_GPS;
            else if(n == 2) return L2_WAVELENGTH_GPS;
            else if(n == 5) return L5_WAVELENGTH_GPS;
            break;
         case SatID::systemGlonass:
            if(n == 1) return (C_MPS/(L1_FREQ_GLO + N*L1_FREQ_STEP_GLO));
            else if(n == 2) return (C_MPS/(L2_FREQ_GLO + N*L2_FREQ_STEP_GLO));
            break;
         case SatID::systemGalileo:
            if(n == 1) return L1_WAVELENGTH_GAL;
            else if(n == 5) return L5_WAVELENGTH_GAL;
            else if(n == 6) return L6_WAVELENGTH_GAL;
            else if(n == 7) return L7_WAVELENGTH_GAL;
            else if(n == 8) return L8_WAVELENGTH_GAL;
            break;
         case SatID::systemGeosync:
            if(n == 1) return L1_WAVELENGTH_GEO;
            else if(n == 5) return L5_WAVELENGTH_GEO;
            break;
         case SatID::systemBeiDou:
               //MGEX data uses 2     if(n == 1) return L1_WAVELENGTH_BDS;
            if(n == 2) return L1_WAVELENGTH_BDS;
            else if(n == 7) return L2_WAVELENGTH_BDS;
            else if(n == 6) return L3_WAVELENGTH_BDS;
            break;
         case SatID::systemQZSS:
            if(n == 1) return L1_WAVELENGTH_QZS;
            else if(n == 2) return L2_WAVELENGTH_QZS;
            else if(n == 5) return L5_WAVELENGTH_QZS;
            else if(n == 6) return L6_WAVELENGTH_QZS;
            break;
         default:
            break;
      }

      return 0.0;
   }

      /** Compute beta(a,b), the ratio of 2 frequencies fb/fa for the
       * given satellite system (sat.id is ignored). Return 0 if
       * either of the input n's are not valid RINEX bands
       * (n=1,2,5,6,7,or 8) for the system. */
   inline double getBeta(const SatID& sat, const int& na, const int& nb)
      throw()
   {
      double wla = getWavelength(sat,na);
      double wlb = getWavelength(sat,nb);
      if(wla == 0.0 || wlb == 0.0) return 0.0;
      return wlb/wla;
   }

      /** Compute alpha (also called gamma) = (beta^2-1) =
       * ((fa/fb)^2-1) for 2 frequencies fa,fb for the given satellite
       * system (sat.id is ignored).
       * @return 0 if either of the input n's are not valid RINEX
       *   bands (n=1,2,5,6,7,8) for the satellite system. */
   inline double getAlpha(const SatID& sat, const int& na, const int& nb)
      throw()
   {
      double beta(getBeta(sat,na,nb));
      if(beta == 0.0) return 0.0;
      return (beta*beta-1.0);
   }

      //@}
   
} // namespace

#endif //GPSTK_GNSS_CONSTANTS_HPP
