###############################################################################
# Copyright (c) 2010-2025 Trimble Inc.
###############################################################################
#
# NoPiDI_Web_Page.py
#
# Class that contains all the functions needed to generate the result web page
#
# Three web page files are created to handle the top-level and two main frames
# used in the display
#
# The web page contains JavaScript functions that are imported from any file
# The data used by the JavaScript functions is generated here
#
###############################################################################


from NoPiDI_javascript_func import *
from NoPiDI_Utils import add_utils_dir_to_path
add_utils_dir_to_path()

from NoPiUT_Common_Meas import convert_sat_type, convert_sub_type
from NoPiUT_Common_Display import get_meas_units
from NoPiUT_Const import *

###############################################################################
# Class of functions used to generate the results web page
###############################################################################

class cl_web_page :

  #############################################################################
  # Variables local to the class
  # Used to ensure that only a single web page is being generated
  # These are used by the menu frame
  #############################################################################

  num_inst = 0
  f_menu = None


  #############################################################################
  # Initialise the web page creatation
  # Ensure that only a single web page can be generated
  #############################################################################

  def __init__( self, diffs_summ, meas_stats, acq_stats ) :
    if ( cl_web_page.num_inst == 0 ) :
      cl_web_page.num_inst = 1
      self.create_top_level( diffs_summ, meas_stats, acq_stats )
      self.create_js_include( diffs_summ, meas_stats, acq_stats )
      self.create_menu_frame()
      self.create_results_frame()


  #############################################################################
  # Create the top-level web page
  # Contains: all the JavaScript functions
  #           all the JavaScript data
  #           the lower frame definitions
  #############################################################################

  def create_top_level( self, diffs_summ, meas_stats, acq_stats ) :
    f_top = open( 'NoPiDI_Top.html', 'wt' )
    f_top.write( """\
<html>
<head>
<script>
  function receiveMessage(event) {
    //console.log(event.data);
    if (event.data=="url?") {
      // Special event, results frame requesting the full URL of the parent
      // This is used to decode any parameters in the URL
      // Also send to the menu frame to creating the web page links

      //console.log("Top-url");
      window.frames.res_frame.postMessage({url:window.location.href},"*");
      window.frames.menu_frame.postMessage({url:window.location.href},"*");
    }
    else {
      //console.log("Top-other");
      window.frames.res_frame.postMessage(event.data,"*");
    }
  }
  window.addEventListener("message",receiveMessage,false) ;
</script>
<style>
  /* Define the style/size of the menu and results frames
   * Use 'float' to stop scroll bars etc. when using 100% of the display
   */
  .menu {
    float:left;
    width:20%;
    height:100%;
    border:0;
  }
  .results {
    float:left;
    width:80%;
    height:100%;
    border:0;
  }
</style>
<title>NoPi Processing Results</title>
</head>
<iframe src="web/NoPiDI_Menu.html" name=menu_frame class="menu"></iframe>
<iframe src="web/NoPiDI_Results.html" name=res_frame class="results"></iframe>
<body id=top_body bgcolor="#fff5e4">
</body>
</html>
""")

    f_top.close()

  #############################################################################
  # Create file with main Javascript code
  #############################################################################
  def create_js_include( self, diffs_summ, meas_stats, acq_stats ) :
    f_top = open( 'web/NoPiDI_funcs.js', 'wt' )
    self.include_javascript_functions( f_top )
    self.include_javascript_globals( f_top, diffs_summ, meas_stats )
    self.include_javascript_globals_acq( f_top, diffs_summ, acq_stats )
    f_top.close()

  #############################################################################
  # Read the JavaScript functions from the dedicated file and include in the
  # web page
  #############################################################################

  def include_javascript_functions( self, f_top ) :

    file_data = get_javascript_functions( )
    f_top.write( file_data )

    # Use this line instead when developing/debugging the JavaScript from
    # the code in NoPiDI_javascript.js
    #f_top.write( '<script language="JavaScript" src="tst.js"></script>\n' )


  #############################################################################
  # Generate the data structures used by the JavaScript
  # This is basically a copy of the measurement statistics for each measurement
  # type and signal combo
  #############################################################################

  def include_javascript_globals( self, f_top, diffs_summ, meas_stats ) :
    # Include the copyright string in the JavaScript data

    f_top.write( '  var cright_str = \"'
                 + self.inc_copyright()
                 + '\";\n' )

    # Describe the measurement types

    f_top.write( '  var num_meas = ' + str( cNoPiConst.NUM_MEAS_TYPE ) + '\n' )
    f_top.write( '  var dd_carr = ' + str( cNoPiConst.MEAS_TYPE_DD_CARR ) + '\n' )
    f_top.write( '  var dd_code = ' + str( cNoPiConst.MEAS_TYPE_DD_CODE ) + '\n' )
    f_top.write( '  var dd_dopp = ' + str( cNoPiConst.MEAS_TYPE_DD_DOPP ) + '\n' )
    f_top.write( '  var sd_cno = ' + str( cNoPiConst.MEAS_TYPE_SD_CNO ) + '\n' )
    f_top.write( '  var dist_plts = ' + str( cNoPiConst.DI_DIST_PLOTS ) + '\n' )
    f_top.write( '  var acq_plts = ' + str( cNoPiConst.DI_ACQ_PLOTS ) + '\n' )
    f_top.write( '  var cno_elev_plts = ' + str( cNoPiConst.DI_CNO_EL_PLOTS ) + '\n' )
    # 0 - D.D. Carrier, 1 - D.D. Code are always valid
    f_top.write( '  var vld_meas = [dd_carr, dd_code,' )
    ext_format = diffs_summ.file_format - 1
    # 2 - D.D. Doppler is only valid for some file formats
    if ( ext_format & cNoPiConst.DIFFS_FMT_DOPPLER ) :
      f_top.write( ' dd_dopp,' )
    # 3 - S.D. CNo is always valid
    f_top.write( ' sd_cno]\n' )

    # Short-cut array of valid signal combos

    found = 0
    f_top.write( '  var vld_combos = [' )
    for combo in range( diffs_summ.num_combos ) :
      if ( diffs_summ.combo[ combo ].combo_vld ) :
        if ( found != 0 ) :
          f_top.write( ',' )
        f_top.write( str( combo ) )
        found += 1
    f_top.write( ']\n' )

    # Measurement statistics data
    # Indexed by combo, measurement type and satellite

    found = 0
    f_top.write( '  var all_combo = new Array('
               + str( diffs_summ.num_combos )
               + ');\n'
               )
    for combo in range( diffs_summ.num_combos ) :
      resolve_sdiff = diffs_summ.combo[ combo ].resolve_sdiff
      if ( diffs_summ.combo[ combo ].combo_vld ) :
        combo_vld = 1
      else :
        combo_vld = 0

      # Construct the per combo data

      stats_idx = cNoPiConst.NUM_MEAS_TYPE * combo

      f_top.write( self.combo_lbl( combo )
                 + ' = {};\n'
                 )
      f_top.write( self.combo_lbl( combo )
                 + '.combo_vld = \"'
                 + str( combo_vld )
                 + '\";\n'
                 )
      f_top.write( self.combo_lbl( combo )
                 + '.combo_name = \"'
                 + self.combo_name( diffs_summ, combo )
                 + '\";\n'
                 )
      f_top.write( self.combo_lbl( combo )
                 + '.num_svs = '
                 + str( int( meas_stats[ stats_idx ].num_svs ) )
                 + ';\n'
                 )

      # Construct the per measurement data

      f_top.write( self.combo_lbl( combo )
                 + '.meas = new Array('
                 + str( cNoPiConst.NUM_MEAS_TYPE )
                 + ');\n'
                 )

      for meas in range( cNoPiConst.NUM_MEAS_TYPE ) :
        # The meas_stats array is 1-D, one entry per measurement type
        # per combo
        # Calc. the index for this combo and measurement type
        stats_idx = cNoPiConst.NUM_MEAS_TYPE * combo + meas

        f_top.write( self.meas_lbl( combo, meas )
                   + ' = {};\n'
                   )
        f_top.write( self.meas_lbl( combo, meas )
                   + '.units = \"'
                   + get_meas_units( meas, resolve_sdiff )
                   + '\";\n'
                   )

        # Construct the per satellite data

        f_top.write( self.meas_lbl( combo, meas )
                   + '.sv = new Array('
                   + str( meas_stats[ stats_idx ].num_svs )
                   + ');\n'
                   )
        for sv in range( meas_stats[ stats_idx ].num_svs ) :
          f_top.write( self.sv_lbl( combo, meas, sv )
                     + ' = {};\n'
                     )
          f_top.write( self.sv_lbl( combo, meas, sv )
                     + '.sv_id = '
                     + str( int( meas_stats[ stats_idx ].sv[ sv ].sv_id ) )
                     + ';\n'
                     )
          f_top.write( self.sv_lbl( combo, meas, sv )
                     + '.vld_epochs = '
                     + str( int ( meas_stats[ stats_idx ].sv[ sv ].vld_epochs ) )
                     + ';\n'
                     )

          # Check for invalid mean/std values when there's insufficient data
          # for the calculation
          if ( meas_stats[ stats_idx ].sv[ sv ].vld_epochs > 0 ) :
            vld_min  = meas_stats[ stats_idx ].sv[ sv ].vld_min
            vld_max  = meas_stats[ stats_idx ].sv[ sv ].vld_max
            vld_mean = meas_stats[ stats_idx ].sv[ sv ].vld_mean
            vld_std  = meas_stats[ stats_idx ].sv[ sv ].vld_std
            vld_mav  = meas_stats[ stats_idx ].sv[ sv ].vld_mav
          else :
            vld_min  = 0.0
            vld_max  = 0.0
            vld_mean = 0.0
            vld_std  = 0.0
            vld_mav  = 0.0

          f_top.write( self.sv_lbl( combo, meas, sv )
                     + '.vld_min = '
                     + str( "%.3f" % vld_min )
                     + ';\n'
                     )
          f_top.write( self.sv_lbl( combo, meas, sv )
                     + '.vld_max = '
                     + str( "%.3f" % vld_max )
                     + ';\n'
                     )
          f_top.write( self.sv_lbl( combo, meas, sv )
                     + '.vld_mean = '
                     + str( "%.3f" % vld_mean )
                     + ';\n'
                     )
          f_top.write( self.sv_lbl( combo, meas, sv )
                     + '.vld_std = '
                     + str( "%.3f" % vld_std )
                     + ';\n'
                     )
          f_top.write( self.sv_lbl( combo, meas, sv )
                     + '.vld_mav = '
                     + str( "%.3f" % vld_mav )
                     + ';\n'
                     )


  #############################################################################
  # Generate the data structures used by the JavaScript
  # This is basically a copy of the measurement statistics for each measurement
  # type and signal combo
  #############################################################################

  def include_javascript_globals_acq( self, f_top, diffs_summ, acq_stats ) :

    if ( diffs_summ.do_acq_analysis ) :
      # Acquisition statistics data
      # Indexed by combo, measurement type and satellite

      found = 0
      f_top.write( '  var acq_combo = new Array('
                 + str( diffs_summ.num_combos )
                 + ');\n'
                 )
      for combo in range( diffs_summ.num_combos ) :
        resolve_sdiff = diffs_summ.combo[ combo ].resolve_sdiff
        if ( diffs_summ.combo[ combo ].combo_vld ) :
          combo_vld = 1
        else :
          combo_vld = 0

        # Construct the per combo data

        stats_idx = cNoPiConst.NUM_MEAS_TYPE * combo

        f_top.write( self.acq_combo_lbl( combo )
                   + ' = {};\n'
                   )
        f_top.write( self.acq_combo_lbl( combo )
                   + '.combo_vld = \"'
                   + str( combo_vld )
                   + '\";\n'
                   )
        f_top.write( self.acq_combo_lbl( combo )
                   + '.combo_name = \"'
                   + self.combo_name( diffs_summ, combo )
                   + '\";\n'
                   )
        f_top.write( self.acq_combo_lbl( combo )
                   + '.num_svs = '
                   + str( acq_stats[ stats_idx ].num_svs )
                   + ';\n'
                   )

        # Construct the per measurement data

        f_top.write( self.acq_combo_lbl( combo )
                   + '.acq = new Array('
                   + str( cNoPiConst.NUM_MEAS_TYPE )
                   + ');\n'
                   )

        for meas in range( cNoPiConst.NUM_MEAS_TYPE ) :
          # The acq_stats array is 1-D, one entry per measurement type
          # per combo
          # Calc. the index for this combo and measurement type
          stats_idx = cNoPiConst.NUM_MEAS_TYPE * combo + meas

          f_top.write( self.acq_lbl( combo, meas )
                     + ' = {};\n'
                     )
          f_top.write( self.acq_lbl( combo, meas )
                     + '.units = \"'
                     + get_meas_units( meas, resolve_sdiff )
                     + '\";\n'
                     )

          # Construct the per satellite data

          f_top.write( self.acq_lbl( combo, meas )
                     + '.sv = new Array('
                     + str( int( acq_stats[ stats_idx ].num_svs ) )
                     + ');\n'
                     )

          for sv in range( acq_stats[ stats_idx ].num_svs ) :
            f_top.write( self.acq_sv_lbl( combo, meas, sv )
                       + ' = {};\n'
                       )
            f_top.write( self.acq_sv_lbl( combo, meas, sv )
                       + '.sv_id = '
                       + str( int( acq_stats[ stats_idx ].sv[ sv ].sv_id ) )
                       + ';\n'
                       )
            epochs = ( len( acq_stats[ stats_idx ].sv[ sv ].residual )
                     + len( acq_stats[ stats_idx ].sv[ sv ].res_mod200 )
                     )
            f_top.write( self.acq_sv_lbl( combo, meas, sv )
                       + '.vld_epochs = '
                       + str( int( epochs ) )
                       + ';\n'
                       )
            f_top.write( self.acq_sv_lbl( combo, meas, sv )
                       + '.mean_res = '
                       + str( "%.3f" % acq_stats[ stats_idx ].sv[ sv ].mean_res )
                       + ';\n'
                       )
            f_top.write( self.acq_sv_lbl( combo, meas, sv )
                       + '.mean_mod200 = '
                       + str( "%.3f" % acq_stats[ stats_idx ].sv[ sv ].mean_mod200 )
                       + ';\n'
                       )
            f_top.write( self.acq_sv_lbl( combo, meas, sv )
                       + '.std_res = '
                       + str( "%.3f" % acq_stats[ stats_idx ].sv[ sv ].std_res )
                       + ';\n'
                       )
            f_top.write( self.acq_sv_lbl( combo, meas, sv )
                       + '.std_mod200 = '
                       + str( "%.3f" % acq_stats[ stats_idx ].sv[ sv ].std_mod200 )
                       + ';\n'
                       )
            f_top.write( self.acq_sv_lbl( combo, meas, sv )
                       + '.res_abv_thresh = '
                       + str( "%.3f" % acq_stats[ stats_idx ].sv[ sv ].res_abv_thresh )
                       + ';\n'
                       )
            f_top.write( self.acq_sv_lbl( combo, meas, sv )
                       + '.mod200_abv_thresh = '
                       + str( "%.3f" % acq_stats[ stats_idx ].sv[ sv ].mod200_abv_thresh )
                       + ';\n'
                       )

            # Check for invalid mean/std values when there's insufficient data
            # for the calculation
            if ( acq_stats[ stats_idx ].sv[ sv ].vld_epochs > 0 ) :
              num_acq  = acq_stats[ stats_idx ].sv[ sv ].acq_epochs
            else :
              num_acq  = 0.0
            f_top.write( self.acq_sv_lbl( combo, meas, sv )
                       + '.num_acq = '
                       + str( int( num_acq ) )
                       + ';\n'
                       )


  #############################################################################
  # Create a simple web page for the initial results frame
  # This will be overwriten later using the JavaScript functions
  #############################################################################

  def create_results_frame( self ) :
    f_res = open( 'web/NoPiDI_Results.html', 'wt' )
    f_res.write( """\
<html>
  <head>
    <script type="text/javascript" src="NoPiDI_funcs.js"></script>
    <script>
      // Request the full URL from the parent window once the frame has
      // been created to ensure that the parent can send the URL back to
      // this frame
      // This is used to decode any parameters in the URL
      parent.window.postMessage("url?","*");
    </script>
  </head>
<font face="arial">
<body id=res_body bgcolor="#fff5e4">
<center>
<h1>NoPi Processing Results Display</h1><br>
<img src="../TRMBlogoMed.png" align=middle><br>
""")
    f_res.write( self.inc_copyright() )
    f_res.write( """\
</center>
</body>
</font>
</html>
""")
    f_res.close()


  #############################################################################
  # Complete the menu frame web page description
  #############################################################################

  def __del__( self ) :
    self.menu_text_line( self.inc_copyright() )
    self.menu_tag_line( '</font>' )
    self.menu_tag_line( '</body>' )
    self.menu_tag_line( '</html>' )

    cl_web_page.f_menu.close()


  #############################################################################
  # Begin the menu frame web page description
  #############################################################################

  def create_menu_frame( self ) :
    cl_web_page.f_menu = open( 'web/NoPiDI_Menu.html', 'wt' )

    self.menu_tag_line( '<html>' )
    self.menu_tag_line( """\
  <head>
    <script type="text/javascript" src="NoPiDI_funcs.js"></script>
  </head>
""")
    self.menu_tag_line( '<title>NoPi Processing Results</title>' )
    self.menu_tag_line( '<body id=menu_body bgcolor="#fff5e4">' )
    self.menu_tag_line( '<font face=\"arial\">' )

    self.menu_tag_line( '<h1>NoPi Processing Results</h1>' )

  #############################################################################
  # Describe the data files used by NoPi in the menu frame
  #############################################################################

  def input_data_file_section( self, diffs_summ ) :
    self.menu_text_line( '<b>Base File:</b>')
    self.menu_text_line( diffs_summ.base_fname )
    self.menu_text_line( '<b>Receiver Model / Serial:</b>' )
    self.menu_text_line( diffs_summ.base_model + ' / ' + diffs_summ.base_serial )
    self.menu_text_line( '<b>Firmware Version(s):</b>' )
    if (len(diffs_summ.base_processor) > 1) :
      self.menu_text_line( 'Version / Bld Date / Checksum / Start ToW' )
      for idx in range(1, len(diffs_summ.base_processor)) :
        fw_str = self.base_fw_version_str( diffs_summ, idx )
        self.menu_text_line( fw_str )
    else :
      self.menu_text_line( 'Version / Bld Date / Checksum' )
      fw_str = self.base_fw_version_str( diffs_summ, 0 )
      self.menu_text_line( fw_str )
    self.menu_text_line( '<b>Receiver IP Address:</b>' )
    self.menu_text_line( diffs_summ.base_ip_add )
    self.menu_text_line( '<b>Antenna:</b>' )
    self.menu_text_line( diffs_summ.base_ant_name )

    self.menu_text_line( '  ' )

    self.menu_text_line( '<b>Rover File:</b>' )
    self.menu_text_line( diffs_summ.rovr_fname )
    self.menu_text_line( '<b>Receiver Model / Serial:  </b>' )
    self.menu_text_line( diffs_summ.rovr_model + ' / ' + diffs_summ.rovr_serial )
    self.menu_text_line( '<b>Firmware Version(s):</b>' )
    if (len(diffs_summ.rovr_processor) > 1) :
      self.menu_text_line( 'Version / Bld Date / Checksum / Start ToW' )
      for idx in range(1, len(diffs_summ.rovr_processor)) :
        fw_str = self.rovr_fw_version_str( diffs_summ, idx )
        self.menu_text_line( fw_str )
    else :
      self.menu_text_line( 'Version / Bld Date / Checksum' )
      fw_str = self.rovr_fw_version_str( diffs_summ, 0 )
      self.menu_text_line( fw_str )
    self.menu_text_line( '<b>Receiver IP Address:</b>' )
    self.menu_text_line( diffs_summ.rovr_ip_add )
    self.menu_text_line( '<b>Antenna:</b>' )
    self.menu_text_line( diffs_summ.rovr_ant_name )

    self.menu_text_line( '  ' )

    self.display_times( '<b>Start Time:</b>', diffs_summ.stime_ms )
    self.display_times( '<b>End Time:</b>', diffs_summ.etime_ms )

    duration = diffs_summ.etime_ms - diffs_summ.stime_ms + diffs_summ.drate_ms
    self.menu_text_line( '<b>Duration:</b>' )
    self.menu_text_line( str( duration / 1000.0 ) + ' s' )
    self.menu_text_line( '<b>Data Rate:</b>' )
    self.menu_text_line( str( diffs_summ.drate_ms / 1000.0 ) + ' s' )

    self.menu_text_line( '  ' )

    self.menu_text_line( '<b>T01Lib:</b>' )
    self.menu_text_line( diffs_summ.t01lib_ver )
    self.menu_text_line( '<b>Antenna Lib:</b>' )
    self.menu_text_line( diffs_summ.antlib_ver )


  #############################################################################
  # Utility function used convert and display ToW in HH:MM:SS format
  #############################################################################

  def display_times( self, label, time_in ) :
    time_use = time_in / 1000.0

    days = int( time_use / 86400.0 )
    time_use -= days * 86400.0

    hours = int( time_use / 3600.0 )
    time_use -= hours * 3600.0

    mins = int( time_use / 60.0 )
    time_use -= mins * 60.0

    label = ( label
              + '<br>'
              + str( "%02d" % hours )
              + ':' + str( "%02d" % mins )
              + ':' + str( "%04.2f" % time_use )
              + ' [ToW ' + str( "%.2f" % (time_in / 1000.0) ) + ' s]' )
    self.menu_text_line( label )


  #############################################################################
  # Create a single "show_all_plots()" JavaScript call
  #############################################################################
  def create_single_show_all_plots_href( self, combos, meas, label ) :
    tmp_str = ( '<a href="javascript:show_all_plots( ['
              + str( combos )
              + '], ['
              + str( meas )
              + '] )">'
              + '&lt;'
              + label
              + '&gt;</a>'
              + ' '
              )
    return tmp_str


  #############################################################################
  # Create the "show_all_plots()" JavaScript calls for a combo
  #############################################################################
  def create_show_all_plots_hrefs( self, combo, meas, show_acq_results ) :
    dfp = meas + cNoPiConst.DI_DIFF_PLOTS
    dtp = meas + cNoPiConst.DI_DIST_PLOTS
    acp = meas + cNoPiConst.DI_ACQ_PLOTS

    tmp_str  = self.create_single_show_all_plots_href( -1, dfp, 'Plots' )

    tmp_str += self.create_single_show_all_plots_href( -1, dtp, 'Dist' )

    if ( show_acq_results ) :
      tmp_str += self.create_single_show_all_plots_href( -1, acp, 'Acq' )

    tmp_str += ( '<a href="javascript:show_all_stats( [-1], ['
               + str( meas )
               + '], [] )">'
               + '&lt;Table&gt;</a>'
               )
    return tmp_str


  #############################################################################
  # Include links to the plots and statistics for all satellites and all signal
  # in the menu frame
  # These links call JavaScript functions
  #############################################################################
  def create_all_combo_links( self,
                              show_acq_results,
                              cno_elev_plots,
                              doppler_plots ) :

    self.menu_tag_line( '<h4>Results for all Combos</h4>' )

    self.menu_text_line( 'D.D. Carrier Phase for all satellites' )
    tmp_str = self.create_show_all_plots_hrefs( -1,
                                                cNoPiConst.MEAS_TYPE_DD_CARR,
                                                show_acq_results
                                              )
    self.menu_text_line( tmp_str )

    self.menu_text_line( 'D.D. Pseudorange for all satellites' )
    tmp_str = self.create_show_all_plots_hrefs( -1,
                                                cNoPiConst.MEAS_TYPE_DD_CODE,
                                                show_acq_results
                                              )
    self.menu_text_line( tmp_str )

    if ( doppler_plots ) :
      self.menu_text_line( 'D.D. Doppler for all satellites' )
      tmp_str = self.create_show_all_plots_hrefs( -1,
                                                  cNoPiConst.MEAS_TYPE_DD_DOPP,
                                                  show_acq_results
                                                )
      self.menu_text_line( tmp_str )

    self.menu_text_line( 'S.D. CNo for all satellites' )
    tmp_str = self.create_show_all_plots_hrefs( -1,
                                                cNoPiConst.MEAS_TYPE_SD_CNO,
                                                show_acq_results
                                              )
    if ( cno_elev_plots ) :
      mt = cNoPiConst.DI_CNO_EL_PLOTS
      tmp_str += ( ' '
                 + self.create_single_show_all_plots_href( -1, mt, 'CNoElv' )
                 )
    self.menu_text_line( tmp_str )

    self.menu_text_line( 'Summary' )
    tmp_str = ( '<a href="javascript:show_all_stats( [-1], vld_meas, [] )">'
              + '&lt;Table&gt;</a>'
              + ' '
              + '<a href="javascript:show_all_ref_svs( [-1] )">'
              + '&lt;Ref SVs&gt;</a>'
              )
    self.menu_text_line( tmp_str )


  #############################################################################
  # Create the per measurement JavaScript calls for the invidual combo results
  #############################################################################
  def create_meas_links( self, combo, meas, label, do_acq ) :
    tmp_str = ( '<a href="javascript:show_sv_stats( ['
              + str( combo )
              + '], ['
              + str( meas )
              + '], [] )">'
              + '&lt;'
              + label
              + '&gt;</a>'
              )

    if ( do_acq ) :
      tmp_str += ( ' '
                 + '<a href="javascript:show_acq_sv_stats( ['
                 + str( combo )
                 + '], ['
                 + str( meas )
                 + '], [] )">'
                 + '&lt;Acq&gt;'
                 + '</a>'
                 )

    return tmp_str

  #############################################################################
  # Include links to the plots and statistics for each satellite in the menu
  # frame for a single combo
  # These links call JavaScript functions
  #############################################################################
  def create_single_combo_links( self, diffs_summ, combo, doppler_plots ) :
    self.menu_tag_line( '<h4>'
                      + 'Per SV results for Combo '
                      + str( combo )
                      + '<br>'
                      + self.combo_name( diffs_summ, combo )
                      + '</h4>'
                      )

    tmp_str = self.create_meas_links( combo,
                                      cNoPiConst.MEAS_TYPE_DD_CARR,
                                      'D.D. Carrier Phase',
                                      diffs_summ.do_acq_analysis
                                    )
    self.menu_text_line( tmp_str )

    tmp_str = self.create_meas_links( combo,
                                      cNoPiConst.MEAS_TYPE_DD_CODE,
                                      'D.D. Pseudorange',
                                      diffs_summ.do_acq_analysis
                                    )
    self.menu_text_line( tmp_str )

    if ( doppler_plots ) :
      tmp_str = self.create_meas_links( combo,
                                        cNoPiConst.MEAS_TYPE_DD_DOPP,
                                        'D.D. Doppler',
                                        diffs_summ.do_acq_analysis
                                      )
      self.menu_text_line( tmp_str )

    tmp_str = self.create_meas_links( combo,
                                      cNoPiConst.MEAS_TYPE_SD_CNO,
                                      'S.D. CNo',
                                      diffs_summ.do_acq_analysis
                                    )
    self.menu_text_line( tmp_str )

    self.menu_text_line( '<a href="javascript:show_sv_stats( ['
                       + str( combo )
                       + '], vld_meas , [] )">'
                       + '&lt;All data&gt;</a>'
                       )

    self.menu_text_line( '<a href="javascript:show_all_ref_svs( ['
                       + str( combo )
                       + '] )">'
                       + '&lt;D.D. Ref SVs&gt;</a>'
                       )


  #############################################################################
  # Include information for combos with empty NoPi results files
  #############################################################################
  def create_single_combo_empty( self, diffs_summ, combo ) :
    self.menu_tag_line( '<h4>'
                      + 'Results for Combo '
                      + str(int( combo ))
                      + '<br>'
                      + self.combo_name( diffs_summ, combo )
                      + '</h4>'
                      )
    self.menu_text_line( 'No data processed' )


  #############################################################################
  # Utility function to write a tag line to the menu frame
  # The tag line has no line break <br>
  #############################################################################
  def menu_tag_line( self, line ) :
    cl_web_page.f_menu.write( line + '\n' )


  #############################################################################
  # Utility function to write a text line to the menu frame
  # The text line has a line break <br>
  #############################################################################
  def menu_text_line( self, line ) :
    cl_web_page.f_menu.write( line + '<br>\n' )


  #############################################################################
  # Utility function to convert the signal combo name into a more readable
  # string to be displayed
  #############################################################################
  def combo_name( self, diffs_summ, combo ) :
    cname = ( convert_sat_type( diffs_summ.combo[combo].ref_sat_type )
            + ' '
            + convert_sub_type( diffs_summ.combo[combo].ref_sub_type )
            )

    # Only append the second signal type if it's different from the first
    if ( ( diffs_summ.combo[combo].ref_sat_type
        != diffs_summ.combo[combo].tst_sat_type
         )
       | ( diffs_summ.combo[combo].ref_sub_type
        != diffs_summ.combo[combo].tst_sub_type
         )
       ) :
      cname = ( cname
              + ' / '
              + convert_sat_type( diffs_summ.combo[combo].tst_sat_type )
              + ' '
              + convert_sub_type( diffs_summ.combo[combo].tst_sub_type )
              )

    return( cname )


  #############################################################################
  # Utility functions to construct the F/W version strings to display on the
  # web page
  # Index 0 is the legacy data (last info. in the T0x file) and doesn't
  # include the ToW information
  # Index 1..N is the expanded data for all F/W versions detected in the T0x
  # files
  #############################################################################
  def base_fw_version_str( self, diffs_summ, idx ) :
    fw_str = ( diffs_summ.base_processor[idx]
             + ' / '
             + diffs_summ.base_fw_date[idx]
             + ' / '
             + diffs_summ.base_fw_csum[idx]
             )
    if (idx != 0) :
      fw_str = fw_str + ' / ' + diffs_summ.base_fw_tow[idx] + 's'
    return( fw_str )

  def rovr_fw_version_str( self, diffs_summ, idx ) :
    fw_str = ( diffs_summ.rovr_processor[idx]
             + ' / '
             + diffs_summ.rovr_fw_date[idx]
             + ' / '
             + diffs_summ.rovr_fw_csum[idx]
             )
    if (idx != 0) :
      fw_str = fw_str + ' / ' + diffs_summ.rovr_fw_tow[idx] + 's'
    return( fw_str )
    

  #############################################################################
  # Construct the various labels for the JavaScript data
  #############################################################################

  def combo_lbl( self, combo ) :
    return( '  all_combo[' + str( int( combo ) ) + ']' )

  def meas_lbl( self, combo, meas ) :
    return( self.combo_lbl( combo ) + '.meas[' + str( int( meas ) ) + ']' )

  def sv_lbl( self, combo, meas, sv ) :
    return( self.meas_lbl( combo, meas ) + '.sv[' + str( int( sv ) ) + ']' )

  def acq_combo_lbl( self, combo ) :
    return( '  acq_combo[' + str( int( combo ) ) + ']' )

  def acq_lbl( self, combo, acq ) :
    return( self.acq_combo_lbl( combo ) + '.acq[' + str( int( acq ) ) + ']' )

  def acq_sv_lbl( self, combo, acq, sv ) :
    return( self.acq_lbl( combo, acq ) + '.sv[' + str( int( sv ) ) + ']' )


  #############################################################################
  # Return the copyright string
  # Included by all web pages
  #############################################################################
  def inc_copyright( self ) :
    return( '<br>&copy; Copyright Trimble Inc. 2010 - 2025</br>' )
