/* Javascript for the Galex GIPS GPW site */

/* global variable to hold various GPW related things */
var GPW = {};

/* number of steps in the process */
GPW.steps = [0, 1, 2, 3, 4, 5];

/* array of ARK cover page lines */
GPW.ark_cover_inp = [];

/* array of ETC request objects */
GPW.etc_req_obj = [];

/* ETC product number associated with popup */
GPW.etc_popup_num = null;

/* hash of observation objects */
GPW.obs_obj = {};

/* array of pending request objects */
GPW.pending_req_obj = [];

/* array of loading request objects */
GPW.loading_req_obj = [];

/* flag to control analysis */
GPW.is_analysis_started = false;

/*
**  array of tool objects, order determines appearance in product table
**
**  Tool Object Properties
**  --------------------------------------------------------------------------
**  name          name of tool
**  cgi           cgi used to call tool
**  step4         flag, true when tool used during GPW step # 4
**  advanced      flag, true when tool considered advanced tool in step # 4
**  use_coord     flag, true when tool uses coordinates as cgi parameters
**  use_time      flag, true when tool uses time as cgi parameters
**  use_obs_name  flag, true when tool uses observation name as cgi parameters 
**  def_parm      default parameters that appear in every cgi for tool
**  cs_prefix     prefix to coordinate system type for tool
**  ark_lvl       level at which tool request info appears in ARK output
**  full_name     full name of the tool, will appear in ARK output
*/
GPW.tools = [
  { 
    name: 'toast', cgi: 'run_toast.pl', step4: true, advanced: false, 
    use_coord: true, use_time: false, use_obs_name: false,
    def_parm: '&scitarg_diam=0&search_method_type=point_search'+
      '&point_fovr=0.5&no_product=1&ow_type=all&survey_type=all', 
    cs_prefix: 'gpw_', ark_lvl: 'obs', full_name: 'Target Search Tool'
  },
  { 
    name: 'cbs', cgi: 'run_chkbstar.pl', step4: true, advanced: false,
    use_coord: true, use_time: false, use_obs_name: true,
    def_parm: '&zody_method=min&no_product=1', cs_prefix: 'gpw_', ark_lvl: 'obs',
    full_name: 'Brightness Checker Tool'
  },
  { 
    name: 'etc', cgi: 'run_expcalc.pl', step4: false, advanced: false,
    use_coord: true, use_time: false, use_obs_name: false,
    def_parm: '&no_product=1', cs_prefix: '', ark_lvl: 'prop',
    full_name: 'Exposure Time Calculator'
  },
  { 
    name: 'skp', cgi: 'run_gmap.pl', step4: true, advanced: false,
    use_coord: true, use_time: false, use_obs_name: true,
    def_parm: '&gmap_plot_width=5.0&no_product=1', cs_prefix: 'gpw_', 
    ark_lvl: 'obs', full_name: 'Sky Plot Tool'
  },
  { 
    name: 'vis', cgi: 'run_gmosvis.pl', step4: true, advanced: false,
    use_coord: true, use_time: true, use_obs_name: true,
    def_parm: '&no_product=1', cs_prefix: 'gpw_', ark_lvl: 'obs',
    full_name: 'Visibility Tool'
  },
  { 
    name: 'ang', cgi: 'run_angcalc.pl', step4: true,  advanced: true,
    use_coord: true, use_time: true, use_obs_name: true,
    def_parm: '&ref_body=sun&ref_body=moon&no_product=1', cs_prefix: 'gpw_',
    ark_lvl: 'obs', full_name: 'Angle Calculator'
  },
  { 
    name: 'zod', cgi: 'run_zlct.pl', step4: true, advanced: true,
    use_coord: true, use_time: true, use_obs_name: true,
    def_parm: '&no_product=1', cs_prefix: 'gpw_', ark_lvl: 'obs',
    full_name: 'Zodiacal Light Calculator'
  }
];

/* return index of tool matching name or -1 if not found */
GPW.indexOfTool = function(tool_name) {
  var ind = -1;
  for ( var i = 0, l = GPW.tools.length; i < l; ++i ) {
    if ( GPW.tools[i].name == tool_name ) {
      ind = i;
      break;
    }
  }
  return ind;
}

/* 
**  default constructor for an observation object
**
**  Obs Object Properties
**  --------------------------------------------------------------------------
**  num       sequence number
**  name      observation name
**  hname     observation name in form useable in hash
**  ra        position
**  dec       position
**  scitarg   array of science targets objects
**  requests  hash of requests, key is tool name
**  ark_inp   array of additional lines of ARK input
*/
GPW.Obs = function() {
  this.num = null;
  this.name = null;
  this.hname = null;
  this.ra = null;
  this.dec = null;
  this.scitarg = [];
  this.requests = {};
  this.ark_inp = [];
}

/* return string representatiion of observation suitable for input to ark */
GPW.Obs.prototype.toArkString = function() {
  var str = '';

  str += 'Observation Number [format=I3,required]: '+this.num+'\n';
  str += 'Observation or Field Name [format=A30,required]: '+this.name+'\n';
  str += 'Right Ascension [format=A11,unit=HH MM SS.SS or '+
    'DDD.DDDDDD,required]: '+this.ra.toString().replace(/:/g, ' ')+'\n';
  str += 'Declination [format=A11,unit=[+/-]DD MM SS.S or '+
    '[+/-]DD.DDDDDD,required]: '+this.dec.toString().replace(/:/g, ' ')+'\n';

  for ( var i = 0, n = i + 1, l = this.scitarg.length; i < l; ++i, ++n ) {
    str += 'Science Target Name('+n+') [format=A30]: '+
            this.scitarg[i].name+'\n';
    str += 'Science Target Right Ascension('+n+') [format=A11]: '+
            this.scitarg[i].ra.toString().replace(/:/g, ' ')+'\n';
    str += 'Science Target Declination('+n+') [format=A11]: '+
            this.scitarg[i].dec.toString().replace(/:/g, ' ')+'\n';
    str += 'Science Target Angular Size('+n+') [format=F6.1]: '+
            this.scitarg[i].diam+'\n';
  }

  return str;
}

/* 
**  default constructor for a science target object
**
**  Scitarg Object Properties
**  --------------------------------------------------------------------------
**  name      science target name
**  ra        position
**  dec       position
**  diam      diameter 
*/
GPW.Scitarg = function() {
  this.name = null;         /* science target name */
  this.ra = null;           /* position */
  this.dec = null;          /* position */
  this.diam = null;         /* diameter */
}

/* return equivalent observation name that can be used as a hash property */
GPW.getObsHashName = function(obs_name) {
  /* NB- my original thought was to replace special characters with 
         underscores, but I realized that by treating the name as a String 
         object, I could use it to access object properties */
  return new String(obs_name); 
  /* NB- after more thought, I realized that by constraining observation
         names to spaces and 'a-z A-Z 0-9 _ - + .', it will make it easier
         on downstream tools that need to use the observation names
  return obs_name.replace(/\s+/g, '_');
  */
}

/* return an array of observation hash names in the current sequence */
GPW.getObsHashNamesInSequence = function() {
  var hnames = [];
  for ( var hname in GPW.obs_obj ) {
    var num = GPW.obs_obj[hname].num;
    if ( num ) {
      hnames[num-1] = hname; /* arrays can be inserted out of order */
    }
  }
  return hnames;
}

/* delete all completed and pending request objects for observation */
GPW.deleteAllObsRequests = function(obs) {
  for ( var i = 0, l = GPW.tools.length; i < l; ++i ) {
    GPW.deleteToolObsRequests(obs, GPW.tools[i].name);
  }
}

/* delete pending, loading, and completed request objects for just one tool */
GPW.deleteToolObsRequests = function(obs, tool_name) {
  var req_ind;
  /* delete pending request object from queue */
  req_ind = GPW.indexOfRequest(GPW.pending_req_obj, obs.hname, tool_name);
  if ( req_ind != -1 ) {
    GPW.pending_req_obj.splice(req_ind, 1);
  }
  /* set ignore flag for loading requests */
  req_ind = GPW.indexOfRequest(GPW.loading_req_obj, obs.hname, tool_name);
  if ( req_ind != -1 ) {
    GPW.loading_req_obj[req_ind].ignore_complete = true;
  }
  /* delete completed request object from observation */
  if ( obs.requests[tool_name] ) {
    delete obs.requests[tool_name];
  }
}

/* 
**  construct a request object for a tool and observation
**
**  Request Object Properties
**  --------------------------------------------------------------------------
**  obs_hname         hash name of observation assoc with request
**  tool_name         name of tool associated with request
**  tool_url          url used to call tool and make new product
**  request_status    result of the request: SUCCESS, FAILURE
**  request_id        request id
**  request_url       url used to retrieve existing product
**  tbl_id            product table cell id
**  ignore_complete   flag, true when product should be ignored
*/
GPW.Request = function(obs, tool) {
  this.obs_hname        = obs.hname;
  this.tool_name        = tool.name;
  this.tool_url         = GPW.makeToolUrl(obs, tool);
  this.request_status   = null;
  this.request_id       = null;
  this.request_url      = null;
  this.tbl_id           = 'gpw_product_obs'+obs.num+'_'+tool.name;
  this.ignore_complete  = false;
}

/* return a url used to call tool in order to make a product */
GPW.makeToolUrl = function(obs, tool) {
  var tool_url = tool.cgi+'?';
  /* add observation coordinates */
  if ( tool.use_coord ) {
    /* determine the coordinate system type of the observation */
    var cs_type = getValueOfFirstCheckedRadio(
      $( tool.cs_prefix+'eqj2000', 
         tool.cs_prefix+'eqb1950', 
         tool.cs_prefix+'galactic') 
    );
    if ( cs_type == 'eqj2000' || cs_type == 'eqb1950' ) {
      tool_url += 
        '&ra='+obs.ra+
        '&dec='+obs.dec+
        '&coord_sys_type='+cs_type;
    } else if ( cs_type == 'galactic' ) {
      tool_url += 
        '&lon='+obs.ra+
        '&lat='+obs.dec+
        '&coord_sys_type='+cs_type;
    }
  }
  /* add time */
  if ( tool.use_time ) {
    /* time period for GI5 -- no user control over this */
    tool_url += 
      '&greg0=2010 1 1 0 0 0.00'+
      '&greg1=2011 1 1 0 0 0.00'+
      '&time_sys_type=greg'+
      '&year0=2010';
  }
  /* add observation name */
  if ( tool.use_obs_name ) {
    tool_url += '&obs_name='+obs.name;
  }
  /* add default parameters that are specific to the tool */
  if ( tool.def_parm )  {
    tool_url += tool.def_parm;
  }
  /* add observation name as science target name for toast tool */
  if ( tool.name == 'toast' ) {
    tool_url += '&scitarg_name='+obs.name;
  }
  /* add science targets only for sky plot tool (aka gmap) */
  if ( tool.name == 'skp' ) {
    /* gmap will want the observation repeated in this format too */
    tool_url += 
      '&obs1_name='+obs.name+
      '&obs1_ra='+obs.ra+
      '&obs1_dec='+obs.dec;
    for ( var i = 0, l = obs.scitarg.length; i < l; ++i ) {
      var scitarg_num = i+1;
      tool_url += 
        '&obs1_scitarg'+scitarg_num+'_name='+obs.scitarg[i].name+
        '&obs1_scitarg'+scitarg_num+'_ra='+obs.scitarg[i].ra+
        '&obs1_scitarg'+scitarg_num+'_dec='+obs.scitarg[i].dec+
        '&obs1_scitarg'+scitarg_num+'_diam='+obs.scitarg[i].diam;
    }
  }
  /* add exposure time calculator parameters */
  if ( tool.name == 'etc' ) {
    var etc_method_type = getValueOfFirstCheckedRadio( 
      $('exp', 'snr')
    );
      if ( etc_method_type == 'exp' ) {
        tool_url += '&exp_time='+$('exp_time').value;
      } else if ( etc_method_type == 'snr' ) {
        tool_url += 
          '&snr_type='+$('snr_type').value+
          '&snr_val='+$('snr_val').value;
      }
      tool_url += '&etc_method_type='+etc_method_type;
    var object_type = getValueOfFirstCheckedRadio( 
      $('star', 'galaxy', 'quasar', 'white_dwarf', 'sed')
    );
      if ( object_type == 'star' ) {
        tool_url += '&star_temp='+$('star_temp').value;
      } else if ( object_type == 'galaxy' ) {
        tool_url +=
          '&spectral_type='+$('spectral_type').value+
          '&gredshift='+$('gredshift').value+
          '&extinction='+$('extinction').value+
          '&escape='+$('escape').value;
      } else if ( object_type == 'quasar' ) {
        tool_url += '&qredshift='+$('qredshift').value;
      } else if ( object_type == 'white_dwarf' ) {
        tool_url += '&wd_temp='+$('wd_temp').value;
      }
      /* TODO: SED is not supported */
      tool_url += '&object_type='+object_type;
    var flux_method_type = getValueOfFirstCheckedRadio(
      $('user_flux', 'magnitude')
    );
      if ( flux_method_type == 'user_flux' ) {
        tool_url += 
          '&wavelength='+$('wavelength').value+
          '&density='+$('density').value;
      } else if ( flux_method_type == 'magnitude' ) {
        tool_url +=
          '&filter='+$('filter').value+
          '&app_magnitude='+$('app_magnitude').value;
      }
      tool_url += '&flux_method_type='+flux_method_type;
    var extinct_method = getValueOfFirstCheckedRadio(
      $('none', 'auto', 'manual')
    );
      if ( extinct_method == 'auto' ) {
        tool_url += '&auto_area='+$('auto_area').value;
      } else if ( extinct_method == 'manual' ) {
        tool_url += 
          '&manual_extinction='+$('manual_extinction').value+
          '&manual_area='+$('manual_area').value;
      } else if ( extinct_method == 'none' ) {
        tool_url += '&none_area='+$('none_area').value;
      }
      tool_url += '&extinct_method='+extinct_method;
    if ( $('calc_cmt') ) {
      tool_url += '&calc_cmt='+$('calc_cmt').value;
    }
  }
  return tool_url;
}

/* return index of request matching obs hash name and tool name or -1 */
GPW.indexOfRequest = function(req_array, obs_hname, tool_name) {
  var ind = -1;
  for ( var i = 0, l = req_array.length; i < l; ++i ) {
    if ( req_array[i].obs_hname == obs_hname && 
         req_array[i].tool_name == tool_name ) {
      ind = i;
      break;
    }
  }
  return ind;
}

/* invoke step transition, when it exists */
GPW.invoke_transition = function(from, to) {
  var funcname = 'on_step'+from+'_to_step'+to;
  if ( typeof GPW[funcname] == "function" ) {
    return GPW[funcname]();
  }
  return true; /* by default permit the transition */
}

/* 'Cover Page' to 'Observations & Science Targets' */
GPW.on_step1_to_step2 = function() {
  /* validate the cover page */
  var valid = checkCoverPageValid();
  if ( valid ) {
    /* initialize the obs & scitarg table with obs hash */
    valid = GPW.initObservationsAndTargetsTable();
  }
  return valid;
}

/* 'Observations & Science Targets' to 'Exposure Time Calculator' */
GPW.on_step2_to_step3 = function() {
  /* validate the obs & scitarg table, Javascript only has short-circuit 
     logical operators, but I want both checks to run even if first fails */
  var valid1 = checkObservationsAndTargetsValid();
  var valid2 = checkObservationsAndTargetsNamesValid();
  var valid = valid1 && valid2;
  if ( valid ) {
    /* synchronize obs hash with the obs & scitarg table */
    valid = GPW.syncObsWithObservationsAndTargetsTable();
    /* clear out any coordinate values that might be copied in */
    if ( $('coordinates') ) {
      $('ra').value = '';
      $('dec').value = '';
      $('lon').value = '';
      $('lat').value = '';
    }
    /* initialize the etc popup with the obs hash */
    GPW.initEtcPopup();
  }
  return valid;
}

/* 'Exposure Time Calculator' to 'Analyze Results' */
GPW.on_step3_to_step4 = function() {
  /* build product table from obs hash */
  var valid = GPW.initProductTable();
  if ( valid ) {
    GPW.queueProductTable(true, false);
  }
  return valid;
}

/* 'Analyze Results' to 'Exposure Time Calculator' */
GPW.on_step4_to_step3 = function() {
  return true; 
}

/* 'Analyze Results' to 'Save File' */
GPW.on_step4_to_step5 = function() {
  onClickAnalysisStop();
  GPW.initArkTextArea();
  return true;
}

/* initialize the obs & scitarg table with obs hash */
GPW.initObservationsAndTargetsTable = function() {
  var table = $('observations_and_targets');
  if ( table ) {
    /* remove everything except observation # 1 */
    var obs_nums = getObservationNums();
    for ( var i = obs_nums.length-1; i > 0; --i ) {
      $('obs'+obs_nums[i]).remove();
    }
    renumberObservation($('obs1'), 1, 1);
    /* remove all science targets in observation # 1 except the first */
    var scitarg_table = $('obs1_scitarg_table');
    for ( var j = scitarg_table.rows.length-1; j > 1; --j ) {
      $(scitarg_table.rows[j]).remove();
    }
    renumberScienceTarget($('obs1_scitarg_table').rows[1], 1, 1);
    /* create the observation div and science target row templates */
    var obs_div = $('obs1').cloneNode(true);
    var scitarg_row = $('obs1_scitarg_table').rows[1].cloneNode(true);
    /* rebuild the observations and targets using the current sequence */
    var obs, prefix, scitarg, scitarg_num;
    var obs_hnames = GPW.getObsHashNamesInSequence();
    for ( var i = 0, l = obs_hnames.length; i < l; ++i ) {
      obs = GPW.obs_obj[ obs_hnames[i] ];
      /* insert new observation div */
      if ( obs.num > 1 ) {
        var new_div = obs_div.cloneNode(true);
        renumberObservation(new_div, 1, obs.num);
        table.insertBefore(new_div, $('obs_scitarg_msg'));
      }
      prefix = 'obs'+obs.num;
      $(prefix+'_name').value = obs.name;
      $(prefix+'_ra').value   = obs.ra;
      $(prefix+'_dec').value  = obs.dec;
      for ( var j = 0, k = obs.scitarg.length; j < k; ++j ) {
        scitarg = obs.scitarg[j];
        scitarg_num = j+1;
        /* insert new science target row */
        if ( scitarg_num > 1 ) {
          var new_row = scitarg_row.cloneNode(true);
          renumberObservation(new_row, 1, obs.num);
          renumberScienceTarget(new_row, 1, scitarg_num);
          scitarg_table = $('obs'+obs.num+'_scitarg_table');
          scitarg_table.rows[
            scitarg_table.rows.length-1].parentNode.appendChild(new_row);
        }
        prefix = 'obs'+obs.num+'_scitarg'+scitarg_num;
        $(prefix+'_name').value = scitarg.name;
        $(prefix+'_ra').value   = scitarg.ra;
        $(prefix+'_dec').value  = scitarg.dec;
        $(prefix+'_diam').value = scitarg.diam;
      }
    }
    hideErrMsg($('obs_scitarg_msg'));
  }
  return table;
}

/* synchronize obs hash with the obs & scitarg table */
GPW.syncObsWithObservationsAndTargetsTable = function() {
  var table = $('observations_and_targets');
  if ( table ) {
    var obs, obs_name, obs_hname, scitarg_name, ra, dec, diam, prefix;
    /* reset all sequence numbers in case the user deleted observations */
    for ( obs_hname in GPW.obs_obj ) { 
      GPW.obs_obj[obs_hname].num = null;
    }
    /* iterate through all observations in the table */
    var obs_nums = getObservationNums();
    for ( var i = 0, l = obs_nums.length; i < l; ++i ) {
      /* extract observation parameters from table */
      obs_name  = $('obs'+obs_nums[i]+'_name').value;
      obs_hname = GPW.getObsHashName(obs_name);
      ra        = $('obs'+obs_nums[i]+'_ra').value;
      dec       = $('obs'+obs_nums[i]+'_dec').value;
      /* extract observation object from hash */
      obs       = GPW.obs_obj[obs_hname];
      /* an observation with this name already exists in the hash */
      if ( obs ) {
        /* update any changed values */
        obs.num = i+1;
        if ( obs.ra != ra || obs.dec != dec ) {
          obs.ra = ra;
          obs.dec = dec;
          GPW.deleteAllObsRequests(obs); /* need new requests */
        }
      /* this is a new observation */
      } else {
        obs       = new GPW.Obs();
        obs.num   = i+1; /* sequence number starts from 1 */
        obs.name  = obs_name;
        obs.hname = obs_hname;
        obs.ra    = ra;
        obs.dec   = dec;
        /* save new observation back into the hash */
        GPW.obs_obj[obs_hname] = obs;
      }
      /* iterate through all science targets in observation */
      var scitarg_tbl = $('obs'+obs_nums[i]+'_scitarg_table');
      var scitarg_nums = getScienceTargetNums(scitarg_tbl);
      if ( scitarg_nums.length != obs.scitarg.length ) {
        GPW.deleteAllObsRequests(obs); /* need new requests */
      }
      for ( var j = 0, k = scitarg_nums.length; j < k; ++j ) {
        prefix        = 'obs'+obs_nums[i]+'_scitarg'+scitarg_nums[j];
        scitarg_name  = $(prefix+'_name').value;
        ra            = $(prefix+'_ra').value;
        dec           = $(prefix+'_dec').value;
        diam          = $(prefix+'_diam').value;
        /* extract science target from array */
        var st        = obs.scitarg[j];
        /* a science target with this index already exists */
        if ( st ) {
          /* update any changed values */
          if ( st.name != scitarg_name || 
               st.ra != ra || st.dec != dec || st.diam != diam ) {
            st.scitarg_name = scitarg_name;
            st.ra = ra;
            st.dec = dec;
            st.diam = diam;
            GPW.deleteAllObsRequests(obs); /* need new requests */
          }
        /* this is a new science target */
        } else {
          var st  = new GPW.Scitarg();
          st.name = scitarg_name;
          st.ra   = ra;
          st.dec  = dec;
          st.diam = diam;
          /* save new science target back into array */
          obs.scitarg[j] = st;
        }
      }
      /* delete any remaining science targets from end of array */
      obs.scitarg.length = scitarg_nums.length;
    }
  }
  return table;
}

/* clear all existing etc requests and associated product table rows */
GPW.clearEtcProductTable = function() {
  /* delete etc request objects */
  GPW.etc_req_obj.clear();
  /* remove all rows from etc product table except the first and last */
  var table = $('etc_product_table');
  for ( var i = table.rows.length-2; i > 0; --i ) {
    table.deleteRow(i);
  }
}

/* append a new row to the etc product table, return row id */
GPW.appendEtcProductTableRow = function() {
  var table = $('etc_product_table');
  if ( table.rows.length >= 22 ) {
    alert('The maximum number of ETC requests that can be saved with a '+
      'proposal is 20.');
    return;
  }
  var new_row = table.rows[table.rows.length-1].cloneNode(true);
    var prefix  = 'etc_product';
    var old_num = parseInt(
      new_row.id.substring(prefix.length, new_row.id.length)
    );
    var new_num = old_num+1;
    /* renumber the ids in the row */
    var old_id  = prefix+old_num;
    var new_id  = prefix+new_num;
    var re      = new RegExp(old_id);
    var elems   = new_row.getElementsByTagName('*');
    for ( var i = 0, l = elems.length; i < l; ++i ) {
      if ( elems[i].id && elems[i].id.search(re) != -1 ) {
        elems[i].id = elems[i].id.replace(re, new_id);
      }
    }
    new_row.id  = new_id;
  table.rows[table.rows.length-1].parentNode.appendChild(new_row);
    $(new_id+'_num').innerHTML = '#'+new_num;
    $(new_id+'_param').innerHTML = '';
    $(new_id+'_status').innerHTML = '';
    $(new_id+'_status').className = '';
    $(new_id+'_cmt').value = '';
    $(new_id+'_obs').value = '';
  return old_id;
}

/* initialize the etc popup using the current obs hash */
GPW.initEtcPopup = function() {
  var popup = $('etc_popup');
  if ( popup ) {
    /* rebuild the list using the current sequence */
    var all_li = '';
    var obs, prefix;
    var obs_hnames = GPW.getObsHashNamesInSequence();
    for ( var i = 0, l = obs_hnames.length; i < l; ++i ) {
      obs = GPW.obs_obj[ obs_hnames[i] ];
      all_li += 
        '<li><input id="etc_cbx'+obs.num+'" name="etc_cbx'+
        obs.num+'" type="checkbox" value="'+obs.hname+'" />'+
        '<label for="etc_cbx'+obs.num+'">'+obs.name+' ('+
        '<em>RA</em>='+obs.ra+' '+'<em>Dec</em>='+obs.dec+')'+
        '</label></li>';
    }
    /* replace the existing list items with the new set */
    $('etc_checkbox_list').update(all_li);
  }
}

/* reset the product table row to an initial default state */
GPW.resetProductTableRow = function(id) {
  var row = $(id);
  if ( row ) {
    $(id+'_num').innerHTML = '';
    $(id+'_name').innerHTML = '';
    /* all product requests begin in the de-queued state */
    for ( var i = 0, l = GPW.tools.length; i < l; ++i ) {
      if ( GPW.tools[i].step4 ) {
        GPW.setDeQueued(id+'_'+GPW.tools[i].name);
      }
    }
  }
  return row;
}

/* renumber the ids in the product table row */
GPW.renumberProductTableRow = function(row, old_num, new_num) {
  var old_id = 'gpw_product_obs'+old_num;
  var new_id = 'gpw_product_obs'+new_num;
  var re = new RegExp(old_id);
  var elems = row.getElementsByTagName('*');
  for ( var i = 0, l = elems.length; i < l; ++i ) {
    /* change any ids containing old number to new number */
    if ( elems[i].id && elems[i].id.search(re) != -1 ) {
      elems[i].id = elems[i].id.replace(re, new_id);
    }
  }
  return row;
}

/* build product table from obs hash */
GPW.initProductTable = function() {
  var table = $('gpw_product_table');
  if ( table ) {
    var prefix = 'gpw_product_obs';
    /* remove everything in the table except observation # 1 
       the first two rows of the product table are used as header rows */
    for ( var i = table.rows.length-1; i > 2; --i ) {
      table.deleteRow(i);
    }
    /* reset the state of observation # 1 */
    var row = GPW.resetProductTableRow(prefix+'1'); 
    /* rebuild the table using the current sequence */
    var obs_hnames = GPW.getObsHashNamesInSequence();
    for ( var i = 0, l = obs_hnames.length; i < l; ++i ) {
      var obs = GPW.obs_obj[ obs_hnames[i] ];
      prefix  = 'gpw_product_obs'+obs.num;
      /* add new row to the table */
      if ( obs.num > 1 ) {
        var new_row = row.cloneNode(true);
          new_row.id = prefix;
          new_row = GPW.renumberProductTableRow(new_row, 1, obs.num);
        row.parentNode.appendChild(new_row);
      }
      /* populate the row */
      $(prefix+'_num').innerHTML = '#'+obs.num;
      $(prefix+'_name').innerHTML = obs.name;
    }
  }
  return table;
}

/* queue requests in the product table from obs hash */
GPW.queueProductTable = function(do_queue_basic, do_queue_advanced) {
  var table = $('gpw_product_table');
  if ( table ) {
    var obs_hnames = GPW.getObsHashNamesInSequence();
    for ( var i = 0, l = obs_hnames.length; i < l; ++i ) {
      var obs     = GPW.obs_obj[ obs_hnames[i] ];
      var prefix  = 'gpw_product_obs'+obs.num;
      for ( var j = 0, k = GPW.tools.length; j < k; ++j ) {
        /* skip tools that are not meant for use during step 4 */
        if ( ! GPW.tools[j].step4 ) {
          continue;
        }
        var req = obs.requests[GPW.tools[j].name];
        var load_ind = GPW.indexOfRequest(GPW.loading_req_obj,
          obs.hname, GPW.tools[j].name);
        var pend_ind = GPW.indexOfRequest(GPW.pending_req_obj, 
          obs.hname, GPW.tools[j].name);
        /* request is complete */
        if ( req ) {
          /* update the table id in case the observation sequence changed */
          req.tbl_id = prefix+'_'+GPW.tools[j].name;
          GPW.setComplete(req.tbl_id, req.request_url);
        /* request is loading */
        } else if ( load_ind != -1 ) {
          req = GPW.loading_req_obj[load_ind];
          /* update the table id in case the observation sequence changed */
          req.tbl_id = prefix+'_'+GPW.tools[j].name;
          GPW.setLoading(req.tbl_id);
        /* request is pending */
        } else if ( pend_ind != -1 ) {
          req = GPW.pending_req_obj[pend_ind];
          /* update the table id in case the observation sequence changed */
          req.tbl_id = prefix+'_'+GPW.tools[j].name;
          GPW.setQueued(req.tbl_id);
        /* request is none of the above */
        } else {
          /* add a new request to the pending queue */
          if ( ( ! GPW.tools[j].advanced && do_queue_basic    ) ||
               (   GPW.tools[j].advanced && do_queue_advanced ) ) {
            GPW.pending_req_obj.push(new GPW.Request(obs, GPW.tools[j]));
            GPW.setQueued(prefix+'_'+GPW.tools[j].name);
          /* mark the cell as dequeued */
          } else {
            GPW.setDeQueued(prefix+'_'+GPW.tools[j].name);
          }
        }
      }
    }
  }
}

/* set product table cell to dequeued state */
GPW.setDeQueued = function(tbl_id) {
  var elem = $(tbl_id);
  if ( elem ) {
    elem.innerHTML = 'Not Queued';
    elem.className = '';
    elem.addClassName('dequeued');
  }
}

/* set product table cell to queued state */
GPW.setQueued = function(tbl_id) {
  var elem = $(tbl_id);
  if ( elem ) {
    elem.innerHTML = 'Queued';
    elem.className = '';
    elem.addClassName('queued');
  }
}

/* set product table cell to loading state */
GPW.setLoading = function(tbl_id) {
  var elem = $(tbl_id);
  if ( elem ) {
    elem.innerHTML = '';
    elem.className = '';
    elem.addClassName('loading');
  }
}

/* set product table cell to complete */
GPW.setComplete = function(tbl_id, url, msg) {
  var elem = $(tbl_id);
  if ( elem ) {
    msg = msg || 'View Product';
    elem.innerHTML = '<a href="'+url.escapeHTML()+'" target="GPW_Product" '+
      'title="Click to '+msg+'">'+msg+'</a>';
    elem.className = '';
  }
}

/* set product table cell to failed */
GPW.setFailed = function(tbl_id, msg) {
  var elem = $(tbl_id);
  if ( elem ) {
    elem.innerHTML = msg ? msg : 'Error';
    elem.className = '';
    elem.addClassName('failed');
  }
}

/* extract the text enclosed by the div with matching id from response text */
GPW.getDivTextFromResponseText = function(id, responseText) {
  var div_text;
  var open_div  = '<div id="'+id+'">';
  var close_div = '</div>';
  var ind1 = responseText.indexOf(open_div);
  if ( ind1 != -1 ) {
    var ind2 = responseText.indexOf(close_div, ind1+open_div.length); 
    if ( ind2 != -1 ) {
      div_text = responseText.substring(ind1+open_div.length, ind2);
    }
  }
  return div_text;
}

/* request a product */
GPW.doRequest = function(req) {
  if ( req ) {
    /* add request to array of loading requests */
    GPW.loading_req_obj.push(req);
    /* indicate that the request is loading */
    GPW.setLoading(req.tbl_id);
    new Ajax.Request( req.tool_url, {
      method: 'get',
      onSuccess: function(transport) {
        /* remove the request from the loading array */
        var req_ind = GPW.indexOfRequest(GPW.loading_req_obj, 
          req.obs_hname, req.tool_name);
        if ( req_ind != -1 ) {
          GPW.loading_req_obj.splice(req_ind, 1);
        }
        /* ignore completed requests */
        if ( req.ignore_complete ) {
          return; /* do not continue analysis */
        }
        /* extract the request parameters */
        req.request_status = GPW.getDivTextFromResponseText(
          'request_status', transport.responseText);
        req.request_id = GPW.getDivTextFromResponseText(
          'request_id', transport.responseText);
        req.request_url = GPW.getDivTextFromResponseText(
          'request_url', transport.responseText);
          req.request_url = req.request_url.unescapeHTML();
        /* request was successful */
        if ( req.request_status == 'SUCCESS' ) {
          /* save the request in the etc array */
          if ( req.tool_name == 'etc' ) {
            GPW.setComplete(req.tbl_id, req.request_url, 'View ETC Results');
            GPW.etc_req_obj.push(req);
          /* save the request in the observation hash */
          } else {
            GPW.setComplete(req.tbl_id, req.request_url);
            if ( GPW.obs_obj[req.obs_hname] ) {
              GPW.obs_obj[req.obs_hname].requests[req.tool_name] = req;
            }
          }
        /* request failed */
        } else {
          /* update the product table */
          var msg;
          if ( req.request_url ) {
            msg = '<a href="'+req.request_url+'" target="_blank" '+
              'title="Click to View Error Message">Error</a>';
          }
          GPW.setFailed(req.tbl_id, msg);
          /* clear the contents of the observation hash */
          if ( GPW.obs_obj[req.obs_hname] ) {
            GPW.obs_obj[req.obs_hname].requests[req.tool_name] = null;
          }
        }
        /* do another request */
        if ( GPW.is_analysis_started && req.tool_name != 'etc' ) {
          GPW.doRequest(GPW.pending_req_obj.shift());
        }
      },
      onFailure: function(transport) {
        /* remove the request from the loading array */
        var req_ind = GPW.indexOfRequest(GPW.loading_req_obj, 
          req.obs_hname, req.tool_name);
        if ( req_ind != -1 ) {
          GPW.loading_req_obj.splice(req_ind, 1);
        }
        /* ignore completed requests */
        if ( req.ignore_complete ) {
          return; /* do not continue analysis */
        }
        /* update the product table */
        GPW.setFailed(req.tbl_id);
        /* update the results hash */
        if ( GPW.obs_obj[req.obs_hname] ) {
          GPW.obs_obj[req.obs_hname].requests[req.tool_name] = null;
        }
        /* do another request */
        if ( GPW.is_analysis_started && req.tool_name != 'etc' ) {
          GPW.doRequest(GPW.req.shift());
        }
      }
    });
  } else {
    if ( GPW.is_analysis_started && GPW.loading_req_obj.length == 0 ) {
      onClickAnalysisStop();
    }
  }
}

/* parse tool request information from ark loader text */
GPW.parseArkLoaderToolRequest = function(tool, obs, val) {
  if ( val != null ) {
    var req = new GPW.Request(obs, tool);
      req.request_status = 'SUCCESS';
      req.request_id  = val.parseQuery().req_id;
      req.request_url = val.substring(val.lastIndexOf('/')+1, val.length);
    if ( req.request_id != null && req.request_url != null ) {
      obs.requests[tool.name] = req;
    }
  }
}

/* parse etc request information from ark loader text */
GPW.parseArkLoaderEtcRequest = function(tool, num, val) {
  if ( val != null ) {
    /* parse the request to make sure we have an id and url */ 
    var request_id  = val.parseQuery().req_id;
    var request_url = val.substring(val.lastIndexOf('/')+1, val.length);
    if ( request_id == null || request_url == null ) {
      return;
    }
    /* append an new entry to the table, used for the next request */
    var old_id = GPW.appendEtcProductTableRow();
    if ( old_id == null ) {
      return;
    }
    /* append a request-like object to the etc array */
    var req = {};
      req.obs_hname       = old_id;
      req.tool_name       = tool.name;
      req.request_status  = 'SUCCESS';
      req.request_id      = request_id;
      req.request_url     = request_url;
      req.tbl_id          = old_id+'_status';
    GPW.etc_req_obj.push(req);
    /* populate some of the cells in the etc table */
    GPW.setComplete(req.tbl_id, req.request_url, 'View ETC Results');
    $(old_id+'_param').innerHTML = 'ETC Request #'+num+' From ARK Save File';
    $(old_id).style.visibility = 'visible';
    /* return the id of the new row in the etc table */
    return old_id;
  }
}

/* reformat the ark text so that one key and value pair appears per line */
GPW.reflowArkText = function(ark_text) {
  /* WARNING: the parser assumes one key and value per line, 
              reflow the ark text to fit the assumption */
  var out_text = [ ];
  var lines = ark_text.split('\n'), kv_pair = null;
  for ( var i = 0, l = lines.length; i < l; ++i ) {
    lines[i] = trim(lines[i]);
    /* skip empty lines */
    if ( lines[i] == '' ) {
      continue;
    /* section heading */
    } else if ( lines[i].match(/^<.+>$/) ) { 
      out_text.push(lines[i]);
    } else {
      /* lookahead to start of next key or section heading */
      kv_pair = lines[i];
      for ( var j = i+1; j < l; ++j ) {
        lines[j] = trim(lines[j]);
        if ( lines[j].match(/\[format=/) || 
             lines[j].match(/^<.+>$/) ) { break; }
        kv_pair += ' ' + lines[++i]; /* concatenate into a single line */
      }
      out_text.push(kv_pair);
    }
  }
  return out_text;
}

/* return an array of observation objects read from the ark loader text */
GPW.parseArkLoader = function(ark_text, cover_inp, errmsg) {
  /* WARNING: this is some ugly code, it just kept growing */
  var ark_obs_arr = [ ];
  var lines = GPW.reflowArkText(ark_text);
  var key, val, char0, charN, sep_ind, num;
  var parser = { BEGIN:1, PROJECT:2, COVER:3, TARGET:4, END:5, state:0 };
  var obs_names = { };
  var obs = null, etc_id = null, tool = null;
  /* clear any existing etc requests before starting to parse */
  GPW.clearEtcProductTable();
  /* parse the text line-by-line */
  for ( var i = 0, l = lines.length; i < l; ++i ) {
    lines[i] = trim(lines[i]);
    if ( lines[i] == '' ) {
      continue; /* skip empty lines */
    }
    char0   = lines[i].charAt(0);
    charN   = lines[i].charAt(lines[i].length-1);
    sep_ind = lines[i].indexOf(':');
    /* line signals start of new section */
    if ( char0 == '<' && charN == '>' ) {
      if (        lines[i] == '<BEGIN>' ) {
        parser.state = parser.BEGIN;
      } else if ( lines[i] == '<PROJECT=GALEX>' ) {
        parser.state = parser.PROJECT;
      } else if ( lines[i] == '<COVER>' ) {
        parser.state = parser.COVER;
      } else if ( lines[i] == '<TARGET>' ) {
        parser.state = parser.TARGET;
        if ( obs != null ) {
          ark_obs_arr.push(obs);
        }
        obs = new GPW.Obs();
      } else if ( lines[i] == '<END>' ) {
        parser.state = parser.END;
        if ( obs != null ) {
          ark_obs_arr.push(obs);
        }
        return ark_obs_arr; /* ignore anything that follows */
      } else {
        errmsg.push(
          "unrecognized keyword within '< >', offending line: '"+lines[i]+"'");
        return ark_obs_arr;
      }
    /* line contains a key and value pair */
    } else if ( sep_ind != -1 ) {
      /* the logic used to assign values to observations and science targets
         does not rely upon the keys appearing in any particular order */
      key = trim( lines[i].substring(0, lines[i].indexOf('[format')) );
        if ( key == null || key == '' ) {
          errmsg.push(
            "unable to extract key from line, offending line: '"+lines[i]+"'");
          return ark_obs_arr;
        }
      val = trim( lines[i].substring(sep_ind+1, lines[i].length) );
      if (        key == 'Observation Number' ) {
        obs.num = parseInt(val);
      } else if ( key == 'Observation or Field Name' ) {
        if ( val == null || val == '' ) {
          val = 'ark_obs_'+obs.num; /* a name is req for hashing */
        }
        obs.name = val;
        /* confirm observation name is valid */
        if ( ! is_observation_name_valid_gpw(obs.name, errmsg) ) {
          return ark_obs_arr;
        }
        obs.hname = GPW.getObsHashName(val);
        /* confirm observation name is unique */
        if ( obs_names[obs.hname] != null ) {
          errmsg.push(
            "duplicate observation names prohibited, offending name: '"+
            obs.name+"'");
          return ark_obs_arr;
        }
        obs_names[obs.hname] = obs.hname; /* save name in hash */
      } else if ( key == 'Right Ascension' ) {
        obs.ra = trim(val);
      } else if ( key == 'Declination' ) {
        obs.dec = trim(val);
      } else if ( key == 'Target Search Tool Request URL' ) {
        tool = GPW.tools[ GPW.indexOfTool('toast') ];
        GPW.parseArkLoaderToolRequest(tool, obs, val);
      } else if ( key == 'Brightness Checker Tool Request URL' ) {
        tool = GPW.tools[ GPW.indexOfTool('cbs') ];
        GPW.parseArkLoaderToolRequest(tool, obs, val);
      } else if ( key == 'Sky Plot Tool Request URL' ) {
        tool = GPW.tools[ GPW.indexOfTool('skp') ];
        GPW.parseArkLoaderToolRequest(tool, obs, val);
      } else if ( key == 'Visibility Tool Request URL' ) {
        tool = GPW.tools[ GPW.indexOfTool('vis') ];
        GPW.parseArkLoaderToolRequest(tool, obs, val);
      } else if ( key == 'Angle Calculator Request URL' ) {
        tool = GPW.tools[ GPW.indexOfTool('ang') ];
        GPW.parseArkLoaderToolRequest(tool, obs, val);
      } else if ( key == 'Zodiacal Light Calculator Request URL' ) {
        tool = GPW.tools[ GPW.indexOfTool('zod') ];
        GPW.parseArkLoaderToolRequest(tool, obs, val);
      } else if ( key.match(/^Science Target Name\(\d+\)$/) ) {
        num = parseInt( key.substring(key.indexOf('(')+1, key.length) );
        if ( obs.scitarg[num-1] == null ) {
          obs.scitarg[num-1] = new GPW.Scitarg();
        }
        obs.scitarg[num-1].name = val;
      } else if ( key.match(/^Science Target Right Ascension\(\d+\)$/) ) {
        num = parseInt( key.substring(key.indexOf('(')+1, key.length) );
        if ( obs.scitarg[num-1] == null ) {
          obs.scitarg[num-1] = new GPW.Scitarg();
        }
        obs.scitarg[num-1].ra = trim(val);
      } else if ( key.match(/^Science Target Declination\(\d+\)$/) ) {
        num = parseInt( key.substring(key.indexOf('(')+1, key.length) );
        if ( obs.scitarg[num-1] == null ) {
          obs.scitarg[num-1] = new GPW.Scitarg();
        }
        obs.scitarg[num-1].dec = trim(val);
      } else if ( key.match(/^Science Target Angular Size\(\d+\)$/) ) {
        num = parseInt( key.substring(key.indexOf('(')+1, key.length) );
        if ( obs.scitarg[num-1] == null ) {
          obs.scitarg[num-1] = new GPW.Scitarg();
        }
        obs.scitarg[num-1].diam = parseFloat(val); 
      } else if ( 
          key.match(/^Exposure Time Calculator Request URL\(\d+\)$/) ) {
        tool = GPW.tools[ GPW.indexOfTool('etc') ];
        num = parseInt( key.substring(key.indexOf('(')+1, key.length) );
        etc_id = GPW.parseArkLoaderEtcRequest(tool, num, val);
      } else if ( 
          key.match(/^Exposure Time Calculator Comments\(\d+\)$/) ) {
        if ( etc_id != null ) {
          $(etc_id+'_cmt').value = val;
        }
      } else if ( 
          key.match(/^Exposure Time Calculator Observation List\(\d+\)$/) ) {
        if ( etc_id != null ) {
          $(etc_id+'_obs').value = val;
        }
      } else {
        /* ARK requires trailing space after colon for keys without value */
        if ( charN == ':' ) { lines[i] += ' '; }
        if ( parser.state == parser.TARGET ) {
          obs.ark_inp.push(lines[i]);
        } else if ( parser.state = parser.COVER ) {
          cover_inp.push(lines[i]);
        }
      }
    /* unrecognized line format */
    } else {
      errmsg.push("unexpected line in file, offending line: '"+lines[i]+"'");
      return ark_obs_arr;
    }
  }
  return ark_obs_arr;
}

/* init ark save file from obs hash */
GPW.initArkTextArea = function() {
  var textarea = $('ark_writer_text');
  if ( textarea ) {
    var ark_text = '';

    /* extract the base portion of the url to prepend to product urls */
    var base_url = document.URL.substring(0, document.URL.lastIndexOf('/')+1);

    /* write prologue */
    ark_text += '<BEGIN>\n';
    ark_text += '<PROJECT=GALEX>\n';

    /* write cover page */
    ark_text += '<COVER>\n';
    for ( var i = 0, l = GPW.ark_cover_inp.length; i < l; ++i ) {
      ark_text += GPW.ark_cover_inp[i]+'\n';
    }

    /* write etc requests */
    var req, etc_tool_ind = GPW.indexOfTool('etc'), cmt, obs_list;
    for ( var i = 0, n = i + 1, l = GPW.etc_req_obj.length; i < l; ++i, ++n ) {
      req = GPW.etc_req_obj[i];
      if ( req != null && req.request_status == 'SUCCESS' ) {
        ark_text += GPW.tools[etc_tool_ind].full_name+
          ' Request URL('+n+') [format=A125]: '+base_url+req.request_url+'\n'; 
        cmt = $(req.obs_hname+'_cmt').value;
        cmt = cmt.replace(/\n|\r\n/g, ' ');
        if ( cmt.length > 500 ) {
          cmt.length = cmt.substring(0, 497);
          cmt += '...';
        }
        ark_text += GPW.tools[etc_tool_ind].full_name+
          ' Comments('+n+') [format=A500]: '+cmt+'\n';
        obs_list = $(req.obs_hname+'_obs').value;
        if ( obs_list.length > 997 ) {
          obs_list.length = obs_list.substring(0, 997);
          obs_list += '...';
        }
        ark_text += GPW.tools[etc_tool_ind].full_name+
          ' Observation List('+n+') [format=A1000]: '+obs_list+'\n';
      }
    }

    /* write the observations using the current sequence */
    var obs, obs_hnames = GPW.getObsHashNamesInSequence();
    for ( var i = 0, l = obs_hnames.length; i < l; ++i ) {
      obs = GPW.obs_obj[ obs_hnames[i] ];
      ark_text += '<TARGET>\n';
      ark_text += obs.toArkString();
      for ( var j = 0, k = obs.ark_inp.length; j < k; ++j ) {
        ark_text += obs.ark_inp[j]+'\n';
      }
      for ( var j = 0, k = GPW.tools.length; j < k; ++j ) {
        if ( GPW.tools[j].ark_lvl == 'obs' ) {
          req = obs.requests[ GPW.tools[j].name ];
          if ( req != null && req.request_status == 'SUCCESS' ) {
            ark_text += GPW.tools[j].full_name+
              ' Request URL [format=A125]: '+base_url+req.request_url+'\n';
          /* NB- ARK requires an entry for every tool */
          } else {
            ark_text += GPW.tools[j].full_name+
              ' Request URL [format=A125]: \n';
          }
        }
      }
    }

    /* terminate file */
    ark_text += '<END>\n';

    textarea.value = ark_text;
  }
}

/* 'cover_page' component validation */
function checkCoverPageValid() {
  var valid = true;
  var ark_loader = $('ark_loader');
  if ( ark_loader ) {
    var ark_text = $('ark_loader_text').value;
    var obs_hnames = GPW.getObsHashNamesInSequence();
    if ( trim(ark_text) != '' && obs_hnames.length == 0 ) {
      valid = false;
      $('ark_loader_text').addClassName('invalid');
      showErrMsg($('ark_loader_text_msg'), 
        "detected text in the ARK Save File Loader, click 'Load File'");
      if ( ! $('ark_loader').visible() ) {
        $('ark_loader').show();
      }
    } else {
      $('ark_loader_text').removeClassName('invalid');
      hideErrMsg($('ark_loader_text_msg'));
    }
  }
  return valid;
}

/* 'observations_and_targets' component validation */
function checkObservationsAndTargetsNamesValid() {
  var valid = true;
  var elem = $('observations_and_targets');
  if ( elem ) {
    var id, elem, name, errmsg = new Array();
    var all_names = {}; /* hash of names, initially empty */
    var obs_nums  = getObservationNums();
    /* save each observation name in hash, use hash to detect duplicates */
    for ( var i = 0, l = obs_nums.length; i < l; ++i ) {
      id    = 'obs'+obs_nums[i]+'_name';
      elem  = $(id);
      name = elem.value;
      hname  = GPW.getObsHashName(name);
      /* check that name is valid */
      if ( ! is_observation_name_valid_gpw(name, errmsg) ) {
        valid = false;
        elem.addClassName('invalid');
        showErrMsg($(id+'_msg'), errmsg.pop());
      /* check whether name has already appeared */
      } else if ( all_names[hname] ) {
        valid = false;
        elem.addClassName('invalid');
        showErrMsg($(id+'_msg'), 'duplicate observation names prohibited');
      /* name is valid and unique */
      } else { 
        all_names[hname] = hname; /* save name in hash */
        elem.removeClassName('invalid');
        showStatusMsg($(id+'_msg'));
      }
    }
    if ( ! valid ) {
      showErrMsg($('obs_scitarg_msg'), 'some of your entries are invalid');
    }
  }
  return valid;
}

/* exposure time calculator component validation */
function checkEtcValid() {
  /* array of validation functions for form
     components that are not part of the form will return true */
  var valid_funcs = [
    { id: 'coordinates',              func: checkCoordinatesValid },
    { id: 'etc_method',               func: checkEtcMethodValid },
    { id: 'astro_object_properties',  func: checkAstroObjectPropertiesValid },
    { id: 'flux_method',              func: checkFluxMethodValid },
    { id: 'area_extinction',          func: checkAreaExtinctionValid },
    { id: 'observation_information',  func: checkObservationInformationValid }
  ];
  /* validate each component of the form */
  var num_valid = 0;
  for ( var i = 0, l = valid_funcs.length; i < l; ++i ) {
    if ( valid_funcs[i].func() ) {
      ++num_valid;
    } else {
      /* if there was a failure, make it visible to the user */
      if ( ! $(valid_funcs[i].id).visible() ) {
        $(valid_funcs[i].id).show();
      }
    }
  }
  /* display message when failures detected */
  if ( num_valid != valid_funcs.length ) {
    showErrMsg($('etc_product_msg'), 'some of your parameters are invalid');
  } else {
    showStatusMsg($('etc_product_msg'));
  }
  return num_valid == valid_funcs.length;
}

/* deactivate a numbered step in the process */
function deactivateStep(num) {
  var elem;
  /* hide the elements for this step */
  elem = $('gpw_step'+num);
  if ( elem ) {
    elem.hide();
  }
  /* set the process to inactive in the user display */
  elem = $('gpw_process'+num);
  if ( elem ) {
    elem.removeClassName('activestep');
  }
}

/* activate a numbered step in the process */
function activateStep(num) {
  var elem;
  /* show the elements for this step */
  elem = $('gpw_step'+num);
  if ( elem ) {
    elem.show();
  }
  /* set the process to active in the user display */
  elem = $('gpw_process'+num);
  if ( elem ) {
    elem.addClassName('activestep');
  }
}

/* go backwords or forwards from current step */
function onClickStep(event) {
  var elem = Event.element(event);
  if ( elem ) {
    var prefix = 'gpw_step';
    var curr = parseInt( elem.id.substring(prefix.length) );
    /* go to next step */
    if ( elem.id.indexOf('_next') != -1 ) {
      var next = curr+1;
      /* check whether we satisfy transition criteria from step */
      if ( GPW.invoke_transition(curr, next) ) {
        deactivateStep(curr);
        activateStep(next);
      }
    /* go to previous step */
    } else if ( elem.id.indexOf('_previous') != -1 ) {
      var prev = curr-1;
      /* check whether we satisfy transition criteria from step */
      if ( GPW.invoke_transition(curr, prev) ) {
        deactivateStep(curr);
        activateStep(prev);
      }
    }
  }
}

/* start the analysis product requests */
function onClickAnalysisStart(event) {
  /* set analysis state flag */
  GPW.is_analysis_started = true;

  /* disable the start button, enable the stop button */
  $('gpw_analysis_start').addClassName('btn_disabled');
  $('gpw_analysis_stop').removeClassName('btn_disabled');

  /* initiate the requests at the front of the queue */
  var num_simultaneous_req = 2;
  for ( var i = 0; i < num_simultaneous_req; ++i ) {
    GPW.doRequest(GPW.pending_req_obj.shift());
  }
}

/* stop the analysis product requests */
function onClickAnalysisStop(event) {
  /* prevent new requests from being initiated when loading complete */
  GPW.is_analysis_started = false;

  /* set ignore flag, dequeue all loading requests, and remove from loading */
  for ( var i = 0, l = GPW.loading_req_obj.length; i < l; ++i ) {
    GPW.loading_req_obj[i].ignore_complete = true;
    GPW.setDeQueued(GPW.loading_req_obj[i].tbl_id);
  }
  GPW.loading_req_obj.splice(0, GPW.loading_req_obj.length);

  /* disable the stop button, enable the start button */
  $('gpw_analysis_stop').addClassName('btn_disabled');
  $('gpw_analysis_start').removeClassName('btn_disabled');
}

/* toggle the gpw coordinate system type */
function onClickGPWCoordSysType(event) {
  var elem = Event.element(event);
  if ( elem ) {
    if ( elem == $('gpw_eqj2000') || elem == $('gpw_eqb1950') ) {
      $('gpw_lonlat').hide();
      $('gpw_radec').show();
    } else if ( elem == $('gpw_galactic') ) {
      $('gpw_radec').hide();
      $('gpw_lonlat').show();
    }
    /* change of coordinate system means all existing requests are invalid */
    for ( var hname in GPW.obs_obj ) {
      GPW.deleteAllObsRequests( GPW.obs_obj[hname] );
    }
  }
}

/* toggle the visibility of the bulk loader */
function onClickToggleArkLoader(event) {
  var elem  = $('ark_loader');
  if ( elem ) {
    if ( ! elem.visible() ) {
      elem.show();
    } else {
      elem.hide();
    }
  }
}

/* copy the contents of the ark save file into the form */
function onClickPutArkLoader(event) {
  var elem = $('cover_page');
  if ( elem ) {
    /* read the observations from the ark text into an array */
    var cover_inp = new Array();
    var errmsg = new Array();
    var ark_text = $('ark_loader_text').value;
    var ark_obs_arr = GPW.parseArkLoader(ark_text, cover_inp, errmsg);
    if ( errmsg.length > 0 ) {
      $('ark_loader_text').addClassName('invalid');
      showErrMsg($('ark_loader_text_msg'), errmsg.pop().escapeHTML());
    } else if ( cover_inp.length == 0 && ark_obs_arr.length == 0 ) {
      $('ark_loader_text').addClassName('invalid');
      showErrMsg($('ark_loader_text_msg'), 
        'no cover page information or observations to load');
    } else {
      /* reset all sequence numbers in case the user deleted observations */
      for ( var hname in GPW.obs_obj ) {
        GPW.obs_obj[hname].num = null;
      }
      /* merge the ark observations into the obs hash */
      for ( var i = 0, l = ark_obs_arr.length; i < l; ++i ) {
        var ark_obs = ark_obs_arr[i];
        /* delete any empty science targets */
        for ( var j = 0 ; j < ark_obs.scitarg.length; ) {
/*          if ( isNaN(ark_obs.scitarg[j].ra) || isNaN(ark_obs.scitarg[j].dec) ){ */
	  if ( ark_obs.scitarg[j].ra==undefined || ark_obs.scitarg[j].dec==undefined ) {
            ark_obs.scitarg.splice(j, 1);
          } else {
            ++j;
          }
        }
        ark_obs.num = i+1;
        /* this will either replace an existing observation of the same name
           or insert the ark observation as a new observation */
        GPW.obs_obj[ ark_obs.hname ] = ark_obs;
      }
      /* replace the cover page lines */
      GPW.ark_cover_inp = cover_inp;
      $('ark_loader_text').removeClassName('invalid');
      showStatusMsg($('ark_loader_text_msg'), 'ARK Save File loaded');
    }
  }
}

/* add a new exposure time calculator request */
function onClickAddEtcRequest(event) {
  /* check that parameters are valid */
  if ( checkEtcValid() ) {
    /* build a dummy observation */
    var obs = new GPW.Obs(); /* build a dummy observation */
      var cs_type = getValueOfFirstCheckedRadio( 
        $('eqj2000', 'eqb1950', 'galactic')
      );
      if ( cs_type == 'eqj2000' || cs_type == 'eqb1950' ) {
        obs.ra = $('ra').value;
        obs.dec = $('dec').value;
      } else if ( cs_type == 'galactic' ) {
        obs.ra = $('lon').value;
        obs.dec = $('lat').value;
      }
      var param = 'RA='+obs.ra+' DEC='+obs.dec;
      var inp_type = getValueOfFirstCheckedRadio( $('exp', 'snr') );
      if ( inp_type == 'exp' ) {
        param += ' EXP='+$('exp_time').value;
      } else if ( inp_type == 'snr' ) {
        param += ' SNR='+$('snr_val').value;
      }
    /* append an new entry to the table, used for the next request */
    var old_id = GPW.appendEtcProductTableRow();
    if ( old_id != null ) {
      /* populate some cells in row */
      $(old_id+'_param').innerHTML = param;
      $(old_id).style.visibility = 'visible';
      /* build a request */
      var tool = GPW.tools[ GPW.indexOfTool('etc') ]; 
      var req = new GPW.Request(obs, tool);
        req.obs_hname = old_id;
        req.tbl_id = old_id+'_status';
      GPW.doRequest(req);
    }
  }
}

/* remove an existing exposure time calculator request */
function onClickRemoveEtcRequest(src) {
  var row = getParentElementByTagName(src, 'tr');
  if ( row != null ) {
    /* build an id matching the row number */
    var prefix = 'etc_product';
    var num = parseInt( row.id.substring(prefix.length, row.id.length) );
    var etc_id = prefix+num;
    /* search for matching ids in array of request objects and delete */
    for ( var i = 0; i < GPW.etc_req_obj.length; ) {
      if ( GPW.etc_req_obj[i].obs_hname == etc_id ) {
        GPW.etc_req_obj.splice(i, 1);
      } else {
        ++i;
      }
    }
    /* remove the row */
    row.parentNode.removeChild(row);
  }
}

/* toggle the display of exposure time calculator popup */
function onClickToggleEtcPopup(src) {
  var popup = $('etc_popup');
  if ( popup ) {
    /* hide popup */
    if ( popup.style.visibility == 'visible' ) {
      popup.style.visibility = 'hidden';
      /* create an array containing the list of checked observations */
      var obs_checked = new Array();
      var obs, obs_hnames = GPW.getObsHashNamesInSequence();
      for ( var i = 0, l = obs_hnames.length; i < l; ++i ) {
        obs = GPW.obs_obj[ obs_hnames[i] ];
        if ( $('etc_cbx'+obs.num).checked == true ) {
          obs_checked.push(obs.name);
        }
        $('etc_cbx'+obs.num).checked = false;
        $('etc_cbx'+obs.num).disabled = false;
      }
      /* save checked observations as comma-separated list */
      if ( $('etc_check_all').checked == true ) {
        $('etc_product'+GPW.etc_popup_num+'_obs').value = '$ALL';
      } else {
        $('etc_product'+GPW.etc_popup_num+'_obs').value = obs_checked.join(',');
      }
      /* clear the checkboxes */
      $('etc_check_all').checked = false;
      $('etc_check_none').checked = false;
    /* show popup */
    } else {
      var row = getParentElementByTagName(src, 'tr');
      /* set the product number associated with the popup */
      var prefix = 'etc_product';
      var num = parseInt( row.id.substring(prefix.length, row.id.length) );
      GPW.etc_popup_num = num;
      /* initialize checkboxes based on value of comma-separated list */
      var checked = $('etc_product'+GPW.etc_popup_num+'_obs').value.split(',');
      var obs_hnames = GPW.getObsHashNamesInSequence();
      /* check for special string used to indicate all are checked */
      if ( checked.length == 1 && checked[0] == '$ALL' ) { 
        for ( var i = 0, l = obs_hnames.length; i < l; ++i ) {
          $('etc_cbx'+GPW.obs_obj[ obs_hnames[i] ].num).checked = true;
          $('etc_cbx'+GPW.obs_obj[ obs_hnames[i] ].num).disabled = true;
        }
        $('etc_check_all').checked = true;
      } else {
        for ( var i = 0, l = obs_hnames.length; i < l; ++i ) {
          obs = GPW.obs_obj[ obs_hnames[i] ];
          /* search for name in the array of checked observations */
          if ( checked.include(obs_hnames[i]) ) {
            $('etc_cbx'+GPW.obs_obj[ obs_hnames[i] ].num).checked = true;
          }
        }
      }
      /* center popup on table horizontally and current row vertically */
      var pos = row.cumulativeOffset();
      var dx = $('etc_product_table').getWidth()/2 - popup.getWidth()/2;
      var dy = pos[1] - popup.getHeight()/2;
      /* set popup absolute position */
      popup.style.left = dx+'px';
      popup.style.top  = dy+'px';
      popup.style.visibility = 'visible';
    }
  }
}

/* check all observations in the exposure time calculator popup */
function onClickEtcCheckAll(event) {
  /* when the user unclicks 'check all' it is the same as 'check none' */
  if ( $('etc_check_all').checked == false ) {
    onClickEtcCheckNone(event);
  } else {
    $('etc_check_none').checked = false;
    /* check all observations and set disabled */
    var obs_hnames = GPW.getObsHashNamesInSequence();
    for ( var i = 0, l = obs_hnames.length; i < l; ++i ) {
      $('etc_cbx'+GPW.obs_obj[ obs_hnames[i] ].num).checked = true;
      $('etc_cbx'+GPW.obs_obj[ obs_hnames[i] ].num).disabled = true;
    } 
  }
}

/* uncheck all observations in the exposure time calculator popup */
function onClickEtcCheckNone(event) {
  $('etc_check_all').checked = false;
  /* uncheck all observations and set enabled */
  var obs_hnames = GPW.getObsHashNamesInSequence();
  for ( var i = 0, l = obs_hnames.length; i < l; ++i ) {
    $('etc_cbx'+GPW.obs_obj[ obs_hnames[i] ].num).checked = false;
    $('etc_cbx'+GPW.obs_obj[ obs_hnames[i] ].num).disabled = false;
  } 
}

/* add all observations to the queue */
function onClickQueueAll(event) {
  var table = $('gpw_product_table');
  if ( table ) {
    /* add observations without an existing completed request and 
       not currently in the request queue to the request queue */ 
    GPW.queueProductTable(true, true);
  }
}

/* remove all observations from the queue */
function onClickQueueNone(event) {
  var table = $('gpw_product_table');
  if ( table ) {
    /* remove all pending requests from the request queue */
    GPW.pending_req_obj.splice(0, GPW.pending_req_obj.length);
    GPW.queueProductTable(false, false);
  }
}

/* toggle the queue status for a single observation in a cell */
function onClickToggleQueueCell(src) {
  var cell = $(src); /* get prototype-extended element */
  if ( cell ) {
    if ( cell.hasClassName('queued') ) {
      /* extract observation name */
      var last_tok_ind = cell.id.lastIndexOf('_');
      var name_id = cell.id.substring(0, last_tok_ind) + '_name';
      var obs_name = $(name_id).innerHTML;
      var obs_hname = GPW.getObsHashName(obs_name);
      /* remove completed and pending requests from queue */
      if ( GPW.obs_obj[obs_hname] ) {
        GPW.deleteToolObsRequests(
          GPW.obs_obj[obs_hname], cell.id.substring(last_tok_ind+1)
        );
        GPW.setDeQueued(cell.id);
      }
    } else if ( cell.hasClassName('dequeued') ) {
      /* extract observation name */
      var last_tok_ind = cell.id.lastIndexOf('_');
      var name_id = cell.id.substring(0, last_tok_ind) + '_name';
      var obs_name = $(name_id).innerHTML;
      var obs_hname = GPW.getObsHashName(obs_name);
      /* add new request to queue */
      if ( GPW.obs_obj[obs_hname] ) {
        var tool_ind = GPW.indexOfTool(cell.id.substring(last_tok_ind+1));
        if ( tool_ind != -1 ) {
          GPW.pending_req_obj.push(
            new GPW.Request(GPW.obs_obj[obs_hname], GPW.tools[tool_ind])
          );
          GPW.setQueued(cell.id);
        }
      }
    }
  }
}

/* open the contents of the ark save file in a new window */
function onClickShowWinArkWriter(event) {
  var textarea = $('ark_writer_text');
  if ( textarea ) {
    var win = window.open(
      '', 'ark_writer_win', 'height=800,width=600,'+
      'resizable=yes,scrollbars=yes,menubar=yes');
    /* window will be null in cases where blocked by popup blocker */
    if ( win != null ) {
      var doc = win.document;
      doc.open();
      doc.write('<pre>',textarea.value.escapeHTML(),'</pre>');
      doc.close();
      win.focus();
    }
  }
}

/* initialize event handlers after page has loaded */
function initGPW() {
  /* set wizard navigation event handlers */
  var elem;
  for ( var i = 0, l = GPW.steps.length; i < l; ++i ) {
    elem = $('img_step'+GPW.steps[i]+'_previous');
    if ( elem ) {
      Event.observe(elem, 'click', onClickStep);
    }
    elem = $('btn_step'+GPW.steps[i]+'_previous');
    if ( elem ) {
      Event.observe(elem, 'click', onClickStep);
    }
    elem = $('img_step'+GPW.steps[i]+'_next');
    if ( elem ) {
      Event.observe(elem, 'click', onClickStep);
    }
    elem = $('btn_step'+GPW.steps[i]+'_next');
    if ( elem ) {
      Event.observe(elem, 'click', onClickStep);
    }
  }
  /* set 'cover_page' event handlers */
  if ( $('ark_loader_show') ) {
    Event.observe($('ark_loader_show'), 'click', onClickToggleArkLoader);
  }
  if ( $('ark_loader_put') ) {
    Event.observe($('ark_loader_put'), 'click', onClickPutArkLoader);
  }
  /* set coordinate system type event handlers */
  var cs_type = $('gpw_eqj2000', 'gpw_eqb1950', 'gpw_galactic');
  setFormHandler(cs_type, 'click', onClickGPWCoordSysType);
  /* set 'etc' event handlers */
  if ( $('etc_add_request') ) {
    Event.observe($('etc_add_request'), 'click', onClickAddEtcRequest);
  }
  /* set exposure time calculator popup handlers */
  if ( $('etc_check_all') ) {
    Event.observe($('etc_check_all'), 'click', onClickEtcCheckAll);
  }
  if ( $('etc_check_none') ) {
    Event.observe($('etc_check_none'), 'click', onClickEtcCheckNone);
  }
  /* set analysis control event handlers */
  if ( $('gpw_analysis_start') ) {
    Event.observe($('gpw_analysis_start'), 'click', onClickAnalysisStart);
  }
  if ( $('gpw_analysis_stop') ) {
    Event.observe($('gpw_analysis_stop'), 'click', onClickAnalysisStop);
  }
  /* set queue control event handlers */
  if ( $('queue_all') ) {
    Event.observe($('queue_all'), 'click', onClickQueueAll);
  }
  if ( $('queue_none') ) {
    Event.observe($('queue_none'), 'click', onClickQueueNone);
  }
  /* set ark save file event handlers */
  if ( $('ark_writer_show_win') ) {
    Event.observe($('ark_writer_show_win'), 'click', onClickShowWinArkWriter);
  }
  if ( $('ark_writer_show_win2') ) {
    Event.observe($('ark_writer_show_win2'), 'click', onClickShowWinArkWriter);
  }
  /* preload some images that are used with css effects */
  var img1 = new Image();
    img1.src = '../img/indicator_medium.gif';
}

Event.observe(window, "load", initGPW);
