// requestUpdate
// makeUrl               Create a URL to the current page.
// makeParams            Create the parameters for the URL.
// getAnnotationType
// 
// setStatusBar
// 
// onGeneChange          Should call when gene information changes.
// refreshGenes
// loadGeneTable
// 
// onAnnotationChange    Should call when annotation type changes.
// refreshAnnotations
// loadAnnotationTable
// clearAnnotationTable
// downloadAnnotations
// 
// PageNavigator
// 
// makeXMLHttpRequest    Create a new XMLHttpRequest object.
// updateTimeout         Set a timeout, clearing old one if exists.
// getValueById          Get the value of a control.
// setValueById          Set the value of a control.
// getCheckedById
// setInnerHTML          Set the HTML of an element.
// getInnerHTML
// onUnload              Unused
// onFocusGeneInput
// onBlurGeneInput
// 
// analyzeRbE2F          Do the Rb/E2F demo.
// 
// prettyInt
// prettyFloat
// prettyScientific
// prettySigbar
// 
// makeLocuslinkIdURL
// makeLocuslinkSearchURL
// unhashNetTraffic

ANNOT_GO = "gene_ontology"
ANNOT_GO_HQ = "gene_ontology_hq"
ANNOT_GOA_R05_HQ = "goa_r05_hq"
ANNOT_WORD = "words_only"
ANNOT_BIGRAM = "bigrams_only"
ANNOT_WORD_AND_BIGRAM = "words"
ANNOT_MESH = "mesh"
ANNOT_KEGG = "kegg"
ANNOT_PROTEIN = "proteins"
ANNOT_LITNET = "literature"
ANNOT_MIRNA = "mirna"
ANNOT_TRANSFAC = "transfac"
ANNOT_MAPLOC = "maploc"


CMD_GENES = "genes"
CMD_ANNOTATIONS = "annotations"
CMD_REPORT = "report"


GENE_INPUT_ID = "gene_box"
GENE_INPUT_DELAY = 400

GENE_TABLE_ID = "gene_table"
GENE_COUNT_ID = "num_genes"
GENE_PAGE_ID = "gene_page"
GENE_TOTAL_PAGES_ID = "gene_total_pages"
GENE_PREVLINK_ID = "gene_prev"
GENE_NEXTLINK_ID = "gene_next"
GENE_PAGE_LENGTH = 10

ANNOTATION_TABLE_ID = "annotation_table"
ANNOTATION_TYPE1_ID = "annotation_type1"
ANNOTATION_TYPE2_ID = "annotation_type2"
ANNOTATION_PAGE_ID = "annotation_page"
ANNOTATION_TOTAL_PAGES_ID = "annotation_total_pages"
ANNOTATION_PREVLINK_ID = "annotation_prev"
ANNOTATION_NEXTLINK_ID = "annotation_next"
ANNOTATION_PAGE_LENGTH = 10
SHOWN_ANNOTATIONS_ID = "shown_annotations"

DOWNLOAD_FORM_ID = "download_form"
DOWNLOAD_BUTTON_ID = "download_button"

ORGANISM_ID = "tax_id"
DEFAULT_ORGANISM_ID = "9606"

ANNOT_TYPE_ID = "annot_type"
DEFAULT_ANNOT_TYPE = ANNOT_GO

NETWORK_ID = "network"
HOMOLOG_ID = "homologs"

STATUSBAR_ID = "statusbar"
STATUSBAR_DELAY = 2500


// cmd       Name of command to send to server.
// funcname  Name of callback function to receive query results.
function requestUpdate(cmd, request_name, callback_fn) {
  req = eval(request_name)
  if(req) { req.abort(); req = false; }
  url = makeUrl(); params = makeParams(cmd)
  req = makeXMLHttpRequest(url, params, 
    function(text) { callback_fn(text); eval(request_name+"=false") })
  eval(request_name + " = req")
}

function makeUrl() {
  url = document.URL
  i = url.indexOf("?")
  if(i >= 0)
    url = url.substring(0, i)
  return url
}

function makeParams(cmd) {
  // Stupid IE 6.0.  It doesn't like the variable names "genes" or
  // "organism".  If I don't declare them here, the script will stop
  // running at this point.  Changing the variable names will also
  // fix this problem.  But why should I do that?
  param_names = Array(
    "ORGANISM_ID", 
    "GENE_PAGE_ID", 
    "ANNOTATION_PAGE_ID",
    "SHOWN_ANNOTATIONS_ID"
    )

  // Be sure to encode user input.  Otherwise, semicolons not
  // recognized.
  params = GENE_INPUT_ID+"="+encodeURIComponent(getValueById(GENE_INPUT_ID))
  params += "&" + ANNOT_TYPE_ID+"="+getAnnotationType()
  params += "&" + NETWORK_ID+"="+getCheckedById(NETWORK_ID)
  params += "&" + HOMOLOG_ID+"="+getCheckedById(HOMOLOG_ID)
  for(i=0; i<param_names.length; i++) {
    n = eval(param_names[i])
    params += "&" + n+"="+getValueById(n)
  }

  if(cmd)
    params += "&" + "cmd="+cmd
  return params
}

function getAnnotationType() {
  annot_type = DEFAULT_ANNOT_TYPE
  for(i=0; i<annot_form.annot_type.length; i++) {
    if(annot_form.annot_type[i].checked)
      annot_type = annot_form.annot_type[i].value
  }
  return annot_type
}

var _setStatusBarTimeout = false
function setStatusBar(value) {
  // Set the statusbar message, and clear it after 2 seconds.
  updateTimeout('setInnerHtml(STATUSBAR_ID, "")', STATUSBAR_DELAY, 
    "_setStatusBarTimeout")
  setInnerHTML(STATUSBAR_ID, value)
}

function _hashGeneInfo() {
  hash = getValueById(ORGANISM_ID) + " " + getValueById(GENE_INPUT_ID)
  hash = hash.replace(/^\s+/, "")
  hash = hash.replace(/\s+$/, "")
  hash = hash.replace(/[,;\s]/g, " ")
  return hash
}

var _onGeneChange_timeout = false
var _onGeneChange_prevhash = ""
function onGeneChange(delay) {
  // Clear annotations immediately, so they are not out of sync.
  gene_hash = _hashGeneInfo()
  if(gene_hash == _onGeneChange_prevhash)
    return
  updateTimeout('refreshGenes()', delay, "_onGeneChange_timeout")
  onAnnotationChange(delay)
}

var _refreshGenesReq = false
function refreshGenes() {
  requestUpdate(CMD_GENES, "_refreshGenesReq",
    function(text) { loadGeneTable(text) })
  _onGeneChange_prevhash = _hashGeneInfo()
}

function loadGeneTable(text) {
  MAX_GENE_NAME_LENGTH = 35
  function cell_id(row, col) {
    return GENE_TABLE_ID + "_" + row + "_" + col
  }
  text = unhashNetTraffic(text)

  var lines = text.split("\n")

  // The first line of the output should be:
  // <num genes> <pagenum> <total_pages>
  x = lines[0].split("\t")
  // IE will break if the variable is named num_genes.  Sheesh.
  my_num_genes = parseInt(x[0])
  page_num = parseInt(x[1])
  total_pages = parseInt(x[2])
  lines.splice(0, 1)

  text = ""
  if(my_num_genes == 1)
    text = "(Only 1 Gene)"
  else if(my_num_genes > 1)
    text = "("+my_num_genes+"&nbsp;Genes&nbsp;Total)"
  setInnerHTML(GENE_COUNT_ID, text)

  // Delete header
  lines.splice(0, 1)

  row = 1
  for(var i=0; i<lines.length; i++) {
    line = lines[i]
    if(!line.length) continue
    columns = line.split("\t")
    if(columns.length != 7) { 
      break 
    }

    // index
    index = parseInt(columns[0])
    setInnerHTML(cell_id(row, 1), index+".")

    user_name = columns[1]
    canonical_llid = columns[2]
    name = columns[4]
    if(canonical_llid) {
      ll_url = makeLocuslinkIdURL(canonical_llid)
      symbol_col = '<B><A HREF="'+ll_url+'">'+user_name+'</A></B>'
      name_col = name
      if(name_col.length > (MAX_GENE_NAME_LENGTH-3))
        // XXX should strip punctuation and space
        name_col = name_col.substr(0, MAX_GENE_NAME_LENGTH-3) + "..."
    } else {
      ll_url = makeLocuslinkSearchURL(user_name)
      symbol_col = '<B><A HREF="'+ll_url+'">'+user_name+'</A></B>'
      name_col = '<FONT COLOR=RED>UNKNOWN GENE</FONT>'
    }
    setInnerHTML(cell_id(row, 2), symbol_col)
    setInnerHTML(cell_id(row, 3), name_col)

    row += 1
  }

  // Clear the rest of the table.
  while(1) {
    if(document.getElementById(cell_id(row, 1)) == null) break
    for(col=1; col<=3; col++)
      setInnerHTML(cell_id(row, col), "&nbsp;")
    row += 1
  }

  // Update the page bar navigator.
  genePageNavigator.updatePageNumbers(page_num, total_pages)
}

var _onAnnotationChangeTimeout = false
function onAnnotationChange(delay) {
  x = 'setValueById("'+ANNOTATION_PAGE_ID+'", 1);' + 
    'setValueById("'+SHOWN_ANNOTATIONS_ID+'","");' + 
    'refreshAnnotations(1);'
  updateTimeout(x, delay, "_onAnnotationChangeTimeout")
}

var _refreshAnnotationsReq = false
function refreshAnnotations(clear_table) {
  if(clear_table) {
    // Clear the table and reset the header.
    // If there's no genes, then not calculating anything.
    g = getValueById(GENE_INPUT_ID)
    g = g.replace(/\s+/g, "")
    if(g) text = "<FONT COLOR=RED>Calculating...</FONT>"
    else text = ""
    clearAnnotationTable(text)
  }
  requestUpdate(CMD_ANNOTATIONS, "_refreshAnnotationsReq",
    function(text) { loadAnnotationTable(text) } )
}

function loadAnnotationTable(text) {
  function cell_id(row, col) {
    return ANNOTATION_TABLE_ID + "_" + row + "_" + col
  }
  text = unhashNetTraffic(text)

  var lines = text.split("\n")

  // The first line of the output should be:
  // <annotation type> <pagenum> <total_pages> <shown annot indexes>
  x = lines[0].split("\t")
  annotation_type = x[0]
  page_num = parseInt(x[1])
  total_pages = parseInt(x[2])
  if(x[3]) shown_annot_indexes = (x[3]).split(" ")
  else shown_annot_indexes = new Array()
  lines.splice(0, 1)
  setInnerHTML(ANNOTATION_TYPE1_ID, annotation_type)
  setInnerHTML(ANNOTATION_TYPE2_ID, annotation_type)
  for(i=0; i<shown_annot_indexes.length; i++)
    shown_annot_indexes[i] = parseInt(shown_annot_indexes[i])
  // Delete header
  lines.splice(0, 1)

  row = 1
  for(var i=0; i<lines.length; i++) {
    line = lines[i]
    if(!line.length) continue
    columns = line.split("\t")
    if(columns.length != 6) { 
      alert("unknown annotation format: " + lines.join("\n"))
      break 
    }

    // index
    index = parseInt(columns[0])
    setInnerHTML(cell_id(row, 1), index+".")

    // Name of annotation.
    setInnerHTML(cell_id(row, 2), columns[1])

    // Number of genes annotated.
    setInnerHTML(cell_id(row, 3), columns[2])

    // Show/Hide genes.
    show_genes = null
    for(j=0; j<shown_annot_indexes.length; j++)
      if(shown_annot_indexes[j] == index) { show_genes=j; break }
    ids = shown_annot_indexes.slice(0)
    if(show_genes != null) {
      text = "hide"  // If they're shown, then let the user hide them.
      ids.splice(show_genes, 1)
    } else {
      text = "show"
      ids.push(index)
    }
    ids_str = ids.join(" ")
    url = "javascript:setValueById('"+SHOWN_ANNOTATIONS_ID+"','"+ids_str+"')"
    url = url + ";refreshAnnotations()"
    x = '<FONT SIZE=-1>[<A HREF="'+url+'">'+text+'</A>]</FONT>'
    setInnerHTML(cell_id(row, 4), x)

    // If genes are present, then show them.
    id = ANNOTATION_TABLE_ID + "_" + row + "_genes"
    if(show_genes != null && columns[5]) {
      setInnerHTML(id, columns[5])
    } else {
      setInnerHTML(id, "")
    }

    // Significance bar.
    nln_p_value = parseFloat(columns[4])
    num_bars = Math.ceil(nln_p_value)
    sigbar = prettySigbar(num_bars, 10)
    setInnerHTML(cell_id(row, 5), sigbar)

    // p_value
    p_value = Math.exp(-nln_p_value)
    p_value = prettyScientific(p_value, 1)
    setInnerHTML(cell_id(row, 6), p_value)

    // bayes_factor
    ln_bayes_factor = parseFloat(columns[3])
    ln_bayes_factor = prettyFloat(ln_bayes_factor, 0)
    setInnerHTML(cell_id(row, 7), ln_bayes_factor)
    row += 1
  }

  annotationPageNavigator.updatePageNumbers(page_num, total_pages)

  if(lines.length >= 1) {
    elem = document.getElementById(DOWNLOAD_BUTTON_ID)
    elem.disabled = false
  }
}

function clearAnnotationTable(header) {
  function cell_id(row, col) {
    return ANNOTATION_TABLE_ID + "_" + row + "_" + col
  }
  if(header)
    setInnerHTML(ANNOTATION_TYPE1_ID, header)
  row = 1
  while(1) {
    if(document.getElementById(cell_id(row, 1)) == null) break
    for(col=1; col<=7; col++)
      setInnerHTML(cell_id(row, col), "&nbsp;")
    id = ANNOTATION_TABLE_ID + "_" + row + "_genes"
    setInnerHTML(id, "")
    row += 1
  }
  annotationPageNavigator.updatePageNumbers(0, 0)
  elem = document.getElementById(DOWNLOAD_BUTTON_ID)
  elem.disabled = true
}

function downloadAnnotations() {
  form = document.getElementById(DOWNLOAD_FORM_ID)
  // First, get rid of all the hidden objects.
  var i = 0
  while(i < form.elements.length) {
    elem = form.elements[i]
    if(elem.type.toLowerCase() == "hidden") form.removeChild(elem)
    else i += 1
  }

  // Then, add the new values of the form.
  data = new Array(
    "cmd", CMD_REPORT,
    GENE_INPUT_ID, getValueById(GENE_INPUT_ID),
    ORGANISM_ID, getValueById(ORGANISM_ID),
    ANNOT_TYPE_ID, getAnnotationType(),
    NETWORK_ID, getCheckedById(NETWORK_ID),
    HOMOLOG_ID, getCheckedById(HOMOLOG_ID)
  )
  for(i=0; i<data.length; i+=2) {
    elem = document.createElement("INPUT")
    elem.type = "HIDDEN"
    elem.name = data[i]
    elem.value = data[i+1]
    form.appendChild(elem)
  }
}

function PageNavigator(myname, refresh_code, current_page_id, total_pages_id,
  next_link_id, prev_link_id) {
  // XXX this.myname is a hack.  Try to get rid of this!
  this.myname = myname
  this.refresh_code = refresh_code
  this.current_page_id = current_page_id
  this.total_pages_id = total_pages_id
  this.next_link_id = next_link_id
  this.prev_link_id = prev_link_id
  
  this.setNextLink = function(status) {
    elem = document.getElementById(this.next_link_id)
    if(status) {
      elem.className = ""
      elem.href = "javascript:" + this.myname+".nextPage()"
    } else {
      elem.className = "inactive"
      elem.href = "javascript:return false"
    }
  }
  this.setPrevLink = function(status) {
    elem = document.getElementById(this.prev_link_id)
    if(status) {
      elem.className = ""
      elem.href = "javascript:" + this.myname+".prevPage()"
    } else {
      elem.className = "inactive"
      elem.href = "javascript:return false"
    }
  }

  this.nextPage = function() {
    page_num = getValueById(this.current_page_id)
    page_num = parseInt(page_num)
    total_pages = getInnerHTML(this.total_pages_id)
    if(page_num >= total_pages) return
    this.setNextLink(false)
    setValueById(this.current_page_id, page_num+1)
    eval(this.refresh_code)
  }

  this.prevPage = function() {
    page_num = getValueById(this.current_page_id)
    page_num = parseInt(page_num)
    if(page_num <= 1) return
    this.setPrevLink(false)
    setValueById(this.current_page_id, page_num-1)
    eval(this.refresh_code)
  }

  this.updatePageNumbers = function(current_page, total_pages) {
    setValueById(this.current_page_id, current_page)
    setInnerHTML(this.total_pages_id, total_pages)
    this.setPrevLink(current_page > 1)
    this.setNextLink(current_page < total_pages)
  }
}

var genePageNavigator = new PageNavigator(
  "genePageNavigator",
  "refreshGenes()", GENE_PAGE_ID, GENE_TOTAL_PAGES_ID,
  GENE_NEXTLINK_ID, GENE_PREVLINK_ID)

var annotationPageNavigator = new PageNavigator(
  "annotationPageNavigator",
  "refreshAnnotations(0)", ANNOTATION_PAGE_ID, ANNOTATION_TOTAL_PAGES_ID,
  ANNOTATION_NEXTLINK_ID, ANNOTATION_PREVLINK_ID)

// url       URL of CGI script.
// params    <name>=<value>&<name>=<value>.
// funcname  Name of callback function, should take results of request.
function makeXMLHttpRequest(url, params, callback_fn, method) {
    if(method == null) method = "POST"
    var req = false
    // IE 7.0 has an XMLHttpRequest object, but it appears to be broken.
    // Thus, check for ActiveXObject first to make sure XMLHttpRequest
    // doesn't get evaluated by IE 7.
    if(window.ActiveXObject) {    // IE
        try { req = new ActiveXObject("Mxsml2.XMLHTTP"); }
        catch(e) { try { req = new ActiveXObject("Microsoft.XMLHTTP") }
            catch(e) { req = false } }
    }
    else if(window.XMLHttpRequest) {        // Mozilla/Safari
        try { req = new XMLHttpRequest() }
        catch(e) { req = false }
        if(req) { req.overrideMimeType('text/xml') }
    }
    if(!req) {
        alert("I could not make a request.\n")
        return
    }

    // Firefox requires "open" before "setRequestHeader".
    req.open(method, url, true)
    req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
    req.setRequestHeader("Connection", "close")  // Fix Firefox error.
    req.onreadystatechange = function() {
        if(req.readyState != 4) return
        if(req.status == 200)
            callback_fn(req.responseText)
        else if(req.status == 0)
            ;
        else
            alert("There was a problem retrieving XML data:" + req.status + " " + req.statusText)
        }
    req.send(params)
    return req
}

// Need to operate on timeout directly (rather than just returning
// it), or function won't be threadsafe.
function updateTimeout(code, delay, timeout_name) {
    if(!delay || !timeout_name) {
        eval(code)
        return
    }
    timeout = eval(timeout_name)
    if(timeout) {
        eval(timeout_name + " = false")
        clearTimeout(timeout)
    }
    x = timeout_name + "=false; " + code
    eval(timeout_name + " = setTimeout(x, delay)")
}

function getValueById(id) {
    elem = document.getElementById(id)
    if(elem == null) { alert("getValueById: I could not find: " + id); return }
    return elem.value
}

function setValueById(id, value) {
    elem = document.getElementById(id)
    if(elem == null) { alert("setValueById: I could not find: " + id); return }
    elem.value = value
}

function getCheckedById(id) {
    elem = document.getElementById(id)
    if(elem == null) { alert("getValueById: I could not find: " + id); return }
    if(elem.checked) 
        return "on"
    return ""
}

function setInnerHTML(id, value) {
    elem = document.getElementById(id)
    if(elem == null) { alert("setInnerHTML: I could not find: " + id); return }
    elem.innerHTML = value
}

function getInnerHTML(id) {
  elem = document.getElementById(id)
  if(elem == null) { alert("setInnerHTML: I could not find: " + id); return }
  return elem.innerHTML
}

function onUnload() {
}

// onGeneChange must be refreshed faster than GENE_INPUT_DELAY.
// Otherwise, this will detect changes faster than the gene list is
// refreshed, make new requests, which delays the update.
var _GeneInputInterval = false
function onFocusGeneInput() {
  if(_GeneInputInterval)
    return
  x = "onGeneChange" + "(" + GENE_INPUT_DELAY + ")"
  _GeneInputInterval = setInterval(x, GENE_INPUT_DELAY*2)
}

function onBlurGeneInput() {
  if(!_GeneInputInterval)
    return
  clearInterval(_GeneInputInterval)
  _GeneInputInterval = false
}

function analyzeRbE2F() {
  url = makeUrl() + "rbe2f_pathway.txt"
  makeXMLHttpRequest(url, "", function(text) { 
      setValueById(GENE_INPUT_ID, text)
      setValueById(ORGANISM_ID, "9606")
      refreshGenes(); refreshAnnotations(1)
    }, "GET")
}

function prettyInt(num) {
  function reverse(s) {
    s = s.split("")
    s.reverse()
    return s.join("")
  }
  snum = reverse(String(num))
  with_comma = new Array()
  for(var i=0; i<snum.length; i += 3)
    with_comma.push(snum.substr(i, 3))
  return reverse(with_comma.join(","))
}

function prettyFloat(num, prec) {
  if(prec == null) prec = 0
  if(num >= 1E99)
    return "&infin;"
  m = Math.pow(10, prec)
  num = Math.round(num*m)/m
  exp = Math.floor(num)
  mant = num-exp
  exp = prettyInt(exp)
  if(prec <= 0)
    return exp
  mant = String(floor(mant*m))
  if(mant.length < prec) {
    for(i=0; i<prec-mant.length; i++)
      mant = "0".concat(mant)
  }
  return exp + "." + mant
}

function prettyScientific(num, prec) {
  if(prec == null) prec = 2
  if(num < 0.0001)
    return "&lt;&nbsp;0.0001"
  if(num < 1) {
    m = Math.pow(10, Math.floor(-Math.log(num)/Math.log(10))+prec)
    num = Math.round(num*m)/m
    return String(num)
  }
  m = Math.pow(10, prec)
  if(num < m)
    return String(Math.round(num))

  exp = Math.floor(Math.log(num)/Math.log(10))
  num = num / Math.pow(10, exp)
  m = Math.pow(10, prec)
  num = Math.round(num*m)/m
  s = "<NOBR>" + num+"x10" + "<SUP>" + exp + "</SUP>" + "</NOBR>"
  return s
}

function prettySigbar(num_bars, max_bars) {
  num_bars = Math.max(Math.min(num_bars, max_bars), 0)

  color = Math.round(255 * num_bars/max_bars)
  s = '<SPAN STYLE="background-color:rgb(0, '+color+', 0)">'
  for(i=0; i<num_bars; i++)
    s = s + "&nbsp;"
  s += '</SPAN>'
  return s
}


function makeLocuslinkIdURL(llid) {
  return "http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?db=gene&cmd=Retrieve&dopt=Graphics&list_uids=" + llid
}

function makeLocuslinkSearchURL(term) {
  //db=104
  //V=0
  //btnG=Search
  return "http://www.ncbi.nih.gov/entrez/query.fcgi?db=gene&cmd=Search&term=" + term
}

function unhashNetTraffic(s) {
  s = s.replace(/!DRJ:GO!/g, "http://www.godatabase.org/cgi-bin/amigo/go.cgi?view=details&query=")
  s = s.replace(/!DRJ:ME!/g, "http://www.nlm.nih.gov/cgi/mesh/2005/MB_cgi?field=uid&exact=Find Exact Term&term=")
  s = s.replace(/!DRJ:KE!/g, "http://www.genome.ad.jp/dbget-bin/www_bget?")
  s = s.replace(/!DRJ:LL!/g, "http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?db=gene&cmd=Retrieve&dopt=Graphics&list_uids=")
  s = s.replace(/!DRJ:MI!/g, "http://microrna.sanger.ac.uk/cgi-bin/sequences/mirna_entry.pl?acc=")
  s = s.replace(/!DRJ:TF!/g, "http://www.gene-regulation.com/cgi-bin/pub/databases/transfac/getTF.cgi?AC=")
  return s
}
