var NAVMODE =
{
   NONDIFFERENTIAL   : 0,
   DGPS              : 1,
   STARFIRE_DUAL     : 2,
   RTK_FLOAT         : 3,
   RTK_X             : 4,
   RTK_WLFIXED       : 5,
   RTK_L1FIXED       : 6,
   RTK_DUALFIXED     : 7,
   STARFIRE_DEGRADE  : 8,
   SF2_FALLBACK      : 9
};

var DGPS = 
{
   WAAS              : 0,
   WAAS_TESTMODE     : 1,
   STARFIRE          : 2,
   RTCM1             : 3,
   RTCM9             : 4,
   EGNOS             : 5,
   MSAS              : 6,
   GAGAN             : 7,
   STARFIRE_GLN      : 8,
   RESERVED          : 9
};

var RTK = 
{
   BASE5B            : 0,
   BASE5E            : 1,
   RTCM1819          : 2,
   RTCM2021          : 3,
   CMR               : 4,
   CMRPLUS           : 5,
   RTCM3_L1COMPACT   : 6,
   RTCM3_L1FULL      : 7,
   RTCM3_DUALCOMPACT : 8,
   RTCM3_DUALFULL    : 9
};

var DATUM =
{
   USERDEF : 0x01,
   ITRF    : 0x02,
   GEOID99 : 0x04,
   GGM02   : 0x08
};

function pvt1b_obj()
{
   var MsgName = "[PVT1B]"
   
   this.update = function(input)
   {
      var start = input.indexOf(MsgName);

      //don't start parsing if this is not the right message
      if(start < 0)
      {
         return false;
      }
      this.name = MsgName;

      //init the parser
      var p = new parser(input, start + MsgName.length);

      //get the pvt1b info
      this.length     = p.fetchU16();
      this.week       = p.fetchU16();
      this.TOWms      = p.fetchU32();
      this.TOW        = this.TOWms/1000.0;

      this.ver        = p.fetchU04();
      this.conf       = p.fetchU04();
      this.status     = p.fetchHex08();
      
      this.navValid   = (this.status & 1) ? true : false;
      this.setApplied = (this.status & 2) ? true : false;
      this.is3D       = (this.status & 4) ? true : false;
      this.isDual     = (this.status & 8) ? true : false;
      this.datum      = (this.status & 0xF0) >> 4;

      this.lat        = p.fetchS32() / 2048.0 / 60.0 / 60.0;
      this.lon        = p.fetchS32() / 2048.0 / 60.0 / 60.0;
      this.llLSB      = p.fetchU08() / 32768;
      this.ht         = p.fetchS32() / 1000.0;
      this.latd       = Math.floor( Math.abs(this.lat) );
      this.lond       = Math.floor( Math.abs(this.lon) );
      lat_min_sec     = (Math.abs(this.lat)%1)*60.0;
      lon_min_sec     = (Math.abs(this.lon)%1)*60.0;
      this.latm       = Math.floor(lat_min_sec);
      this.lonm       = Math.floor(lon_min_sec);
      this.lats       = (lat_min_sec%1)*60.0;
      this.lons       = (lon_min_sec%1)*60.0;
      if(this.lat > 0)
         this.latdir = 'N';
      else
         this.latdir = 'S';
      if(this.lon > 0)
         this.londir = 'E';
      else
         this.londir = 'W';
      this.standardLat = this.latdir + ' ' + this.latd + '&#176 ' + this.latm + "' " + this.lats.toFixed(4) + '"';
      this.standardLon = this.londir + ' ' + this.lond + '&#176 ' + this.lonm + "' " + this.lons.toFixed(4) + '"';
         
      this.geoid      = p.fetchS24() / 1024.0;
      this.latstd     = p.fetchU16() / 1024.0;
      this.lonstd     = p.fetchU16() / 1024.0;
      this.htstd      = p.fetchU16() / 1024.0;
      
      this.alt        = this.ht - this.geoid;

      this.NDOP       = p.fetchU08() / 10.0;
      this.EDOP       = p.fetchU08() / 10.0;
      this.UDOP       = p.fetchU08() / 10.0;
      this.VDOP       = this.UDOP;
      this.HDOP       = Math.sqrt( Math.pow(this.NDOP, 2) + Math.pow(this.EDOP, 2) );
      this.PDOP       = Math.sqrt( Math.pow(this.NDOP, 2) + Math.pow(this.EDOP, 2) + Math.pow(this.UDOP, 2) );

      this.velN       = p.fetchS24() / 1024.0;
      this.velE       = p.fetchS24() / 1024.0;
      this.velU       = p.fetchS24() / 1024.0;
      this.groundSpd  = Math.sqrt( Math.pow(this.velN, 2) + Math.pow(this.velE, 2) );
      
      this.groundTrck = (360 + Math.atan2( this.velE, this.velN ) * 180/Math.PI) % 360;
      
      this.numsat     = p.fetchU08();
      this.navmode    = p.fetchU04();
      this.source     = p.fetchU04();

      this.corrAge    = p.fetchU16() / 10.0;
      this.baseID     = p.fetchU16();

      this.FOM        = p.fetchU08();
      this.failcode   = p.fetchU08();
      
      this.tideN      = p.fetchS16();
      this.tideE      = p.fetchS16();
      this.tideU      = p.fetchS16();

      this.gnss_usage = p.fetchU08();
      if ( this.gnss_usage & 0x01 )//GPS constellation is set
      {
         rev1_gps_pos       = p.fetchU32();
         rev1_gps_vel       = p.fetchU32();
         this.satsUsedGPS_P = GetNumberOfSvs(rev1_gps_pos);
         this.satsUsedGPS_V = GetNumberOfSvs(rev1_gps_vel);
         this.isGPS         = true;
      }
      else
      {
         rev1_gps_pos       = 0;
         rev1_gps_vel       = 0;
         this.satsUsedGPS_P = 0;
         this.satsUsedGPS_V = 0;
         this.isGPS         = false;
      }
      if ( this.gnss_usage & 0x02 )//GLONASS constellation is set
      {
         rev1_glonass_pos   = p.fetchU32();
         rev1_glonass_vel   = p.fetchU32();
         this.satsUsedGLN_P = GetNumberOfSvs(rev1_glonass_pos);
         this.satsUsedGLN_V = GetNumberOfSvs(rev1_glonass_vel);
         this.isGLN         = true;
      }
      else
      {
         rev1_glonass_pos   = 0;
         rev1_glonass_vel   = 0;
         this.satsUsedGLN_P = 0;
         this.satsUsedGLN_V = 0;
         this.isGLN         = false;
      }
      if ( this.gnss_usage & 0x10 )//WAAS constellation is set
      {
         rev1_waas_pos       = p.fetchU32();
         rev1_waas_vel       = p.fetchU32();
         this.satsUsedSBAS_P = GetNumberOfSvs(rev1_waas_pos);
         this.satsUsedSBAS_V = GetNumberOfSvs(rev1_waas_vel);
         this.isSBAS         = true;
      }
      else
      {
         rev1_waas_pos       = 0;
         rev1_waas_vel       = 0;
         this.satsUsedSBAS_P = 0;
         this.satsUsedSBAS_V = 0;
         this.isSBAS         = false;
      }
      
      this.status2 = p.fetchU08();
      this.isDoppler  = (this.status2 & 0x01) ? true : false;
      this.isMBRTK    = (this.status2 & 0x02) ? true : false;      
      this.isGeoFence = (this.status2 & 0x04) ? true : false;
      this.isATDisEng = (this.status2 & 0x08) ? true : false;
      this.isSFX      = (this.status2 & 0x10) ? true : false;
      
      // Get the Navmode Text according to navmode and source
      var navModeText = "";
      var type = "NA";
      switch(this.navmode)
      {
      case NAVMODE.NONDIFFERENTIAL:  navModeText = "Non-Diff";       type = "NA";    break;
      case NAVMODE.DGPS:             navModeText = "DGPS:";
         switch(this.source)
         {
         case DGPS.WAAS:            navModeText += "WAAS";           type = "SBAS";  break;
         case DGPS.WAAS_TESTMODE:   navModeText += "WAAS_TESTMODE";  type = "SBAS";  break;
         case DGPS.STARFIRE:        navModeText += "GPS";            type = "SF";    break;
         case DGPS.RTCM1:           navModeText += "RTCM Msg 1";     type = "RTCM";  break;
         case DGPS.RTCM9:           navModeText += "RTCM Msg 9";     type = "RTCM";  break;
         case DGPS.EGNOS:           navModeText += "EGNOS";          type = "SBAS";  break;
         case DGPS.GAGAN:           navModeText += "GAGAN";          type = "SBAS";  break;
         case DGPS.STARFIRE_GLN:    navModeText += "GNSS";           type = "SF";    break;
         default:                   navModeText += "Unknown";        type = "NA";    break;
         }
         break;
      case NAVMODE.STARFIRE_DUAL:  navModeText = "StarFire Dual:";   type = "SF";
         switch(this.source)
         {
         case DGPS.STARFIRE:        navModeText += "GPS";            break;
         case DGPS.STARFIRE_GLN:    navModeText += "GNSS";           break;
         default:                   navModeText += "Unknown";        break;
         }
         break;
      case NAVMODE.STARFIRE_DEGRADE:  navModeText = "StarFire LP:";  type = "SF";
         switch(this.source)
         {
         case DGPS.STARFIRE:        navModeText += "GPS";            break;
         case DGPS.STARFIRE_GLN:    navModeText += "GNSS";           break;
         default:                   navModeText += "Unknown";        break;
         }
         break;
      case NAVMODE.RTK_X:  navModeText = "RTK-X:";                   type = "RTK";
         switch(this.source)
         {
         case DGPS.STARFIRE:        navModeText += "GPS";            break;
         case DGPS.STARFIRE_GLN:    navModeText += "GNSS";           break;
         default:                   navModeText += "Unknown";        break;
         }
         break;;
      case NAVMODE.RTK_FLOAT:      navModeText = "RTK-Float:"      + GetRTKSourceType(this.source);  type = "RTK";  break;
      case NAVMODE.RTK_WLFIXED:    navModeText = "RTK-WL Fixed:"   + GetRTKSourceType(this.source);  type = "RTK";  break;
      case NAVMODE.RTK_L1FIXED:    navModeText = "RTK-L1 Fixed:"   + GetRTKSourceType(this.source);  type = "RTK";  break;
      case NAVMODE.RTK_DUALFIXED:  navModeText = "RTK-Dual Fixed:" + GetRTKSourceType(this.source);  type = "RTK";  break;
      default: navModeText = "NA " + this.navmode;                   type = "NA";    break;
      }
      this.navmodeStr = navModeText;
      this.type       = type;
      
      // Get the Datum Text according to datum nibble
      if ( DATUM.USERDEF == (this.datum & DATUM.USERDEF))
         this.datumStr = "User Datum";
      else if ( DATUM.ITRF == (this.datum & DATUM.ITRF))
      {
         if (this.source & DGPS.STARFIRE)
         {
            this.datumStr = "ITRF";
         }
         else
         {
            this.datumStr = "ITRF"; // assuming sf 2.5 is ITRF-08 after Nov 2011
         }
      }
      else
         this.datumStr = "WGS 84";

      if ( DATUM.GEOID99 == (this.datum & DATUM.GEOID99))
         this.datumStr += " / GEOID99";
      else if ( DATUM.GGM02 == (this.datum & DATUM.GGM02))
         this.datumStr += " - GGM02";
      else
         this.datumStr += " / No Geoid Model";

      return true;
   }
   
   //takes an int and returns the number of high bits
   GetNumberOfSvs = function(sats)
   {
      var totalSats = 0;
      //32, assuming an int input
      for(i = 0; i<32; i++)
      {
         if( (sats>>i) & 1 )
         {
            totalSats++;
         }
      }
      return totalSats;
   }
   GetRTKSourceType = function(source)
   {
      var sourceStr = "";
      switch ( source )
      {
      case RTK.BASE5B:              sourceStr = "5B";                   break;
      case RTK.BASE5E:              sourceStr = "5E";                   break;
      case RTK.RTCM1819:            sourceStr = "RTCM1819";             break;
      case RTK.RTCM2021:            sourceStr = "RTCM2021";             break;
      case RTK.CMR:                 sourceStr = "CMR";                  break;
      case RTK.CMRPLUS:             sourceStr = "CMR Plus";             break;
      case RTK.RTCM3_L1COMPACT:     sourceStr = "RTCM3-L1 Compact";     break;
      case RTK.RTCM3_L1FULL:        sourceStr = "RTCM3-L1 Full";        break;
      case RTK.RTCM3_DUALCOMPACT:   sourceStr = "RTCM3-Dual Compact";   break;
      case RTK.RTCM3_DUALFULL:      sourceStr = "RTCM3-Dual Full";      break;
      default:                      sourceStr = "Unknown";              break;
      }
      return sourceStr;
   }
}