// $Id: webapp.js,v 1.18 2006/12/18 23:36:42 mikeg Exp $

// ***
// *** Generic state functions
// ***

// Keep track of client-side state 
// TODO: make this into a complete object
var global_state =
{
  "frontpage_organism_id" : 35,
  "preferences_molecules_new_window" : "no",
  "frontpage_warning" : "",
  "preferences_minimum_confidence" : 0.3,
  "preferences_max_suggest": 10,
  "preferences_name" : "Anonymous Coward",
  "preferences_show_resultsets" : "yes",
  "preferences_search_result_style" : "default",
  "show_repeated_phrases" : 0
}
var current_page = "";
var frontpage_loaded = false;
var accumulator = {global_state : global_state};
var template = {};

// set the preference key k to a value v
function set_pref(k, v, saveit) // pref_set
{
  global_state[k] = v;
  if (saveit != undefined)
  {
    var _k = k;
    var _v = v;
    urls = [];
    urls.push({ url:session_setvar(_k, _v) });
    url_update(urls);
  }
  return false;
}

// retrieve the value of a preferences variable
function get_pref(k) // pref_get
{
  var ks = keys(global_state);
  for (var i=0; i<ks.length; i++)
  {
    if (ks[i] == k)
    {
      //alert("get_pref: inspecting key " + i + " it is " + ks[i] + " and value is " + global_state[ks[i]]);
      return global_state[k];
    }
  }
  return false;
}

// The next state is the same as the current one
var global_state_next = global_state;

// Call set_state with new state
function refresh_state()
{
  // retrieve contents of /api/web/session?what=getvars
  var urls = [];
  urls.push( {url:"/api/web/session?what=getvars", type:"JSON", callback: set_state });
  url_update(urls);
  return false;
}

// initialize the server state to the global state, and
// delete the global_state on the client so that refresh_state
// can work
function init_state()
{
  var state_keys = keys(global_state);
  var urls = [];
  for (i=0; i<state_keys.length; i++)
  {
    var k = state_keys[i];
    var v = global_state[k];
    //alert("got _k is " + _k + " and _v is " + _v);
    //alert("init state: setting on server: "+k+" to "+v+" if new");
    urls.push( {url:session_setvar(k, v)+"&ifnew=1" } );
  }
  //alert("init state: clearing global state");
  global_state = {};
  //alert("init state: starting the setting now.");
  urls.push( {callback: refresh_state} );
  url_update(urls);
}

// set new state and call paint_state with the list of changes
function set_state(new_state)
{
   //eval("var new_state = " + new_state);
   global_state_next = new_state;

   changes = global_state_changes();
   global_state = global_state_next;
   if (changes)
   {
     paint_state(changes);
   }
}

function update_random_molecule_list()
{
  /*
  var urls = new Array();
  var this_accumulator = {};
  urls.push({ callback: function(x){ puttmpl("organismlist_simple_random",  "organismlist_simple_random", {} );} });
  url_update(urls);
  */
  puttmpl("organismlist_simple_random",  "organismlist_simple_random", {} );
}

function init_tabs()
{
  Tab.register("tab_buttons","tab_content", load_tabs_from_session);
}

// paint fields in the state structure given
function paint_state(state)
{
  saydebug("paint_state called");
  set_status("Loading...")
  // accumulate requests to be made and actions to be performed
  var urls = [];
  if (frontpage_loaded!=true)
  {
    saydebug("drawing front page");
    if (current_page == "frontpage")
    {
      urls.push({ callback: function(){puttmpl("body_content", "body_frontpage", {});} });
    }
    else if (current_page == "search")
    {
      urls.push({ callback: function(){puttmpl("body_content", "body_search2");} }); 
      var this_accumulator1 = {};
      var this_accumulator2 = {};
      urls.push({ url:"/api/web/search_terms", type:"JSON", callback:function(x){this_accumulator1.details=x;} });
      urls.push({ callback: function(x){ puttmpl("search_terms", "search_terms", this_accumulator1, 1);} });
      // urls.push({ url:"/api/web/search_results", type:"JSON", callback:function(x){this_accumulator2.results=x;} });
      // urls.push({ callback: function(x){ puttmpl("search_results", "search_results", this_accumulator2, 1);} });
    }
    else if (current_page == "settings")
    {
      urls.push({ callback: function(){puttmpl("organismlist", "organismlist_placeholder");} }); 
    }
    else if (current_page == "about")
    {
      urls.push({ callback: function(){puttmpl("body_content", "body_about", {});} });
    }
    else if (current_page == "molecule")
    {
      urls.push({ callback: function(){ puttmpl("body_content", "body_molecule", {});} });
      var accumulator = {};
      urls.push({ url:"/api/web/organism",
                  type:"JSON",
                  callback: function(x){ accumulator.organisms = x; }
                });
      //urls.push({ callback: function(x){ puttmpl("organismlist_simple",  "organismlist_simple", {});} });
      //urls.push({ callback: function(x){ puttmpl("organismlist_simple_random",  "organismlist_simple_random", {});} });

      //urls.push({ callback: function(){
      //  setTimeout("init_tabs();",3000);
      //}});
    }
    frontpage_loaded=true;
  }
  if (current_page == "settings")
  {
    urls.push({ callback: function(){puttmpl("body_content", "body_settings", {});} });
  }
  if (current_page == "explore")
  {
    urls.push({ callback: function(){puttmpl("body_content", "body_explore", {});} });
  }
  if (current_page == "explore2")
  {
    urls.push({ callback: function(){puttmpl("body_content", "body_explore2", {});} });
  }
  if (current_page == "collate")
  {
    urls.push({ callback: function(){puttmpl("body_content", "body_collate", {});} });
  }

  // inspect all keys
  var state_keys = keys(state);
  for (i=0; i<state_keys.length; i++)
  {
    var k = state_keys[i];
    var v = state[state_keys[i]];

    // if in the frontpage...
    if (current_page == "frontpage")
    {
      if (k=="frontpage_organism_id")
      {
        urls.push({ url:"/api/web/organism",
                    type:"JSON",
                    callback: function(x){ accumulator.organisms = x; }
                  });
        urls.push({ url:"/api/web/organism?organism_id=" + v,
                    type:"JSON",
                    callback: function(x){ accumulator.organism = x; }
                  });
        urls.push({ callback: function(x){ accumulator.global_state = global_state;} });
        urls.push({ callback: function(x){ puttmpl("organismlist",  "organismlist", accumulator);} });
        urls.push({ callback: function(x){ puttmpl("simple_search", "simple_search", accumulator);} });
      }
      else if (k=="frontpage_warning")
      {
        var _k = k;
        var _v = v;
        urls.push({ callback: function(x){puthtml("warning", _v);} });
      }
    }
    // if in the settings page
    else if (current_page == "settings")
    {
     
    }
  }
  urls.push({ callback:unset_status });
  url_update(urls);
  init_tabs(); // does this work without the setTimeout?
}

var urls = [];
urls.push( {url:"/api/web/organism", callback:function(x){ accumulator.organisms = x; } } )
urls.push( {callback:function(){ puttmpl("organismlist", "organismlist", accumulator);}  })

// ***
// *** Bioverse-specific functions
// ***

function session_setvar(_var, val)
{
  return "/api/web/session?what=setvar&var=" + _var + "&val=" + val;
}

function session_setvar_append(_var, val)
{
  return "/api/web/session?what=setvar&var=" + _var + "&val=" + val + "&type=array&action=append";
}

function session_delvar(_var)
{
  return "/api/web/session?what=delvar&var=" + _var;
}

// Set the sessions organism to organism_id
function set_organism(organism_id)
{
  saydebug("setting organism to " + organism_id)
  var urls = [];
  urls.push( {url:session_setvar("frontpage_organism_id", organism_id) } );
  urls.push( {callback: refresh_state} );
  url_update(urls);

  return false;
}

// return list of keys of an associative array
function keys(array)
{
  var ks = [];
  for (var k in array)
  {
    ks.push(k);
  }
  return ks;
}

// return list of distinct items in a list
function distinct(array)
{
  var items = {};
  for (i in array)
  {
    items[array[i]] += 1;
  }
  return keys(items);
}

// concatanate all arrays in first argument list
function concat_arrays(arrays) { var items = []; for (var i=0; i<arrays.length; i++) { for (var j=0; j<arrays[i].length; j++) { items.push(arrays[i][j]); } } return items; }

function concat_arrays(arrays)
{
  var items = [];
  for (var i=0; i<arrays.length; i++)
  {
    for (var j=0; j<arrays[i].length; j++)
    {
      items.push(arrays[i][j]);
    }
  }
  return items;
}

function global_state_changes()
{
  saydebug("global_state_changes");
  var changes = {};
  var keys_to_check = distinct(concat_arrays([keys(global_state), keys(global_state_next)]))
  for (var i=0; i<keys_to_check.length; i++)
  {
    if (global_state[keys_to_check[i]] != global_state_next[keys_to_check[i]])
    {
      changes[keys_to_check[i]] = global_state_next[keys_to_check[i]];
    }
  }
  return changes;
}

function plural(val, one, more)
{
  if (val==1)
  { return one; }
  else
  { return more; }
}

var bioverse_modifiers= {
  date : function(str) {
    var monthname=new Array("January","February","March","April","May","June",
                            "July","August","September","October","November","December");
  },
  bold : function(str, phrase) {
    phrase = phrase.replace("\+", "\\+");
    var re = new RegExp(phrase, "gi");
    str = str.replace(re, "<b>$&</b>");
    return str;
  },
  // if str is longer than maxlen,
  // then shorten it and append three dots
  shorten : function(str, maxlen) {
    if (str.length>maxlen)
    { return str.substring(0,maxlen) + "..."; }
    else
    { return str; }
  },
  // if str is 1, then append one to str
  // else, append more to str
  plural : function(str, one, more) {
    if (str==1)
    { return str + one; }
    else
    { return str + more; }
  },
  // if str is "empty" (only contains whitespaces, or no characters at all), then return
  // replacement, else return str
  ifempty : function(str, replacement) {
    if (String(str).match(/^\s*$/))
    { return replacement; }
    else
    { return str; }
  },
  // is a similarity alignment -- lowercase letters are grey
  // uppercase letters are blue
  alignmentsequence : function(str) {
/*
    str = String(str);
    str = str.replace(/([a-z\-]+)/g, "<span style='color: lightgrey;'>$&</span>");
    str = str.replace(/([A-Z]+)/g, "<span style='color: blue;'>$&</span>");
*/
    return str;
  },
  // is a sequence alignment -- each AA gets its own color
  // (this uses the trick that text added to str is not uppercase
  // and text to be modified is always uppercase)
  sequence : function(str) {
    var out = String(str);
/*

    var colors = {
      green   : "#66aa66",
      cyan    : "#66aaaa",
      blue    : "#9999aa",
      purple  : "#aa66aa",
      red     : "#aa6666",
      darkred : "#aa4444",
      grey    : "#666666"
    };

    out = out.replace(/([A]+)/g, "<span style='color: "+colors["green"  ]+";'>$&</span>");
    out = out.replace(/([I]+)/g, "<span style='color: "+colors["green"  ]+";'>$&</span>");
    out = out.replace(/([L]+)/g, "<span style='color: "+colors["green"  ]+";'>$&</span>");
    out = out.replace(/([M]+)/g, "<span style='color: "+colors["green"  ]+";'>$&</span>");
    out = out.replace(/([V]+)/g, "<span style='color: "+colors["green"  ]+";'>$&</span>");
    out = out.replace(/([F]+)/g, "<span style='color: "+colors["green"  ]+";'>$&</span>");
    out = out.replace(/([P]+)/g, "<span style='color: "+colors["green"  ]+";'>$&</span>");
    out = out.replace(/([Y]+)/g, "<span style='color: "+colors["cyan"   ]+";'>$&</span>");
    out = out.replace(/([W]+)/g, "<span style='color: "+colors["cyan"   ]+";'>$&</span>");
    out = out.replace(/([S]+)/g, "<span style='color: "+colors["blue"   ]+";'>$&</span>");
    out = out.replace(/([T]+)/g, "<span style='color: "+colors["blue"   ]+";'>$&</span>");
    out = out.replace(/([Q]+)/g, "<span style='color: "+colors["blue"   ]+";'>$&</span>");
    out = out.replace(/([N]+)/g, "<span style='color: "+colors["blue"   ]+";'>$&</span>");
    out = out.replace(/([C]+)/g, "<span style='color: "+colors["purple" ]+";'>$&</span>");
    out = out.replace(/([H]+)/g, "<span style='color: "+colors["purple" ]+";'>$&</span>");
    out = out.replace(/([D]+)/g, "<span style='color: "+colors["red"    ]+";'>$&</span>");
    out = out.replace(/([E]+)/g, "<span style='color: "+colors["red"    ]+";'>$&</span>");
    out = out.replace(/([K]+)/g, "<span style='color: "+colors["darkred"]+";'>$&</span>");
    out = out.replace(/([R]+)/g, "<span style='color: "+colors["darkred"]+";'>$&</span>");
    out = out.replace(/([G]+)/g, "<span style='color: "+colors["grey"   ]+";'>$&</span>");
*/
    return out;
  },
  // draw a ruler like 0........10........11...
  sequenceruler : function(str) {
    var out = "";
    for (var i=0; i<str.length;i+=1)
    {
      if (i % 10 == 0)
      {
        var newtext = "|" + String(i);
        out += newtext;
        i += newtext.length - 1;
      }
      else { out += "&nbsp;"; }
    }
    return out;
  }


};


function addEvent(obj, event_type, func)
{ 
 if (obj.addEventListener)
 { 
   obj.addEventListener(event_type, func, true); 
   return true; 
 } else if (obj.attachEvent)
 { 
   var r = obj.attachEvent("on"+event_type, func); 
   return r; 
 } else
 { 
   return false; 
 } 
}

// get "current working directory"
function get_cwd()
{
  // match until the last slash (/) in the location
  var path = new String(document.location);
  return path.match(".*/");
}

// get root directory for static content
function get_root()
{
  return "/s/";
}

function ping_server()
{
  url_update([{url:"/ping"}]);
}

var __ORGANISMS = new Array();

function all_organisms()
{
  return __ORGANISMS;
}

function organism_by_id(organism_id)
{
  for (var i=0; i<__ORGANISMS.length; i++)
  {
    if (__ORGANISMS[i].organism_id==organism_id)
    { 
      return __ORGANISMS[i];
    }
  }

  if (organism_id == 0)
  {
    return {"organism_id":0,
            "db_name":"*",
            "long_name": "all organisms",
            "molecule_count": 0,
            "interaction_count": 0,
            "updated": "*",
            "short_name": "all organisms",
            "path": [],
            "tiny_name": "all"}
  }

  return {"organism_id":organism_id}
}

function organism_by_db_name(db_name)
{
  for (var i=0; i<__ORGANISMS.length; i++)
  {
    if (__ORGANISMS[i].db_name==db_name)
    { 
      return __ORGANISMS[i];
    }
  }
  return {}
}

var agent=navigator.userAgent.toLowerCase();
var browser_is_ffox = (agent.indexOf("firefox") != -1);
var browser_is_ffox_1  = (agent.indexOf("firefox/1.") != -1);
var browser_is_ffox_2  = (agent.indexOf("firefox/2.") != -1);

// called on body load of root.
function init_root(pagename)
{
  // a friendly reminder
  if (!browser_is_ffox_1 && !browser_is_ffox_2)
  {
    warning("The target browser for Bioverse is <a style='color: white; font-weight: bold' href='http://www.mozilla.org/products/firefox/'>Mozilla Firefox</a> 1.x-2.x for all platforms.", 0);
  }

  set_status("Loading...")

  current_page = pagename;
  begin_pinging_server();
  var urls = [];

  // render the initial screen
  urls.push({ callback: init_state    });
  urls.push({ callback: refresh_state });

  urls.push({ callback: function(){puttmpl("body", "body", {});} });
  urls.push({ url:"/api/web/organism",
              type:"JSON",
              callback: function(x){
                                      __ORGANISMS = x;
                                      /*
                                      var molecule_count = 0;
                                      for (i=0; i<__ORGANISMS.length; i++)
                                      {
                                        molecule_count += __ORGANISMS[i].molecule_count;
                                      }
                                      __ORGANISMS.unshift({"organism_id":0,
                                                          "db_name":"*",
                                                          "long_name": "all " + x.length + " organisms (" + molecule_count.commify() + " molecules)",
                                                          "molecule_count": molecule_count,
                                                          "interaction_count": 0,
                                                          "updated": "*",
                                                          "short_name": "all " + x.length + " organisms",
                                                          "path": [],
                                                          "tiny_name": "all"})
                                      */
                                   }
            });
  urls.push({ callback: function(x){ puttmpl("search_organismlist",  "search_organismlist", {});} });

  urls.push({ callback:unset_status });

  url_update(urls);

  return false;
}

// called on body load of about
function init_about()
{
  begin_pinging_server();
  var urls = [];

  // render the initial screen
  urls.push( {url:get_root()+"ping",    type:"JSON", tmpl:"body", id:"body" } );
  urls.push( {url:get_root()+"ping",    type:"JSON", tmpl:"body_about", id:"body_content" } );
  urls.push( {url:get_root()+"ping",    type:"JSON", tmpl:"organismlist_placeholder", id:"organismlist" } ); //urls.push( {url:get_cwd()+"render?what=body;type=JSON",  type:"JSON", tmpl:"body_about", id:"body_content" } );

  url_update(urls);
}

// called on body load of settings
function init_settings()
{
  begin_pinging_server();
  var urls = [];

  // render the initial screen
  urls.push( {url:get_root()+"ping",                 type:"JSON", tmpl:"body", id:"body" } );
  urls.push( {url:get_root()+"render?what=settings", type:"JSON", tmpl:"body_settings", id:"body_content" } );
  urls.push( {url:get_root()+"ping",                 type:"JSON", tmpl:"organismlist_placeholder", id:"organismlist" } ); 
  url_update(urls);
}

function settings_update()
{
  var urls = [];
  urls.push( {url:get_root()+"render?what=settings", type:"JSON", tmpl:"body_settings", id:"body_content" } );
  url_update(urls);
}


/* ping the server every 10 minutes to keep the session alive. */
function begin_pinging_server()
{
  setInterval(ping_server, (1000*60)*10);
}

var go_dot_interval = 0;
var go_dot_counter = 0;

/* Add a single dot to the status text */
function go_dot()
{
  saydebug("Adding dot to status");
  puthtml('status', gethtml('status') + ".");
  go_dot_counter += 1;
 
  if (go_dot_counter == 5)
  { 
    puthtml('status', gethtml('status') + "working hard ");
  }
  if (go_dot_counter == 10)
  { 
    puthtml('status', gethtml('status') + "really! ");
  }
}

// start calling go_dot every 1000 ms
function start_godot()
{
  stop_godot();
  go_dot_interval = setInterval("go_dot()", 1000);
}

// stop calling go_dot
function stop_godot()
{
  if (go_dot_interval !== 0)
  {
    clearInterval(go_dot_interval);
    go_dot_interval = 0;
    go_dot_counter = 0;
  }
}

// set status to msg and begin go_dot
function set_status(msg)
{
  puthtml("status", msg);
  start_godot();
}
// clear status
function unset_status()
{
  stop_godot();
  puthtml("status", "");
}

// useful?
function make_content_request(name)
{
  return {url:get_root()+"render?what="+name, type:"JSON", tmpl:name, id:name }
}

function myescape(stuff)
{
  return encodeURIComponent(stuff);

  // see page 467 in flanangan
  var mapping = {'\\!': '%21', '\\$': '%24', '\\&': '%26',
                 "\\'": '%27', '\\(': '%28', '\\)': '%29',
                 '\\*': '%2a', '\\+': '%2b', '\\,': '%2c',
                 '\\-': '%2d', '\\.': '%2e', '\\/': '%2f',
                 '\\:': '%3a', '\\;': '%3b', '\\=': '%3d',
                 '\\?': '%3f', '\\@': '%40', '\\_': '%5f',
                 '\\~': '%7e'}
  for (m in mapping)
  {
    //stuff = stuff.replace(m, mapping[m]);
    stuff = stuff.replace(RegExp(m, "g"), mapping[m]); 
  }
  return stuff;
}

function myescape2(stuff)
{
  stuff = stuff.replace(RegExp('\'', "g"), "\\'");
  return stuff;
}

function myescape3(stuff)
{
  stuff = stuff.replace(RegExp('"', "g"), '\\"');
  return stuff;
}

function show_content(name, force_render)
{
  var template_name = "content_" + name;
  var tab_name = "content_" + name;
  var name_to_title = {
    "about":                "About",
    "confidence_methods":   "About: Confidence methods",
    "contact":              "About: Contact",
    "credits":              "About: Credits",
    "data_status":          "About: Data status",
    "dataflow_schema":      "About: Dataflow and schema",
    "help":                 "About: Quick help",
    "introductory_notes":   "About: Introducory notes",
    "references":           "About: References",
    "relationship_methods": "About: Contextual relationship determination",
    "sources":              "About: Data sources",
    "summary":              "About: Summary",
    "versions":             "About: Historical updates",
    "api":                  "Remote Procedure Call",
    "api_technical":        "api: Technical Details",
    "api_examples":         "api: Examples"
  };

  if (Tab.exists(tab_name))
  {
    Tab.focus(tab_name);
    if (!force_render) // if we're not forced to render, then exit
    {
      return false;
    }
  }

  Tab.append(tab_name, "");

  if (name in name_to_title)
  {
    var tab_title = name_to_title[name];
  }
  else
  {
    var tab_title = name.capitalize().replace(/_/g, " ");
  }

  Tab.set_button(tab_name, tab_title);

  var destination = Tab.tabs[tab_name].content.id;

  var molecule_urls = [];
  molecule_urls.push({ callback: function(){ puttmpl(destination, template_name, {}, 1);} });
  url_update(molecule_urls);
}

function show_organism(organism_id)
{
  var template_name = "organism_info";
  var tab_name = "organism_" + String(organism_id);
  var tab_title = (organism_by_id(organism_id)).short_name;

  if (!Tab.exists(tab_name))
  {
    Tab.append(tab_name, tab_title);
  }
  else
  {
    Tab.focus(tab_name);
  }
  Tab.set_button(tab_name, tab_title);

  var destination = Tab.tabs[tab_name].content.id;
  var urls = [];
  var accumulator = {};
  //urls.push({ url:"/api/web/organism?organism_id="+organism_id, type:"JSON", callback: function(x){accumulator.details=x} })
  accumulator.details = organism_by_id(organism_id);
  urls.push({ callback: function(){ puttmpl(destination, template_name, accumulator, 1);} });
  url_update(urls);
}

function show_settings(repaint)
{
  var repaint;
  var template_name = "body_settings";
  var tab_name = "settings";
  if (!Tab.exists(tab_name))
  {
    Tab.append(tab_name, "");
    repaint = 1;
  }
  else
  {
    Tab.focus(tab_name);
  }
  Tab.set_button(tab_name, "Preferences");

  var repaint = 1;
  if (repaint)
  {
    var destination = Tab.tabs[tab_name].content.id;
    var urls = [];
    urls.push({ callback: function(){ puttmpl(destination, template_name, {}, 1);} });
    url_update(urls);
  }

}

function show_admin(repaint)
{
  var repaint;
  var template_name = "body_admin";
  var tab_name = "admin";
  if (!Tab.exists(tab_name))
  {
    Tab.append(tab_name, "");
    repaint = 1;
  }
  else
  {
    Tab.focus(tab_name);
  }
  Tab.set_button(tab_name, "Admin");

  var repaint = 1;
  if (repaint)
  {
    var destination = Tab.tabs[tab_name].content.id;
    var urls = [];
    var accumulator = {};
    //urls.push({ url:"/api/web/log_aggregate?hours=200", type:"JSON", callback: function(x){accumulator.stats=x} })
    urls.push({ callback: function(){ puttmpl(destination, template_name, accumulator, 0);} });
    url_update(urls);
  }
}

function show_log_stats(form_id)
{
  var template_name = "log_stats";
  var destination = "log_stats_container";
  var urls = [];
  var accumulator = {};

  url = form2url(el(form_id));

  urls.push({ url:url, type:"JSON", callback: function(x){accumulator.stats=x} })
  urls.push({ callback: function(){ puttmpl(destination, template_name, accumulator, 0);} });
  url_update(urls);
}

function load_tabs_from_session()
{
  var tabs = global_state["tabs"];
  var things_to_do = new Array(); // accumulate a list of callbacks to call in series
  var data_to_do_things_with = new Array(); // avoid namespace collision by using a unique place for each item
  if (tabs)
  {
    for (var i=0; i<tabs.length; i++)
    {
      var j = new Object(i);
      var tab_name = tabs[i];
      var title = "Tab " + i;
      var content = "Tab content";
      // determine tab content by its tab_name
      if (tab_name == "settings")
      {
        title="Loading";
        content="Loading..."
        Tab.append(tab_name, title, content);
        things_to_do.push({ callback:function(){ show_settings(1); } });  // retrieve the settings
      }
      else if (tab_name == "admin")
      {
        title="Loading";
        content="Loading..."
        Tab.append(tab_name, title, content);
        things_to_do.push({ callback:function(){ show_admin(1); } });  // retrieve the settings
      }

      else if (tab_name.match("^content_(.+)"))
      {
        var m = tab_name.match("^content_(.+)");
        if (m)
        {
          data_to_do_things_with.push(m);
          title="Loading";
          content="Loading..."
          Tab.append(tab_name, title, content);
          things_to_do.push({ callback:function(){ var local_m = data_to_do_things_with.shift(); show_content(local_m[1],1); } });  // retrieve the content
        }
      }
      else if (tab_name.match("^organism_([0-9]+)"))
      {
        var m = tab_name.match("^organism_([0-9]+)");
        if (m)
        { 
          data_to_do_things_with.push(m);
          title="Loading";
          content="Loading..."
          Tab.append(tab_name, title, content);
          things_to_do.push({ callback:function(){ var local_m = data_to_do_things_with.shift(); show_organism(local_m[1]); } }); // retrieve the data
        }
      }
      else if (tab_name.match("^search2_(.+)"))
      {
        var m = tab_name.match("^search2_(.+)");
        if (m)
        { 
          data_to_do_things_with.push(m);
          title="Loading";
          content="Loading..."
          Tab.append(tab_name, title, content);
          things_to_do.push({ callback:function(){ var local_m = data_to_do_things_with.shift(); search2_retrieve(local_m[1]); } }); // retrieve the data
        }
      }
      else if (tab_name.match("^search_(.+)"))
      {
        var m = tab_name.match("^search_(.+)");
        if (m)
        { 
          data_to_do_things_with.push(m);
          title="Loading";
          content="Loading..."
          Tab.append(tab_name, title, content);
          things_to_do.push({ callback:function(){ var local_m = data_to_do_things_with.shift(); search_retrieve(local_m[1]); } }); // retrieve the data
        }
      }
      else if (tab_name.match("^molview_([0-9]+)_([0-9]+)"));
      {
        var m = tab_name.match("^molview_([0-9]+)_([0-9]+)");
        if (m)
        {
          content = "<span class='fakelink' onclick='javascript:molecule_details(" + m[1] + "," + m[2] + ", \"\", 0, 0, 1);'>Load this molecule</span>";
          o = organism_by_id(m[1]);
          title = "<span class='fakelink' onclick='javascript:molecule_details(" + m[1] + "," + m[2] + ", \"\", 0, 0, 1);'><img valign='baseline' src='/s/images/molecule_icon.png' /> " +  o.short_name + " molecule " + m[2] + "</span>";
          Tab.append(tab_name, title, content);

          // if there is only one tab (so far), then call molecule_details right now
          if (Tab.length()==1)
          {
	    // this works out conveniently for us because later we set the focus to the first (this) tab
            molecule_details(m[1], m[2], "", 0, 0, 1);
	  }
        }
      }
    }
    Tab.focus(tabs[0]);
    url_update(things_to_do); // load each tab serially
  }
}

function molecule_details(organism, molecule, kargs, add, remove, force_render)
{
  var original_molecule = molecule;
  molecule = parseInt(molecule);
  if (isNaN(molecule) || molecule <= 0)
  {
    log("Invalid molecule number " + original_molecule);
    notice("Invalid molecule number", 5);
    return false;
  }
  organism = parseInt(organism);
  if (isNaN(organism) || organism <= 0)
  {
    notice("Invalid organism number", 5);
    return false;
  }

  notice("", 0);

  var tab_name = "molview_" + organism + "_" + molecule;
  if (Tab.exists(tab_name))
  {
    Tab.focus(tab_name);
    if (!force_render) // if we're not forced to render, then exit
    {
      return false;
    }
  }
  else
  {
    Tab.append(tab_name, "Molecule " + organism + "," + molecule, "");
    // alert("After appending the tab, (this.tabs[${tab_name}].content == el(${id})) is:".process({tab_name:tab_name, id:"tab_content_" + tab_name}) + (Tab.tabs[tab_name].content == el("tab_content_" + tab_name)));
  }

  // window.scrollTo(0,0);
  set_status("Loading...")

  var args = "";
  if (kargs)
  {
    if (add) { kargs += "&" + add; }
    if (remove) { kargs = kargs.replace(remove,""); }
    args += "?" + kargs;
  }
  else
  {
    args += "?organism_id=" + myescape(organism) + "&bioverse_id=" + myescape(molecule);
  }

  molecule_details_pass1(organism, molecule, args);

  return false;
}


function molecule_details_pass1(organism, molecule, args)
{
  var molecule_url = "/api/web/molecule_view_init" + args;
  var molecule_accumulator = {}
  var molecule_destination = Tab.tabs["molview_" + organism + "_" + molecule].content.id;

  var molecule_urls = [];

  // Fetch the molecule skeleton container

  /*
  molecule_urls.push({ url:molecule_url,
                       type:"JSON",
                       callback: function(x){ molecule_accumulator.details = x; }
                     });
  */
  // above code can be replaced with:
  molecule_accumulator.details = {"organism_id":organism, "bioverse_id":molecule, "organism":organism_by_id(organism)}

  molecule_urls.push({ callback: function(x){ puttmpl(molecule_destination, "molecule_details", molecule_accumulator, 1);} });
  molecule_urls.push({ callback: function(x){ molecule_details_pass2(organism, molecule, args); } });

  // var tab_name = "molview_" + organism + "_" + molecule;
  // molecule_urls.push({ callback: function(){ alert("After pass1, (this.tabs[${tab_name}].content == el(${id})) is:".process({tab_name:tab_name, id:"tab_content_" + tab_name}) + (Tab.tabs[tab_name].content == el("tab_content_" + tab_name))); } });

  url_update(molecule_urls);
}

function molecule_details_pass2(organism, molecule, args)
{
  // var tab_name = "molview_" + organism + "_" + molecule;
  // alert("After pass2 begins, (this.tabs[${tab_name}].content == el(${id})) is:".process({tab_name:tab_name, id:"tab_content_" + tab_name}) + (Tab.tabs[tab_name].content == el("tab_content_" + tab_name)));

  // Fetch the sequence details
  var sequence_url = "/api/web/molecule_view_sequence" + args;
  var sequence_accumulator = {};
  var sequence_destination = "molecule_details_sequence_" + organism + "_" + molecule;
  var sequence_urls = [];

  sequence_urls.push({ url:sequence_url,
              type:"JSON",
              callback: function(x){ sequence_accumulator.details = x; }
            });
  sequence_urls.push({ callback: function(x){ puttmpl(sequence_destination, "molecule_details_sequence", sequence_accumulator, 0, 1);} });
  // sequence_urls.push({ callback: function(){ alert("After pass2.0, (this.tabs[${tab_name}].content == el(${id})) is:".process({tab_name:tab_name, id:"tab_content_" + tab_name}) + (Tab.tabs[tab_name].content == el("tab_content_" + tab_name))); } });
  url_update(sequence_urls);

  // Fetch the structure details
  var structure_url = "/api/web/molecule_view_structure" + args;
  var structure_accumulator = {};
  var structure_destination = "molecule_details_structure_" + organism + "_" + molecule;
  var structure_urls = [];
  structure_urls.push({ url:structure_url,
              type:"JSON",
              callback: function(x){ structure_accumulator.details = x; }
            });
  structure_urls.push({ callback: function(x){ puttmpl(structure_destination, "molecule_details_structure", structure_accumulator, 0, 1);} });
  // structure_urls.push({ callback: function(){ alert("After pass2.1, (this.tabs[${tab_name}].content == el(${id})) is:".process({tab_name:tab_name, id:"tab_content_" + tab_name}) + (Tab.tabs[tab_name].content == el("tab_content_" + tab_name))); } });
  url_update(structure_urls);

  // Fetch the function details
  var function_url = "/api/web/molecule_view_function" + args;
  var function_accumulator = {};
  var function_destination = "molecule_details_function_" + organism + "_" + molecule;
  var function_urls = [];
  function_urls.push({ url:function_url,
              type:"JSON",
              callback: function(x){ function_accumulator.details = x; }
            });
  function_urls.push({ callback: function(x){ puttmpl(function_destination, "molecule_details_function", function_accumulator, 0, 1);} });
  function_urls.push({ callback: unset_status });

  // function_urls.push({ callback: function(){ alert("After pass2.2, (this.tabs[${tab_name}].content == el(${id})) is:".process({tab_name:tab_name, id:"tab_content_" + tab_name}) + (Tab.tabs[tab_name].content == el("tab_content_" + tab_name))); } });

  url_update(function_urls)

}

function molecule_details_function(organism, molecule, kargs, add, remove)
{
  // window.scrollTo(0,0);
  set_status("Loading...")

  var args = "";
  if (kargs)
  {
    if (add) { kargs += "&" + add; }
    if (remove) { kargs = kargs.replace(remove,""); }
    args += "?" + kargs;
  }
  else
  {
    args += "?organism_id=" + myescape(organism) + "&bioverse_id=" + myescape(molecule);
  }

  var function_url = "/api/web/molecule_view_function" + args;
  var function_accumulator = {};

  var urls = [];
  urls.push({ url:function_url,
              type:"JSON",
              callback: function(x){ function_accumulator.details = x; }
            });
  urls.push({ callback: function(x){ puttmpl("molecule_details_function_"+organism+"_"+molecule, "molecule_details_function", function_accumulator, 0, 1);} });
  urls.push({ callback: unset_status });
  url_update(urls);
  return false;
}

function molecule_details_sequence(organism, molecule, kargs, add, remove)
{
  // window.scrollTo(0,0);
  set_status("Loading...")

  var args = "";
  if (kargs)
  {
    if (add) { kargs += "&" + add; }
    if (remove) { kargs = kargs.replace(remove,""); }
    args += "?" + kargs;
  }
  else
  {
    args += "?organism_id=" + myescape(organism) + "&bioverse_id=" + myescape(molecule);
  }

  var sequence_url = "/api/web/molecule_view_sequence" + args;
  var sequence_accumulator = {};

  var urls = [];
  urls.push({ url:sequence_url,
              type:"JSON",
              callback: function(x){ sequence_accumulator.details = x; }
            });
  urls.push({ callback: function(x){ puttmpl("molecule_details_sequence_"+organism+"_"+molecule, "molecule_details_sequence", sequence_accumulator, 0, 1);} });
  urls.push({ callback: unset_status });
  url_update(urls);
  return false;
}

function molecule_details_structure(organism, molecule, kargs, add, remove)
{
  // window.scrollTo(0,0);
  set_status("Loading...")

  var args = "";
  if (kargs)
  {
    args += "?" + kargs;
    if (add) { args += "&" + add; }
    if (remove) { args = args.replace(remove,""); }
  }
  else
  {
    args += "?organism_id=" + myescape(organism) + "&bioverse_id=" + myescape(molecule);
  }

  var structure_url = "/api/web/molecule_view_structure" + args;
  var structure_accumulator = {};

  var urls = [];
  urls.push({ url:structure_url,
              type:"JSON",
              callback: function(x){ structure_accumulator.details = x; }
            });
  urls.push({ callback: function(x){ puttmpl("molecule_details_structure_"+organism+"_"+molecule, "molecule_details_structure", structure_accumulator, 0, 1);} });
  urls.push({ callback: unset_status });
  url_update(urls);
  return false;
}

var __search2_status_timer = new Object();
function search2_status_init(search_id)
{
  __search2_status_timer[search_id] = setInterval("search2_status('" + search_id + "')",1000);
}

function search2_status_kill(search_id)
{
  clearInterval(__search2_status_timer[search_id]);
}

function search2_status(search_id)
{
  if (el("live_search_status"))
  {
    var urls = [];
    urls.push({ url:"/api/web/search2_status?search_id=" + search_id, type:"JSON", callback: function(x) { puttmpl("live_search_status", "live_search_status", {details:x}, 1);} });
    url_update(urls);
  }
}

function search2_retrieve(search_id)
{
  var this_accumulator = {};
  var urls = [];

  var url = "/api/web/search2_get_query?search_id=" + search_id;
  urls.push({ url:url, type:"JSON", callback:function(x){this_accumulator.details=x;} });
  urls.push({ callback: function(x){ 
                          var tabname = "search2_" + this_accumulator.details.search_id; 
                          Tab.append(tabname, "Search", "<img src='/s/images/spinner.gif' />");
                          puttmpl( Tab.tabs[tabname].content.id, "body_search2", this_accumulator, 1 );
                          puttmpl("search_terms_" + this_accumulator.details.search_id, "search2_terms", this_accumulator, 1);
                        } });
  urls.push({ callback: function(x){ puthtml("search_status", "");} });
  url_update(urls);
  return false;
}

function search2_new(query, organism_id)
{
  var this_accumulator = {};
  var urls = [];

  var url = "/api/web/search2_make_query?organism_id=" + organism_id + "&q=" + myescape(query);
  urls.push({ callback: function(x){ puthtml("search_status", "Processing " + query);} });
  urls.push({ url:url, type:"JSON", callback:function(x){this_accumulator.details=x;} });
  urls.push({ callback: function(x){ 
                          var tabname = "search2_" + this_accumulator.details.search_id; 
                          Tab.append(tabname, "Search", "<img src='/s/images/spinner.gif' />");
                          puttmpl( Tab.tabs[tabname].content.id, "body_search2", this_accumulator, 1 );
                          puttmpl("search_terms_" + this_accumulator.details.search_id, "search2_terms", this_accumulator, 1);
                        } });
  urls.push({ callback: function(x){ puthtml("search_status", "");} });
  url_update(urls);
  return false;
}

// retrieve search numbered search_id from the session
function search_retrieve(search_id)
{
  var s = 'search_' + search_id;
  var query          = global_state[s + '_query'];
  var organism_id    = global_state[s + '_organism_id'];
  var min_confidence = global_state[s + '_min_confidence'];
  var search_id      = global_state[s + '_search_id'];
  var noshow_cutoff  = global_state[s + '_noshow_cutoff'];
  var start          = global_state[s + '_start'];
  var end            = global_state[s + '_end'];

  molecule_search(query, organism_id, min_confidence, search_id, noshow_cutoff, start, end);
}

function molecule_search(query, organism_id, min_confidence, search_id, noshow_cutoff, start, end)
{
  var this_accumulator = {};
  this_accumulator.result = {};
  var urls = [];

  if (!min_confidence)
  {
     min_confidence = 0.3;
  }

  if (!search_id)
  {
    search_id=Math.floor(Math.random()*100000);
  }

  if (!noshow_cutoff)
  {
     noshow_cutoff = 500;
  }

  if (!start && !end)
  {
    start = 1;
    end = 20;
  }

  var url = "/api/web/molecule_search?";
  url += "organism_id=" + organism_id;
  url += "&min_confidence=" + min_confidence;
  url += "&noshow_cutoff=" + noshow_cutoff;
  if (start>0 && end>0 && start<end)
  {
    url += "&start="+start+"&end="+end; 
  }
  url += "&q=" + myescape(query);

  urls.push({ callback: function(x){ var tabname = "search_" + search_id; 
                                     Tab.append(tabname, "Search", "<img src='/s/images/spinner.gif' />");
                                     this_accumulator.result.search_id = search_id;
                                     this_accumulator.result.query = query;
                                     this_accumulator.result.organism_id = organism_id;
                                     this_accumulator.result.searching = 1;
                                     puttmpl( Tab.tabs[tabname].content.id, "body_search", this_accumulator, 1 );
                                   } });

  // describe the state of this search query
  urls.push({ url: session_setvar('search_' + search_id + '_query',          myescape(query)) })
  urls.push({ url: session_setvar('search_' + search_id + '_organism_id',    organism_id) })
  urls.push({ url: session_setvar('search_' + search_id + '_min_confidence', min_confidence) })
  urls.push({ url: session_setvar('search_' + search_id + '_search_id',      search_id) })
  urls.push({ url: session_setvar('search_' + search_id + '_noshow_cutoff',  noshow_cutoff) })
  urls.push({ url: session_setvar('search_' + search_id + '_start',          start) })
  urls.push({ url: session_setvar('search_' + search_id + '_end',            end) })

  urls.push({ url:url, type:"JSON", callback:function(x){this_accumulator.result=x;} });
  urls.push({ callback: function(x){ 
                          this_accumulator.result.noshow_cutoff = noshow_cutoff;
                          this_accumulator.result.search_id = search_id;
                          this_accumulator.result.query = query;
                          this_accumulator.result.organism_id = organism_id;
                          this_accumulator.result.searching = 0;
                          var tabname = "search_" + search_id; 
                          if (! Tab.exists(tabname) )
                          {
                            Tab.append(tabname, "<img valign='bottom' src='/s/images/page.gif' /> Search", "<img src='/s/images/spinner.gif' />");
                          }
                          puttmpl( Tab.tabs[tabname].content.id, "body_search", this_accumulator, 1 );

                          var template_file = "body_search_style_" + get_pref("preferences_search_result_style");

                          puttmpl( "search_results_" + search_id, template_file, this_accumulator, 1 );
                        } });
  url_update(urls);
  return false;
}

function search_frontpage(query, organism_id)
{
  var todo = [];
  todo.push({ callback: function(x){ puthtml("search_status", "Processing " + query); } });
  todo.push({ callback: function(x){ molecule_search(query, organism_id, get_pref("preferences_minimum_confidence")); } });
  todo.push({ callback: function(x){ puthtml("search_status", "");} });
  url_update(todo);
  return false;
}

function search_refine(search_id)
{
  var f=el('search_' + search_id).getElementsByTagName('*');
  var organism_id=f.namedItem('organism_id').value;
  var q=f.namedItem('q').value;
  var min_confidence=f.namedItem('min_confidence').value;
  molecule_search(q, organism_id, min_confidence, search_id);
  return false;
}

function search2_consider(query, organism_id, search_id)
{
  // hide the search results, if they exist
  if (el("search_results_" + search_id))
  {
    el("search_results_" + search_id).style.display = "none";
    el("search_results_" + search_id).innerHTML = "";
  }

  var this_accumulator = {};
  var urls = [];
  urls.push({ callback: function(x){ puthtml("search_terms_status_" + search_id, "Processing " + query + " <img src='/s/images/spinner.gif' />");} });
  var url = "/api/web/search2_make_query?organism_id=" + organism_id + "&q=" + myescape(query) + "&search_id=" + search_id;
  urls.push({ url:url, type:"JSON", callback:function(x){this_accumulator.details=x;} });
  urls.push({ callback: function(x){ puttmpl("search_terms_" + search_id, "search2_terms", this_accumulator, 1);} });

  urls.push({ callback: function(x){ puthtml("search_terms_status_" + search_id, "");} });
  url_update(urls);
  return false;
}

function search2_show_results(search_id, page)
{
  var url = "/api/web/search2_find_molecules?search_id=" + search_id + "&page=" + page;
  var this_accumulator = {};

  var urls = [];
  urls.push({ callback: function(x){ search2_status_init(search_id); } });
  urls.push({ callback: function(x){ puttmpl("search_results_" + search_id, "search2_results", {results:{done:0}}, 1);} });
  urls.push({ callback: function(x){ el("search_results_" + search_id).style.display="block";} });
  urls.push({ url:url, type:"JSON", callback:function(x){this_accumulator.results=x;} });
  urls.push({                       callback:function(x){this_accumulator.results.search_id=search_id;} });
  urls.push({ callback: function(x){ puttmpl("search_results_" + search_id, "search2_results", this_accumulator, 1);} });
  urls.push({ callback: function(x){ search2_status_kill(search_id); } });
  url_update(urls);
  return false;
}

function search_consider(query)
{
  set_status("Loading...")

  var this_accumulator1 = {};
  var this_accumulator2 = {};
  var urls = [];
  urls.push({ callback: function(x){ puthtml("search_status", "Processing " + query);} });
  urls.push({ url:"/api/web/search_consider?query=" + myescape(query)  });
  urls.push({ url:"/api/web/search_terms", type:"JSON", callback:function(x){this_accumulator1.details=x;} });
  urls.push({ callback: function(x){ puttmpl("search_terms", "search_terms", this_accumulator1, 1);} });
  urls.push({ url:"/api/web/search_results", type:"JSON", callback:function(x){this_accumulator2.results=x;} });
  urls.push({ callback: function(x){ puttmpl("search_results", "search_results", this_accumulator2, 1);} });
  urls.push({ callback: unset_status });
  urls.push({ callback: function(x){ puthtml("search_status", "");} });
  url_update(urls);
  return false;
}

function search2_clear()
{
  puthtml("search_terms","");
  puthtml("search_results","");
  return false;
}

function search_clear()
{
  set_status("Loading...")

  var this_accumulator1 = {};
  var this_accumulator2 = {};
  var urls = [];
  urls.push({ callback: function(x){ puthtml("search_status", "Clearing search session");} });
  urls.push({ url:"/api/web/search_clear" });
  urls.push({ url:"/api/web/search_terms", type:"JSON", callback:function(x){this_accumulator1.details=x;} });
  urls.push({ url:"/api/web/search_results", type:"JSON", callback:function(x){this_accumulator2.results=x;} });
  urls.push({ callback: function(x){ puttmpl("search_terms", "search_terms", this_accumulator1, 1);} });
  urls.push({ callback: function(x){ puttmpl("search_results", "search_results", this_accumulator2, 1);} });
  urls.push({ callback: unset_status });
  urls.push({ callback: function(x){ puthtml("search_status", "");} });
  url_update(urls);
  return false;
}

function search2_match_policy(search_id, organism_id, term, category, new_policy)
{
  var urls = [];
  urls.push({ url: "/api/web/search2_terms_match_policy?search_id="+myescape(search_id)+"&term=" + myescape(term) + "&context=" + myescape(category) + "&new_policy=" + myescape(new_policy) });
  urls.push({ callback: function() { search2_consider("", organism_id, search_id); } });
  url_update(urls);
  return false;
}

function search_match_policy(term, category, new_policy)
{
  /*
  if (new_policy=="")
  {
    alert("Deleting \nterm:"+term+"\nin:"+category);
  }
  else
  {
    alert("Changing policy of\nterm:"+term+"\nin:"+category+"\nto:"+new_policy);
  }
  */

  var urls = [];
  urls.push({ url: "/api/web/search_terms_match_policy?term=" + myescape(term) + "&context=" + myescape(category) + "&new_policy=" + myescape(new_policy) });
  urls.push({ callback: function() { search_consider(""); } });
  url_update(urls);
}

function search_term_info(term, category)
{
  el("search_term_info").style.display="block";
  el("search_term_info").innerHTML = "<span style='color: grey;'>Loading information about the term <b>${term}</b> in <b>${category}</b>...</span> ".process({term:term, category:category});
  var this_accumulator = {};
  var urls = [];
  urls.push({ url:"/api/web/function_related?function=%22" + term + "%22", type:"JSON", callback:function(x){this_accumulator.results=x;} });
  urls.push({ callback: function(x){ puttmpl("search_term_info", "search_term_info", this_accumulator, 1);} });
  url_update(urls);
  return false;
}

function search2_term_info(search_id, organism_id, term, category)
{
  el("search_term_info_" + search_id).style.display="block";
  el("search_term_info_" + search_id).innerHTML = "<span style='color: grey;'>Finding functions matching <b>${term}</b>...</span> ".process({term:term, category:category});
  var this_accumulator = {};
  var urls = [];
  //urls.push({ url:"/api/web/function_related?function=%22" + term + "%22", type:"JSON", callback:function(x){this_accumulator.results=x;} });
  urls.push({ url:"/api/web/function_search?q=" + term + "&organism_id=" + organism_id, type:"JSON", callback:function(x){this_accumulator.results=x;} });
  urls.push({                                                                                        callback:function(x){this_accumulator.results.input=term;} });
  urls.push({                                                                                        callback:function(x){this_accumulator.results.search_id=search_id;} });
  urls.push({                                                                                        callback:function(x){this_accumulator.results.organism_id=organism_id;} });
  urls.push({ callback: function(x){ puttmpl("search_term_info_" + search_id, "search2_term_info", this_accumulator, 1);} });
  url_update(urls);
  return false;
}

/* retrieve a url, and run a specified callback with the retrieved content
   as the single argument.

   url_updates is a list of objects. Each object should have several attributes:

     url
     callback
     tmpl
     id
     type

   These determine the url to fetch and the callback function to call with the contents
   retrieved. If type is "JSON", then the fetched content is assumed to be in JSON format. The
   callback function is not called. Instead, the retrieved data is fed into the javascript
   template engine, combined with the appropriate template, and the result is placed in the
   innerHTML of the page element with the specified id.

   Note: accessing attributes of objects and keys in associtive-lists in Javascript is identical.
   So, if "foo=new Object();" then "foo.bar = 5;" and "foo["bar"] = 5;" are identical. Further,
   "foo = {bar:5};" has the same end-result of making bar an attribute of foo (that is the
   {key:value} notation quotes the key automatically.

   Each object in the url_updates list describes one http call. These happen in the order given.

   Note: This will append "&" or "?" (as appropriate), followed by
   "nocache=##########", where # is a unique integer, to the url
   being retrieved to defeat any browser caching mechanisms.
*/
url_update = async_url_update;

function async_url_update(url_updates)
{

  // if there is nothing to do, then exit
  if (url_updates.length==0)
    return False;

  // pull out the first item in the list of urls, leaving
  // the list of urls shorter by one url.

  var thisurl_update = url_updates.shift();
  // if there are more urls to visit, then continue by recursing.

  var url      = thisurl_update.url      || ""; // The URL to use contents of
  var type     = thisurl_update.type     || ""; // The type of content ("JSON" or not)
  var callback = thisurl_update.callback || ""; // callback method to call

  // allow only a valid combination of these arguments
  // if (! (thisurl && (thisupdate || ((thistmpl && thisid) || thistype))) ) { alert("Invalid argument list to url_update()"); return; }

  // alert("this url is " + thisurl + " and callback function is " + thisupdate);

  // no url to process, but there is a callback...
  if (!url && callback)
  {
    callback();
    url_update(url_updates);
    return False;
  }

  // prevent chaching... of requests for things that arn't /s/....tmpl
  // or /s/....js files.
  if (!url.match(/s\/.+\.(tmpl|js)$/))
  {
    if (url.indexOf('?')>=0) { url += "&"; } else { url += "?"; }
    url += "nocache=" + (new Date()).getTime();
  }

  // if there is a url to process
  if (url)
  {
    var xmlhttp = get_xmlhttp()
  
    // prepare the connection in asynchronous mode
    xmlhttp.open("GET", url, true);
    // when the connection's over with, evaluate this function
    xmlhttp.onreadystatechange = function() {
      if (xmlhttp.readyState==4)
      {
        // if there is a callback function to deal with the content...
        if (callback)
        {
          // then capture the content
          var content = xmlhttp.responseText;

          // if the content ought to be interpreted as JSON...
          if (type == "JSON")
          {
            // and there is content to interpret...
            if (content.length>0)
            {
              //alert("about to evaluate content of length " + content.length + " in response to url " + url);
              saydebug("evaluating content: " + content);
              try
              {
                eval("var content = " + content);
              }
              catch(e)
              {
                log("Cannot eval() JSON output from " + url + " -- exception: " + e);
                warning("An error has occured. Please reload the application. <!-- Cannot eval() JSON output from <a href=\"" + url + "\">api server</a> -- exception: " + e  + " -->", 1);
                saydebug("Cannot eval() JSON output from " + url + " -- exception: " + e);
              }
            }
            else
            {
              content = {}
            }
          }

          // call the callback function with the content
          callback(content);
        }
        url_update(url_updates);
      }
    }
  
    // go
    xmlhttp.send(null)
  
    return false;
  }
}

function sync_url_update(url_updates)
{

  // if there is nothing to do, then exit
  if (url_updates.length==0)
    return False;

  // pull out the first item in the list of urls, leaving
  // the list of urls shorter by one url.

  var thisurl_update = url_updates.shift();
  // if there are more urls to visit, then continue by recursing.

  var url      = thisurl_update.url      || ""; // The URL to use contents of
  var type     = thisurl_update.type     || ""; // The type of content ("JSON" or not)
  var callback = thisurl_update.callback || ""; // callback method to call

  // allow only a valid combination of these arguments
  // if (! (thisurl && (thisupdate || ((thistmpl && thisid) || thistype))) ) { alert("Invalid argument list to url_update()"); return; }

  // alert("this url is " + thisurl + " and callback function is " + thisupdate);

  // prevent chaching... of requests for things that arn't /s/....tmpl
  // or /s/....js files.
  if (!url.match(/s\/.+\.(tmpl|js)$/))
  {
    if (url.indexOf('?')>=0) { url += "&"; } else { url += "?"; }
    url += "nocache=" + (new Date()).getTime() + "";
  }

  // no url to process, but there is a callback...
  if (!url && callback)
  {
    callback();
    url_update(url_updates);
  }
  // if there is a url to process
  if (url)
  {
    var xmlhttp = get_xmlhttp()
  
    // prepare the connection in synchronous mode
    xmlhttp.open("GET", url, false);

    // go
    xmlhttp.send(null)
  
    // when the connection's over with, evaluate this code
    // if there is a callback function to deal with the content...
    if (callback)
    {
      // then capture the content
      var content = xmlhttp.responseText;

      // if the content ought to be interpreted as JSON...
      if (type == "JSON")
      {
        // and there is content to interpret...
        if (content.length>0)
        {
          //alert("about to evaluate content of length " + content.length + " in response to url " + url);
          saydebug("evaluating content: " + content);
          try
          {
            eval("var content = " + content);
          }
          catch(e)
          {
            log("Cannot eval() JSON output from " + url + " -- exception: " + e);
            warning("An error has occured. Please reload the application. <!-- Cannot eval() JSON output from " + url + " -- exception: " + e  + " -->", 1);
            saydebug("Cannot eval() JSON output from " + url + " -- exception: " + e);
          }
        }
        else
        {
          content = {}
        }
      }

      // call the callback function with the content
      callback(content);
    }
    url_update(url_updates);
  
    return false;
  }
}

function get_xmlhttp()
{
  /* set up fancy xmlhttp stuff.
     note: for more information about using xmlhttp, see:
       http://jibbering.com/2002/4/httprequest.html
       http://webfx.eae.net/dhtml/xmlextras/xmlextras.html
  */
  var xmlhttp=false;
  /*@cc_on @*/
  /*@if (@_jscript_version >= 5)
  // JScript gives us Conditional compilation, we can cope with old IE versions.
  // and security blocked creation of the objects.
   try {
    xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
   } catch (e) {
    try {
     xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    } catch (E) {
     xmlhttp = false;
    }
   }
  @end @*/
  if (!xmlhttp && typeof XMLHttpRequest!='undefined') {
    xmlhttp = new XMLHttpRequest();
  }

  return xmlhttp;
}

// return element with given id
function el(id)
{
  if (document.getElementById)
  {
    return document.getElementById(id);
  }
  else
  {
    return null;
  }
}

// couple the template tmpl with data and stick it
// into an element
// this causes a bug: puttmpl("search_terms_" + this_accumulator.details.search_id, "search2_terms", this_accumulator, 1);
function puttmpl(id, tmpl, data, simple_replace, is_table)
{
  //alert("puttmpl to " + id + " from template " + tmpl);

  // var t = template[tmpl] || ("Template " + thistmpl + " not found");
  var t = template[tmpl];
  if (t == undefined)
  {
    var urls = [];
    urls.push( {url:get_root()+"templates/" + tmpl + ".tmpl", callback: function (data){ template[tmpl] = data; } } );
    urls.push( {                                              callback: function (){ puttmpl(id, tmpl, data, simple_replace, is_table); } } );
    url_update(urls);
  }
  else
  {
    if (data)
    {
      data._MODIFIERS = bioverse_modifiers;
    }
    var result = t.process(data);

    puthtml(id, result, simple_replace, is_table);
  }
}

/* populate the innerHTML attribute of an object with a
   given id
*/
var puthtml_tryagain = new Object;

function puthtml(id, data, simple_replace, is_table)
{
  // a unique ID of this set of arguments
  var key = id + "_" + hex_md5(new String(data)) + "_" + simple_replace + "_" + is_table;

  if (document.getElementById)
  {
    if (document.getElementById(id))
    {
      // if the request can finally be satisfied, then clear it from the queue.
      if (key in puthtml_tryagain)
      {
        delete(puthtml_tryagain[key]);
      }

      if (is_table)
      {
        // per Paul Chiu's script at http://ajaxian.com/archives/innerhtml-gotchas
        // handle inserting data into a <tbody> differently.
        var target = el(id);
        
        /* Remove all existing rows */
        while (target.rows.length > 0)
        {
          target.deleteRow(0);
        }
        
        /* Create temporary table */
        var temp_div = document.createElement("div");
        document.body.appendChild(temp_div);
        var temp_table_id = "temp_table_" + id;
        temp_div.innerHTML = '<table id="' + temp_table_id + '" style="display: none">' + data + '</table>';
        var tt = el(temp_table_id);
        
        /* Copy temporary table's rows to target */
        for (var i = 0; i<tt.rows.length; i++)
        {
          target.appendChild(tt.rows[i].cloneNode(true));
        }
        
        /* Remove temporary table */
        tt.parentNode.removeChild(tt);
      }
      else if (simple_replace)
      {
        //alert("puthtml - simple_replace is true");
        var old_element = el(id);
        try
        {
          old_element.innerHTML = data;
        }
        catch(ex)
        {
          alert("Malformed HTML?");
        }
      }
      else
      {
        var old_element = el(id);
        var parent = old_element.parentNode;
        var new_element = old_element.cloneNode(true);
        new_element.innerHTML = data;
        parent.replaceChild(new_element, old_element);
      }
    }
    else
    {
      // if the key is not in puthtml_tryagain, then add it
      if (!(key in puthtml_tryagain))
      {
        puthtml_tryagain[key] = {counter:5};
      }
      
      if (puthtml_tryagain[key].counter > 0)
      {
        puthtml_tryagain[key].counter-=1;
        //alert("puthtml_tryagain id=" + id + " key=" + key + ", " + puthtml_tryagain[key].counter + " times left");
        var call_this = function() { puthtml(id, data, simple_replace, is_table); }
        setTimeout(call_this, 1000);
      }
      else
      {
        //alert("puthtml failed for id=" + id + " after 5 tries");
        delete(puthtml_tryagain["key"]);
      }
    }
  }

  // return false so if called from an <a onClick=...>, 
  // browser will remain on the same page
  return false;
}

function set_search_tab_title(details)
{
  var query_digest = new Array();
  var things = ["name","sequence","func"];
  
  for (i=0; i<things.length; i++)
  {
    var c = things[i];
    var terms = details.query[c];
    if (keyvalue(terms).length>0)
    {
      var things2 = keyvalue(terms);
  
      for (j=0; j<things2.length; j++)
      {
        var t = things2[j];
        var term = t.key;
        var desc = t.value;
        query_digest.push(t.key);
      }
    }
  }
  
  if (query_digest.length==0)
  {
    query_digest = "Empty query";
  }
  else
  {
    query_digest = "Terms: " + query_digest.join(", ");
  }
  
  Tab.set_button("search2_"+details.search_id, "<span title='" + query_digest + "'><img valign='bottom' src='/s/images/page.gif' /> Search</span>");
}

// return the URL to a confidence bar
function confbar(c)
{
  if ((c>=0) && (c<10))
  {
    var display_c = c;
    if (display_c==0) { display_c = "0.0"; }
    if (display_c==1) { display_c = "1.0"; }
    display_c = String(display_c);
    display_c = display_c.substr(0,3);
    return "<img src=\"/s/images/confbar_" + String(Math.min(parseInt(10*c),10)) + ".gif\" title=\"" + display_c + " confidence\" />";
  }
  else
  {
    return "<img src=\"/s/images/confbar_0.gif\" title=\"" + c + " confidence\" />";
  }
}

// return HTML for making a button in the molecule view
// type is one of "evidence", "evolutionary", "contextual", "info"
// state is one of "open", "closed", "inactive"
function mkbutton(type, state, onclick)
{
  var filename = "";
  var title = "";
  var onclick = onclick;

       if (type=="context"    && state=="open"  )   { filename = "context_opened.gif";      title = "Hide contextual relationships"; }
  else if (type=="context"    && state=="closed")   { filename = "context_closed.gif";      title = "Show contextual relationships"; }
  else if (type=="context"    && state=="inactive") { filename = "context_inactive.gif";    title = "Contextual relationships unavailable"; }

  else if (type=="similarity" && state=="open"  )   { filename = "similarity_opened.gif";   title = "Hide evolutionary relationships"; }
  else if (type=="similarity" && state=="closed")   { filename = "similarity_closed.gif";   title = "Show evolutionary relationships"; }
  else if (type=="similarity" && state=="inactive") { filename = "similarity_inactive.gif"; title = "Evolutionary relationships unavailable"; }

  else if (type=="evidence"   && state=="open"  )   { filename = "evidence_opened.gif";     title = "Hide evidence"; }
  else if (type=="evidence"   && state=="closed")   { filename = "evidence_closed.gif";     title = "Show evidence"; }
  else if (type=="evidence"   && state=="inactive") { filename = "evidence_inactive.gif";   title = "Evidence unavailable"; }

  else if (type=="info"       && state=="open"  )   { filename = "info_opened.gif";         title = "Hide information"; }
  else if (type=="info"       && state=="closed")   { filename = "info_closed.gif";         title = "Show information"; }
  else if (type=="info"       && state=="inactive") { filename = "info_inactive.gif";       title = "Information unavailable"; }

  else { filename = "unknown.gif"; title = "unknown: " + type + "," + state; }

  if (!onclick) { onclick = ""; }

  var result = '<img ';
  if (onclick) { result += 'class="fakelink" '; }
  result += 'src="/s/images/${filename}" title="${title}" onclick="${onclick}" />'.process({filename:filename, title:title, onclick:onclick});
  return result;
}

// turn the referenced image into a spinner animation
function mkspinner(img)
{
  img.src = "/s/images/spinner.gif";
}

function update(stuff)
{
  try { eval(stuff); }
  catch (e) { }
}

// Given a floating point number 
// return a x.xx representation of that number.
function format_confidence(f)
{
  var f = String( ("000" + parseInt(f * 100)).match("...$") );
  return f[0] + "." + f[1] + f[2];
}

var typeahead_on = false;
var typeahead_haschanged = false;
var typeahead_q = "unlikely to be typed for the first time";

function setup_typeahead()
{
  saydebug("setting up typeahead")
  setInterval(typeahead_pickup, (500));
  saydebug("done setting up typeahead")
}
   
function typeahead(text)
{
  if (typeahead_on)
  {
    /*if (text=="")
    {
      typeahead_q = myescape(text);
      el("explore_vocab").innerHTML = "";
    }
    else*/ if (text != typeahead_q)
    {
      typeahead_haschanged = true;
      typeahead_q = text;
      typeahead_escaped_q = myescape(text);
    }
  }
}

function typeahead_pickup()
{
  if (typeahead_haschanged)
  {
    el('typeahead_status').innerHTML += "!";
    var q = typeahead_escaped_q;
    var urls = [];
    urls.push( {url:get_root() + "explore/?what=vocab;input=" + q, callback: update, type: "JSON", tmpl:"explore_vocab", id:"explore_vocab"} );
    url_update(urls);
    if (q==typeahead_escaped_q)
    {
      typeahead_haschanged = false;
    }
  }
  else
  {
    el('typeahead_status').innerHTML += ".";
  }
  var x = el('typeahead_status').innerHTML;
  if (x.length >= 10)
  {
    x = x.substr(Math.max(1, x.length-9), x.length);
  }
  el('typeahead_status').innerHTML = x;
}

function typeahead_open()
{
  el("explore_vocab_link").style.display = "none";
  typeahead_on = true;
  el("explore_vocab").style.display = "inline";
  typeahead(el('search_simple_query').value);
}

function typeahead_close()
{
  typeahead_on = false;
  typeahead_haschanged = false;
  typeahead_q = "!@#$!@#$"
  el("explore_vocab").style.display = "none";
  el("explore_vocab").innerHTML = "Searching...";
  el("explore_vocab_link").style.display = "inline";
}

function highlight_many_match(tokens, text)
{
  var newtext = text;
  //saydebug("highlight_many_match tokens is a " + typeof(tokens));
  for (var i=0; i<tokens.length; i++)
  {
    var t = tokens[i];
    //saydebug("highlight_many_match has t, text = " + String(t) + "," + text);
    newtext = newtext.replace(new RegExp('([^\<]' + t + '[^\>])', 'gi'), "<b>$1</b>" )
  }
  return newtext; 
}

function highlight_odd(chunks, start_text, stop_text)
{
  var final_text = new Array();
  for (var i=0; i<chunks.length; i++)
  {
    var text = chunks[i];
    if ((i>0) && (i%2==1))
    {
      text = start_text + text + stop_text;
    }
    /*
    else
    {
      var maxlen = 20;
      if (text.length > maxlen)
      {
        text = text.substr(0,maxlen/2) + "..." + text.substr(text.length-maxlen/2)
      }
    }
    */
    final_text.push(text);
  }
  return final_text.join("");
}


function annotate_clickable()
{
  var e = document.getElementsByTagName("*");

  saydebug("annotate_clickable: analyzing " + e.length + " elements");

  for (i=0; i<e.length; i++)
  {
    var o = e[i].getAttribute("onclick");
    if (o)
    {
      saydebug("found element " + i + " to annotate");
      e[i].setAttribute("onmouseover", "javascript: window.status='onclick: " + myescape2(o) + "'; return true;");
      e[i].style.setProperty("border", "1px solid red", null);
    }
  }
  
  return true;
}

