function Signal()
{
   this.valid;
   this.cpValid;
   this.dpValid;
   this.channel;
   this.prn;
   this.measType;
   this.halfCycleAdj;
   this.cphQuality ;
   this.cno;
   this.cycleSlipCount;
   this.pseudorange;
   this.carrierPhase;
   this.doppler;
   this.constType;
}
function GPSDataStore()
{
   this.PRN                    = null;
   this.COLUMN_CA              = null;
   this.COLUMN_L1CA            = null;
   this.COLUMN_P1CA            = null;
   this.COLUMN_P2CA            = null;
   this.COLUMN_L2CA            = null;
   this.COLUMN_L2CCA           = null;
   this.COLUMN_L2CCCA          = null;
   this.COLUMN_CACNO           = null;
   this.COLUMN_P2CNO           = null;
   this.COLUMN_L1CADOPPLER     = null;
   this.COLUMN_L2PDOPPLER      = null;
   this.COLUMN_L5QCA           = null;
   this.COLUMN_L5CA            = null;
   this.COLUMN_L5QCNO          = null;
   this.COLUMN_L5DOPPLER       = null;
}
function GLNDataStore()
{
   this.PRN                    = null;
   this.COLUMN_G1C             = null;
   this.COLUMN_L1C_GLO         = null;
   this.COLUMN_L1P_GLO         = null;
   this.COLUMN_P1_GLO          = null;
   this.COLUMN_P2_GLO          = null;
   this.COLUMN_G2C             = null;
   this.COLUMN_L2_GLO          = null;
   this.COLUMN_G1CCNO          = null;
   this.COLUMN_G2CCNO          = null;
   this.COLUMN_G1DOPPLER       = null;
   this.COLUMN_G2DOPPLER       = null;
   this.COLUMN_P1DOPPLER       = null;
   this.COLUMN_P2DOPPLER       = null;
}

var SPEED_OF_LIGHT            = 299792458.0;
var L1_CARRIER_FREQUENCY      = 1575420000.0;
var L2_CARRIER_FREQUENCY      = 1227600000.0;
var L5_CARRIER_FREQUENCY      = 1176450000.0;
var L1_LAMBDA                 = (SPEED_OF_LIGHT/L1_CARRIER_FREQUENCY);
var L2_LAMBDA                 = (SPEED_OF_LIGHT/L2_CARRIER_FREQUENCY);
var L5_LAMBDA                 = (SPEED_OF_LIGHT/L5_CARRIER_FREQUENCY);

var FREQTYPE =
{
   L1_FREQ :0,
   L2_FREQ :1,
   L5_FREQ :2
};
var MEASTYPE =
{
   INVD_MEAS_TYPE : -1,
   L1_CA          : 0, // CA on L1
   L1_P           : 1,  // P on L1
   L2_P           : 2,  // P on L2
   L2_C           : 3,  // C on L2 (do we need differnet types for L2CM and L2CL?)
   L5_Q           : 4,  // L5 Q
   L5_I           : 5,  // L5 I
   L1_GLO         : 6, // G1C
   L2_GLO         : 7,
   P1_GLO         : 8, //G1P
   P2_GLO         : 9, //G2P
   L1_E1B         : 10
};

GLONASS_WAVELENGTHS = [
    // G1               PRN FreId
    [
    0.18713636579276   //1:(00)
   ,0.18739956743241   //2:(01) =>(-4)
   ,0.18680840160455   //3:(05)
   ,0.18713636579276   //4:(06) =>(00)
   ,0.18713636579276   //5:(00)
   ,0.18739956743241   //6:(01) =>(-4)
   ,0.18680840160455   //7:(05)
   ,0.18674294666355   //8:(06)
   ,0.18726787413133   //9:(-2)
   ,0.18759745504322   //10:(04) =>(-7)
   ,0.18713636579276   //11:(00)
   ,0.18713636579276   //12:(00)
   ,0.18726787413133   //13:(-2)
   ,0.18759745504322   //14:(04) =>(-7)
   ,0.18713636579276   //15:(00)
   ,0.18713636579276   //16:(00)
   ,0.18687390244663	  //17:(-1) =>(04)
   ,0.18733369763718	  //18:(-3)
   ,0.18693944923808	  //19:(03)
   ,0.18700504202729	  //20:(02)
   ,0.18687390244663	  //21:(-1) =>(04)
   ,0.18733369763718	  //22:(-3)
   ,0.18693944923808	  //23:(03)
   ,0.18700504202729	  //24:(02)
   ]
   // G2
   ,[
    0.24060389887640   //1:(00)
   ,0.24094230098453   //2:(01) =>(-4)
   ,0.24018223063442   //3:(05) 
   ,0.24060389887640   //4:(06) =>(00)
   ,0.24060389887640   //5:(00) 
   ,0.24094230098453   //6:(01) =>(-4)
   ,0.24018223063442   //7:(05)
   ,0.24009807428171   //8:(06)
   ,0.24077298102600   //9:(-2)
   ,0.24119672791271   //10:(04) =>(-7)
   ,0.24060389887640   //11:(00)
   ,0.24060389887640   //12:(00)
   ,0.24077298102600   //13:(-2)
   ,0.24119672791271   //14:(04) =>(-7)
   ,0.24060389887640   //15:(00)
   ,0.24060389887640   //16:(00)
   ,0.24026644600281	  //17:(-1) =>(04)
   ,0.24085761124780	  //18:(-3)
   ,0.24035072044897	  //19:(03)
   ,0.24043505403509	  //20:(02)
   ,0.24026644600281	  //21:(-1) =>(04)
   ,0.24085761124780	  //22:(-3)
   ,0.24035072044897	  //23:(03)
   ,0.24043505403509	  //24:(02)
   ]
    // Narrow Lane
   ,[
    0.10526420575843   //1:(00)
   ,0.10541225668073   //2:(01) =>(-4)
   ,0.10507972590256   //3:(05) 
   ,0.10526420575843   //4:(06) =>(00)
   ,0.10526420575843   //5:(00) 
   ,0.10541225668073   //6:(01) =>(-4)
   ,0.10507972590256   //7:(05)
   ,0.10504290749825   //8:(06)
   ,0.10533817919888   //9:(-2)
   ,0.10552356846181   //10:(04)  =>(-7)
   ,0.10526420575843   //11:(00)
   ,0.10526420575843   //12:(00)
   ,0.10533817919888   //13:(-2)
   ,0.10552356846181   //14:(04)  =>(-7)
   ,0.10526420575843   //15:(00)
   ,0.10526420575843   //16:(00)
   ,0.10511657012623	  //17:(-1)  =>(04)
   ,0.10537520492091	  //18:(-3)
   ,0.10515344019642	  //19:(03)
   ,0.10519033614035	  //20:(02)
   ,0.10511657012623	  //21:(-1) =>(04)
   ,0.10537520492091	  //22:(-3)
   ,0.10515344019642	  //23:(03)
   ,0.10519033614035	  //24:(02)
   ]
];

function ISGPS(id)       { return (id >= 1) && (id <= 37); }
function ISGLONASS(id)   { return (id >= 37) && (id <= ( 37 + 24 )); }
function ISGALILEO(id)   { return (id >= 37 + 24) && (id <= (37 + 24 + 50)); }
function ISSBAS(id)      { return (id >= 120) && (id <= 138); }

function meas1b_obj()
{
   var MsgName = "[MEAS1B]"
   
   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 meas1b general info
      this.length     = p.fetchU16();
      this.week       = p.fetchU16();
      this.TOWms      = p.fetchU32();
      this.TOW        = this.TOWms/1000;
      this.ver        = p.fetchU04();
      this.conf       = p.fetchU04();
      
      this.numSv      = p.fetchU08();
      
      //initialize variables
      measNum = 0;
      chnlNum = 0;
      this.gpsDataStore = new Array(32);
      this.glnDataStore = new Array(24);
      
      //********** begin parsing each byte of meas1b function ************//
      //itterate through all the available Satellites in the message
      for(i=0; i<this.numSv; i++)
      {
         svId = p.fetchU08();
         u32 = p.fetchU32();
         dwNumCode    = ((u32 & 0xF0000000)>>28);     //4 bits
         fCoarseRange = (u32 & 0xFFFFE00) >>9;  //19 bits
         fCoarseRange *= 100.;
         fCoarseDoppler = (u32 & 0x1FF);        //9 bits
         if(fCoarseDoppler > 0xFF)
            fCoarseDoppler -= 0x200;
         fCoarseDoppler *= 10.;
         
         if(ISGPS(svId))
         {
            prn = svId;
            dwSatConst = "GPS";
         }
         else if(ISGLONASS(svId))
         {
            prn = svId - 37;
            dwSatConst = "GLONASS";
         }
         else if(ISGALILEO(svId))
         {
            prn = svId - (37 + 24);
            dwSatConst = "GALILEO";
         }
         else if(ISSBAS(svId))
         {
            prn = svId; //not shift
            dwSatConst = "SBAS";
         }
         
         for(j=0; j<dwNumCode; j++)
         {               
            u16 = p.fetchU16();
            
            if (this.ver == 1 )
            {
               // revision 1:
               // U16 
               // U03 Frequency Number
               // U03 Code Type
               // U06 CN0
               // U04 Cycle Slip Count
               freqType = ((u16 & 0xE000)>>13);    //3 bits frequency number
               codeType = ((u16 & 0x1C00)>>10);   // 3 bits code type
               cphQual  = 0;
               halfCycleAdj = 0;
            }
            else if (this.ver >= 2 )
            {
               // revision 2:
               // U16 
               // U01 Half-Cycle Adjusted
               // U02 Frequency Number
               // U01 CPH Quality
               // U02 Code Type
               // U06 CN0
               // U04 Cycle Slip Count
               halfCycleAdj = ((u16 & 0x8000)>>15);
               freqType     = ((u16 & 0x6000)>>13);
               cphQual      = ((u16 & 0x1000)>>12);
               codeType     = ((u16 & 0x0C00)>>10);
            }
         
            
            cno            = ((u16 & 0x3F0 )>>4);    // 6 bits cno
            cycleSlip      = (u16 & 0xF);           //cycle clip count

            u16 = p.fetchU16();             //delta range
            if(u16 != 0x8000)
            {
               prValid = 1;
               if(u16 > 0x8000)
                  delta_range = u16 - 0x10000;
               else
                  delta_range = u16;
            }
            else
            {
               prValid = 0;
            }
            
            u32 = p.fetchU32();
            u32Tmp = (u32 & 0xFFFFF000)>>12;  //delta phase
            if(u32Tmp != 0x80000)
            {
               cpValid = 1;
               if(u32Tmp > 0x80000)
                  delta_phase = u32Tmp - 0x100000;
               else
                  delta_phase = u32Tmp;
            }
            else
            {
               cpValid = 0;
            }
            u32Tmp =  (u32 & 0xFFF);              //delta doppler
            if(u32Tmp != 0x800)
            {
               dpValid = 1;
               if(u32Tmp > 0x800)
                  delta_doppler= u32Tmp - 0x1000;
               else
                  delta_doppler = u32Tmp;
            }
            else
            {
               dpValid = 0;
            }
            
            measType = 0xFF;         //init with a non-meas type
            if(dwSatConst == "GPS" )
            {
               switch ( freqType )
               {
               case FREQTYPE.L1_FREQ:
                  fWaveLength = L1_LAMBDA;
                  switch ( codeType )
                  {
                  case 0: measType = MEASTYPE.L1_CA; break;
                  case 1: measType = MEASTYPE.L1_P;  break;
                  }
                  break;
               case FREQTYPE.L2_FREQ:
                  fWaveLength = L2_LAMBDA;
                  switch ( codeType )
                  {
                  case 0: measType = MEASTYPE.L2_C; chnlNum++; break;
                  case 1: measType = MEASTYPE.L2_P; chnlNum++; break;
                  }
                  break;
               case FREQTYPE.L5_FREQ:
                  fWaveLength = L5_LAMBDA;
                  switch ( codeType )
                  {
                  case 0: measType = MEASTYPE.L5_Q; chnlNum++; break;
                  case 1: measType = MEASTYPE.L5_I; chnlNum++; break;
                  }
                  break;
               }
            } //end if GPS
            else if( dwSatConst == "GLONASS" )
            {
               switch ( freqType )
               {
               case FREQTYPE.L1_FREQ:
                  fWaveLength = GLONASS_WAVELENGTHS[FREQTYPE.L1_FREQ][prn - 1];
                  switch ( codeType )
                  {
                  case 0: measType = MEASTYPE.L1_GLO; break;   //g1c
                  case 1: measType = MEASTYPE.P1_GLO; break;    //g1p
                  }
                  break;
               case FREQTYPE.L2_FREQ:
                  fWaveLength = GLONASS_WAVELENGTHS[FREQTYPE.L2_FREQ][prn - 1];
                  switch ( codeType )
                  {
                  case 0: measType = MEASTYPE.L2_GLO; chnlNum++; break;//g2c
                  case 1: measType = MEASTYPE.P2_GLO; chnlNum++; break;     //g2p
                  }
                  break;
               }
            } //end if GLONASS
            else if ( dwSatConst == "SBAS" )   //not support L5 yet
            {
               switch ( freqType )
               {
               case FREQTYPE.L1_FREQ:
                  fWaveLength = L1_LAMBDA;
                  switch ( codeType )
                  {
                  case 0: measType = MEASTYPE.L1_CA; break;
                  }
                  break;
               }
            }

            //if non meas type match, continue
            if( measType == 0xFF )
            {
               continue;
            }
            //recover the ranges,phases and dopplers
            if( prValid )
            {
               recovered_range = fCoarseRange + delta_range * 0.01;
               if( cpValid )
                  recovered_phase = delta_phase/256.0 + recovered_range/fWaveLength; //cycles
               else
                  recovered_phase = 0;
            }
            else
            {
               recovered_range = 0;
            }
            if( dpValid )
            {
               recovered_doppler = (delta_doppler * 0.005 + fCoarseDoppler)/fWaveLength;   //change the unit to be cycles/s
            }
            else
            {
               recovered_doppler = 0;
            }
            
            signal = new Signal();
            signal.valid = prValid;
            signal.cpValid = cpValid;
            signal.dpValid = dpValid;
            signal.channel = chnlNum;     //do not have this field any more, fill the measurement number here
            signal.prn = prn;
            signal.measType = measType;
            // nedded for revision 2 and MEAS3B
            signal.halfCycleAdj = halfCycleAdj;
            signal.cphQuality = cphQual;
            
            signal.cno = cno;
            signal.cycleSlipCount = cycleSlip;
            signal.pseudorange = recovered_range;
            signal.carrierPhase = recovered_phase;
            signal.doppler =  recovered_doppler;//
            signal.constType = dwSatConst; //
            measNum ++;
            
            if( dwSatConst == "GPS" )
               this.organizeGPSData(signal);
            else if( dwSatConst == "GLONASS" )
               this.organizeGLNData(signal);
         }
         chnlNum++;
      }
      this.measNum = measNum;
      
      return true;
   }
   
   // pass 1 - collect the data into the tables, indexed
   // by prn number, allowing us to collect all data for
   // one prn into one row, and also to sort the rows by prn
   this.organizeGPSData = function(signal)
   {//********** begin to organize the GPS data into columns ************//
      measType = signal.measType;
      
      //***********************  GPS   *************************/
      if ( measType == MEASTYPE.L1_CA || measType == MEASTYPE.L1_P  ||
           measType == MEASTYPE.L2_P  || measType == MEASTYPE.L2_C  ||
           measType == MEASTYPE.L5_Q
         )
      {
         prn = signal.prn;
         if(prn >= 1 && prn <= 32)  //if it is GPS
         {
            idx = prn - 1;
            if(this.gpsDataStore[idx] == null)
               this.gpsDataStore[idx] = new GPSDataStore();
            
            this.gpsDataStore[idx].PRN = prn;
            
            pseudorange    = signal.pseudorange;
            carrier_phase  = signal.carrierPhase;

            valid = signal.valid;

            if ( measType == MEASTYPE.L1_CA)
            {
               this.gpsDataStore[idx].COLUMN_CA = pseudorange;

               if (signal.cpValid )
               {
                  this.gpsDataStore[idx].COLUMN_L1CA = carrier_phase * L1_LAMBDA - pseudorange;

                  if (signal.cphQuality != 0)
                  {
                     this.gpsDataStore[idx].COLUMN_L1CA_CPHQUALITY = true; //??
                  }
                  if (signal.halfCycleAdj != 0)
                  {
                     this.gpsDataStore[idx].COLUMN_L1CA_HALFCYCLEADJ = true; //??
                  }
               }

               if ( signal.valid )
                  m_nPseudorangeGps = pseudorange;

               this.gpsDataStore[idx].COLUMN_CACNO = signal.cno;
               this.gpsDataStore[idx].COLUMN_L1CADOPPLER = signal.doppler;
            }
            else if ( measType == MEASTYPE.L1_P)
            {
               if ( m_nPseudorangeGps > 0 )
               {
                  this.gpsDataStore[idx].COLUMN_P1CA = pseudorange - m_nPseudorangeGps;
               }
            }
            else if ( measType == MEASTYPE.L2_P)
            {
               if ( m_nPseudorangeGps > 0 )
               {
                  this.gpsDataStore[idx].COLUMN_P2CA = pseudorange - m_nPseudorangeGps;
                  if(signal.cpValid )
                  {
                     this.gpsDataStore[idx].COLUMN_L2CA = carrier_phase* L2_LAMBDA - m_nPseudorangeGps;

                     if (signal.cphQualit != 0)
                     {
                        this.gpsDataStore[idx].COLUMN_L2CA_CPHQUALITY = true;
                     }
                  }
               }
               this.gpsDataStore[idx].COLUMN_P2CNO = signal.cno;
               this.gpsDataStore[idx].COLUMN_L2PDOPPLER = signal.doppler;
            }
            else if ( measType == MEASTYPE.L2_C)
            {
               if ( m_nPseudorangeGps > 0 )
               {
                  this.gpsDataStore[idx].COLUMN_L2CCA = pseudorange - m_nPseudorangeGps;
                  if(signal.cpValid )
                  {
                     this.gpsDataStore[idx].COLUMN_L2CCCA = carrier_phase *L2_LAMBDA - m_nPseudorangeGps;
                  }
               }
            }
            else if ( measType == MEASTYPE.L5_Q )
            {
               if ( m_nPseudorangeGps > 0 )
               {
                  this.gpsDataStore[idx].COLUMN_L5QCA = pseudorange - m_nPseudorangeGps;

                  if (signal.cphQuality != 0)
                  {
                     this.gpsDataStore[idx].COLUMN_L5QCA_CPHQUALITY = true;
                  }
             
                  if(signal.cpValid )
                  {
                     this.gpsDataStore[idx].COLUMN_L5CA = carrier_phase *L5_LAMBDA - m_nPseudorangeGps;
                  }
                  this.gpsDataStore[idx].COLUMN_L5QCNO = signal.cno;
                  this.gpsDataStore[idx].COLUMN_L5DOPPLER = signal.doppler;
               }
            }
         }
      }
   }
   
   this.organizeGLNData = function(signal)
   {//********** begin to organize the GPS data into columns ************//
      measType = signal.measType;
      
      //***********************  GLN  *************************/
      if ( measType == MEASTYPE.L1_GLO || measType == MEASTYPE.L2_GLO ||
           measType == MEASTYPE.P1_GLO || measType == MEASTYPE.P2_GLO )
      {
         prn = signal.prn;
         if (prn >= 1 && prn <= 24)
         {
            idx = prn - 1;
            prange  = signal.pseudorange;
            phase   = signal.carrierPhase;
            cn0     = signal.cno;
            doppler = signal.doppler;

            prValid = signal.valid;
            cpValid = signal.cpValid;
            dpValid = signal.dpValid;

            valid = true;
            prn = prn;
            prnGreen = signal.valid;
            Type = measType;
            
            if(this.glnDataStore[idx] == null)
               this.glnDataStore[idx] = new GLNDataStore();
            
            this.glnDataStore[idx].PRN = prn;
            
            if ( measType == MEASTYPE.L1_GLO)
            {
               this.glnDataStore[idx].COLUMN_G1C = prange;
               this.glnDataStore[idx].COLUMN_L1C_GLO = (phase * GLONASS_WAVELENGTHS[FREQTYPE.L1_FREQ][prn - 1] - prange);

               if (signal.cphQuality != 0)
               {
                  this.glnDataStore[idx].COLUMN_L1C_GLO_CPHQUALITY = true;
               }
               if (signal.halfCycleAdj != 0)
               {
                  this.glnDataStore[idx].COLUMN_L1C_GLO_HALFCYCLEADJ = true;
               }
               this.glnDataStore[idx].COLUMN_G1CCNO = cn0;
               this.glnDataStore[idx].COLUMN_G1DOPPLER = doppler;

               if( signal.valid )
                  m_nPseudorangeGLN = prange; 
            }
            else if ( measType == MEASTYPE.L2_GLO)
            {
               if(m_nPseudorangeGLN>0)
               {
                  this.glnDataStore[idx].COLUMN_G2C = prange - m_nPseudorangeGLN;
                  this.glnDataStore[idx].COLUMN_L2_GLO = (phase * GLONASS_WAVELENGTHS[FREQTYPE.L2_FREQ][prn - 1] - m_nPseudorangeGLN);

                  if (signal.cphQuality != 0)
                  {
                     this.glnDataStore[idx].COLUMN_L2_GLO_CPHQUALITY = true;
                  }

                  this.glnDataStore[idx].COLUMN_G2CCNO = cn0;
                  this.glnDataStore[idx].COLUMN_G2DOPPLER = doppler;
               }
            }
            else if ( measType == P1_GLO)
            {
               if(m_nPseudorangeGLN>0)
               {
                  this.glnDataStore[idx].COLUMN_L1P_GLO = (phase * GLONASS_WAVELENGTHS[FREQTYPE.L1_FREQ][prn - 1] - prange);
                  this.glnDataStore[idx].COLUMN_P1_GLO = prange - m_nPseudorangeGLN;
                  this.glnDataStore[idx].COLUMN_P1DOPPLER = doppler;
               }
            }
           else if ( measType == P2_GLO)
           {
                if(m_nPseudorangeGLN>0)
               {
                  this.glnDataStore[idx].COLUMN_P2_GLO = prange - m_nPseudorangeGLN;
                  this.glnDataStore[idx].COLUMN_P2DOPPLER = doppler;
               }
            }

         }
      }
   }
}