// $Id: misc.js,v 1.6 2006/11/03 07:24:19 mikeg Exp $


/*
 Returns true if the object is an array, else false
*/
function isArray(a) {
    return isObject(a) && a.constructor == Array;
}
function isObject(a) {
    return (a && typeof a == 'object') || isFunction(a);
}
function isFunction(a) {
    return typeof a == 'function';
}

/* get the innerhtml attribute of an object with a
   given id
*/
function gethtml(id)
{
  if (document.getElementById)
  {
    return document.getElementById(id).innerHTML;
  } 
  // return false so if called from an <a onClick=...>, 
  // browser will remain on the same page
  return false
}

/*
 Log a message to the debug buffer in the document
*/
function saydebug(stuff)
{
  return;
  stuff = stuff.replace(new RegExp('<', 'gi'), "&lt;")
  stuff = stuff.replace(new RegExp('>', 'gi'), "&gt;")
  if (document.getElementById)
  {
    html = document.getElementById("debug").innerHTML
    var h = ("0" + new String(new Date().getHours())).substr(-2);
    var m = ("0" + new String(new Date().getMinutes())).substr(-2);
    var s = ("0" + new String(new Date().getSeconds())).substr(-2);
    html += "<p><span style='background-color: lightgreen'>" + h + ":" + m + ":" + s + "</span> <code>" + stuff + "</code></p>"
    puthtml("debug", html)
  }
  else
  {
    alert("debug: " + stuff)
  }
}

// return the number x rounded to y decimal places
function round(x,y)
{
  return Math.round(x*Math.pow(10,y))/Math.pow(10,y);
}

function log(text)
{
  var urls = [];
  urls.push({ url:"/log?text=" + myescape(text), type:"none" });
  url_update(urls);
  return false;
}

// return list of integers in range 0 .. x exclusive
function range(x)
{
  var result = [];
  for (var i=0; i<x; i++)
  {
    result.push(i);
  } 
  return result;
}

// return a list of objects, each having the attribute "key" and "value"
// each item in the list represents a key/value pair in the object
// (Used for purpose of iterating over object properties in Javascript
// templates)
function keyvalue(o)
{
  result = new Array();
  for (i in o)
  {
    result.push({"key":i, "value":o[i]});
  }
  return result;
}


// format a list of numbers
//
// i = index 
// end = max value
// w = window size
function long_list(start, pos, end, w)
{
  var r = new Object(); // result
  r.first = start;
  r.rew = null;
  r.left = [];
  r.pos = pos;
  r.right = [];
  r.fwd = null;
  r.last = end;

  // compute left side
  for (var i=Math.max(r.first+1, pos-w); i<pos; i++)
  {
    if (i>r.first) { r.left.push(i); }
  }
  // compute right side
  for (var i=pos+1; i<=Math.min(r.last-1, pos+w); i++)
  {
    if (i<end) { r.right.push(i); }
  }

  r.rew = pos-10;
  if (r.rew<=r.first) { r.rew = null; }

  r.fwd = pos+10;
  if (r.fwd>=r.last) { r.fwd = null; }

  if (r.first == r.pos) { r.first = null; }
  if (r.last == r.pos) { r.last = null; }

  //alert("Made pages=" + "${first} ... ${rew} ... [${left}] ${pos} [${right}] ... ${fwd} ... ${last}".process(r));
  return r;
}

// go thru pages object (as generated by long_list())
// and for each place where a page number ought to be displayed
// display the string t with the string %s replaced by the page number
// display the string t with the string %p replaced by the 1 + page number
function long_list_render(pages, t)
{
  var result = "";

  // short circuit if there are no pages
  if (pages.first == null && pages.rew == null && pages.left.length == 0 && pages.pos == 0 && pages.right.length == 0 && pages.fwd == null && pages.last == null)
  {
    return result;
  }

  if (pages.first || pages.first==0)
  {
   result += t.replace(/%s/g, pages.first).replace(/%p/g, 1+pages.first);
   if (!pages.rew && !pages.left.length && Math.abs(pages.pos-pages.first)>1 || (pages.left.length && Math.abs(pages.left.min()-pages.first)>1) || (pages.rew && Math.abs(pages.first-pages.rew)>1))
   {
     result += " ... ";
   }
  }
  if (pages.rew)
  {
    result += t.replace(/%s/g, pages.rew).replace(/%p/g, 1+pages.rew);
    result += " ... ";
  }
  if (pages.left)
  {
    for (var i=0; i<pages.left.length; i++)
    {
      var p = pages.left[i];
      result += t.replace(/%s/g, p).replace(/%p/g, 1+p);
    }
  }

  result += 1+pages.pos;

  if (pages.right)
  {
    for (var i=0; i<pages.right.length; i++)
    {
      var p = pages.right[i];
      result += t.replace(/%s/g, p).replace(/%p/g, 1+p);
    }
  }
  if (pages.fwd)
  {
    result += " ... ";
    result += t.replace(/%s/g, pages.fwd).replace(/%p/g, 1+pages.fwd);
  }
  if (pages.last)
  {
    if (!pages.left.length && !pages.fwd && Math.abs(pages.pos-pages.last)>1 || (pages.right.length && Math.abs(pages.right.max()-pages.last)>1) || (pages.fwd && Math.abs(pages.last-pages.fwd)>1))
    {
      result += " ... ";
    }
    result += t.replace(/%s/g, pages.last).replace(/%p/g, 1+pages.last);
  }

  return result;
}


// make a CGI/URL string representing the data in structure
function mkargs(structure)
{
  var args = new String();
  var args_tokens = new Array();
  for (var k in structure)
  {
    v = structure[k];
    if (v && isArray(v)) /* v.length) */
    {
      for (var i=0;i<v.length; i++)
      {
        args_tokens.push(escape(k) + "=" + escape(v[i]));
      }
    }
    else if (v != "")
    {
      args_tokens.push(k + "=" + escape(v));
    }
  }
  args = args_tokens.join("&");
  return args;
}

// construct a URL by appending params to base
// - base is a string like http://x/y
// - params is a list of parameters
// - each parameter is a two-element list, containing
//   the key as the first item and value as the second item
// i.e., if base="http://x/y", and params=[["a","1"],["x","5"]],
// then mkurl will return "http://x/y?a=1;x=5"
function mkurl ( args )//base, params)
{
  var base = args.base || "";
  var params = args.params || [];
  var url = "";
  url += base;
  params_values = []
  if (params.length>0)
  {
    url += "?";
    for (i=0;i<params.length; i++)
    {
      params_values.push(params[i][0] + "=" + params[i][1]);
    }
    url += params_values.join(";");
  }
  return url;
}


function quote_if_has_spaces(x)
{
  if (x.indexOf(" ")>=0)
  {
    return '"' + x + '"';
  }
  else
  {
    return x;
  }
}

// turn an object into json
function stringify(arg) {
    var i, o, u, v;

    switch (typeof arg) {
    case 'object':
        if (arg) {
            if (arg.constructor == Array) {
                o = '';
                for (i = 0; i < arg.length; ++i) {
                    v = stringify(arg[i]);
                    if (o) {
                        o += ',';
                    }
                    if (v !== u) {
                        o += v;
                    } else {
                        o += 'null,';
                    }
                }
                return '[' + o + ']';
            } else if (typeof arg.toString != 'undefined') {
                o = '';
                for (i in arg) {
                    v = stringify(arg[i]);
                    if (v !== u) {
                        if (o) {
                            o += ',';
                        }
                        o += stringify(i) + ':' + v;
                    }
                }
                return '{' + o + '}';
            } else {
                return "";
            }
        }
        return 'null';
    case 'unknown':
    case 'undefined':
    case 'function':
        return u;
    case 'string':
        return '"' + arg.replace(/(["\\])/g, '\\$1') + '"';
    default:
        return String(arg);
    }
    return "";
}


// return index of the element equal to searchElement, or -1 if not found
Array.prototype.indexOf = function(searchElement, fromIndex)
{
  if (!fromIndex)
  {
    var fromIndex = 0;
  }
  for (var i=fromIndex; i<this.length; i++)
  {
    if (this[i]==searchElement)
    {
      return i;
    }
  }
  return -1;
}

// remove an element indexed by elementIndex
Array.prototype.remove = function(elementIndex)
{
  for (i=elementIndex+1;i<this.length;i++)
  {
    this[i-1] = this[i];
  }
  this.length -= 1;
}

// true if item exists in array
Array.prototype.contains = function(item)
{
  for (var i=0; i<this.length; i++)
  {
    if (this[i]==item)
    {
      return true;
    }
  }
  return false;
};

// return min value of array items
Array.prototype.min = function()
{
  if (this.length==0) return null;

  var smallest = this[0];
  for (var i=0; i<this.length; i++)
  {
    if (this[i]<smallest) { smallest = this[i]; }
  }
  return smallest;
}


// return max value of array items
Array.prototype.max = function()
{
  if (this.length==0) return null;

  var largest = this[0];
  for (var i=0; i<this.length; i++)
  {
    if (this[i]>largest ) { largest = this[i]; }
  }
  return largest;
}

// pick a random thing from an array
Array.prototype.pickone = function()
{
  var i = Math.floor(Math.random() * this.length);
  return this[i];
}

// return self repeated x times
String.prototype.repeat = function(x)
{
  var result = "";
  for (var i=0; i<x; i++)
  {
    result += this;
  }
  return result;
}

// determine if two supplied strings fall in the 
// same "confidence category" (used for color
// styling) 
function confidence_category_cmp(a,b)
{
  if (!a || !b) { return false; }
  if (a.length==0) { return false; }
  if (b.length==0) { return false; }

  return (a[0]==b[0]);
}


// determine if two supplied strings fall in the 
// same "hmmr category" (used for color styling)
// by inspecting the first character of each string
function hmmr_category_cmp(a,b)
{
  if (!a || !b) { return false; }
  if (a.length==0) { return false; }
  if (b.length==0) { return false; }

  a_is_alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".indexOf(a[0])!=-1;
  b_is_alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".indexOf(b[0])!=-1;

  a_is_plus = "+".indexOf(a[0])!=-1;
  b_is_plus = "+".indexOf(b[0])!=-1;

  a_is_space = " ".indexOf(a[0])!=-1;
  b_is_space = " ".indexOf(b[0])!=-1;

  return ((a_is_alpha && b_is_alpha) ||
          (a_is_plus && b_is_plus) ||
          (a_is_space && b_is_space))
}


// find all repeating chunks of characters in this string
// and return a list of objects representing these chunks.
// each object has properties .text, .start and .end
String.prototype.splitIntoRepeats = function(compare_function)
{
  var result = new Array();

  var chunk = new Object();
  chunk.text = "";
  chunk.start = null;
  chunk.end = null;

  for (var i=0; i<this.length; i++)
  {
    if (chunk.text=="")
    {
      chunk = new Object();
      chunk.text = this[i];
      chunk.start = i;
      chunk.end = i+1;
    }
    else
    {
      if (compare_function(this[i].hmmr, chunk.text)) // if they are in the same category
      {
        chunk.text += this[i];
        chunk.end = i+1;
      }
      else
      {
        result.push(chunk);
        chunk = new Object();
        chunk.text = this[i];
        chunk.start = i;
        chunk.end = i+1;
      }
    }
  }
  result.push(chunk);
  return result;
}

function warning(text, dimming)
{
  if (el("warning"))
  {
    el("warning").innerHTML = text;
    el("warning_container").style.display = "block";
    if (dimming==1)
    {
      el("body").style.opacity = "0.5"; // for Firefox et al.
      el("body").style.filter = "alpha(opacity=50)"; // For IE
      window.setTimeout("el('body').style.opacity = '1.0'; el('body').style.filter = 'alpha(opacity=100)';", 5000);
    }
  }
}

var notice_timeout = null;

// put up a notice, timeout after timeout seconds
function notice(text, timeout)
{
  if (el("notice"))
  {
    el("notice").innerHTML = text;
    if (text=="")
    {
      el("notice_container").style.display = "none";
    }
    else
    {
      el("notice_container").style.display = "block";
      if (notice_timeout) { window.clearTimeout(notice_timeout); }
      notice_timeout = setTimeout("notice('');",timeout*1000);
    }
  }
}

// style an hmmr string based on the alignment 
// TODO: this should be "style an alignment based on the hmmr"
String.prototype.hmmr_style = function (alignment)
{
  var hmmr = new String(this);
  if (alignment.length > hmmr.length)
  {
    hmmr = hmmr + " ".repeat(alignment.length - hmmr.length);
  }
  if (alignment.length < hmmr.length)
  {
    //alignment = alignment + "-".repeat(hmmr.length - alignment.length);
    hmmr = hmmr.substr(0,alignment.length);
  }

  if (alignment.length != hmmr.length)
  {
    return "";
  }

  var result = "";

  var chunks = hmmr.splitIntoRepeats(hmmr_category_cmp);
  for (var i=0; i<chunks.length; i++)
  {
    var c = chunks[i];
    var style = "color: lightgrey;";
    if (c.text[0]=="+") // this is a "pretty good match according to the model"
    {
      // find the equivalent portion of text in a
      c.text = alignment.substr(c.start, c.end-c.start);

      style = "color: #0df";
    }
    else if (c.text[0]!=" ") // this an "exact match to highest probability"
    {
      style = "color: #00f;"
    }

    // decorate this chunk
    c.text = "<span style='" + style + "'>" + c.text.replace(/ /g,"-") + "</span>";

    // append it to the result
    result += c.text;
  }

  return result;
}

// style a secondary structure string based on confidence measures for each position
String.prototype.ss_style = function (confidence)
{
  var ss = new String(this);
  if (confidence.length > ss.length)
  {
    ss = ss + " ".repeat(confidence.length - ss.length);
  }

  if (confidence.length != ss.length) { return ""; }

  var result = "";

  var chunks = confidence.splitIntoRepeats(confidence_category_cmp);
  for (var i=0; i<chunks.length; i++)
  {
    var c = chunks[i];
    var style = "color: white;";
    var confidence_value = Number(c.text[0]);
    if (confidence_value || confidence_value==0)
    {
      // find the equivalent portion of text in ss
      c.text = ss.substr(c.start, c.end-c.start);
      confidence_value = Math.pow(confidence_value / 9.0, 4);
      var color = new Object();
      if (c.text[0]=="H") // 255, 153, 153
      {
        color.r = String(       Math.round(confidence_value * 255) );
        color.g = String( 255 - Math.round(confidence_value * 102) );
        color.b = String( 255 - Math.round(confidence_value * 102) );
      }
      else if (c.text[0]=="E") // 255, 153, 255
      {
        color.r = String(       Math.round(confidence_value * 255) );
        color.g = String( 255 - Math.round(confidence_value * 102) );
        color.b = String(       Math.round(confidence_value * 255) );
      }
      else if (c.text[0]=="C") // 153, 153, 153
      {
        color.r = String( 255 - Math.round(confidence_value * 102) );
        color.g = String( 255 - Math.round(confidence_value * 102) );
        color.b = String( 255 - Math.round(confidence_value * 102) );
      }
      else // use a grey ramp for unexpected characters
      {
        color.r = String( 255 - Math.round(confidence_value * 255) );
        color.g = String( 255 - Math.round(confidence_value * 255) );
        color.b = String( 255 - Math.round(confidence_value * 255) );
      }
      style = "color: rgb(${r}, ${g}, ${b});".process( color );
      //style = "color: rgb(" + String(intensity) + "," + String(intensity) + "," + String(intensity) + ")";
    }

    // decorate this chunk
    c.text = "<span style='" + style + "'>" + c.text + "</span>";

    // append it to the result
    result += c.text;
  }

  return result;
}

// style a conservation sequence string based on confidence measures for each position
String.prototype.conservation_style = function (confidence)
{
  var cons = new String(this);
  if (confidence.length > cons.length)
  {
    cons = cons + " ".repeat(confidence.length - cons.length);
  }

  if (confidence.length != cons.length) { return ""; }

  var result = "";

  var chunks = confidence.splitIntoRepeats(confidence_category_cmp);
  for (var i=0; i<chunks.length; i++)
  {
    var c = chunks[i];
    var style = "color: white;";
    var confidence_value;
    if (c.text[0]=="*")
    {
      confidence_value = 10;
    }
    else
    {
      confidence_value = Number(c.text[0]);
    }

    if (confidence_value || confidence_value==0)
    {
      // find the equivalent portion of text in the conservation
      c.text = cons.substr(c.start, c.end-c.start);
      confidence_value = Math.pow(confidence_value / 10.0, 1);
      var intensity = new Object();
      intensity.r = String(  255 - Math.round(confidence_value * 255)  );
      intensity.g = String(  255 - Math.round(confidence_value * 128)  );
      intensity.b = String(  255 - Math.round(confidence_value * 255)  );

      style = "color: rgb(${r|default:'0'}, ${g|default:'0'}, ${b|default:'0'});".process( intensity );
      //style = "color: rgb(" + String(intensity) + "," + String(intensity) + "," + String(intensity) + ")";
    }

    // decorate this chunk
    c.text = "<span style='" + style + "'>" + c.text + "</span>";

    // append it to the result
    result += c.text;
  }

  return result;
}

// shorten a string to maxlen by appending ... as necessary
String.prototype.shorten = function (maxlen)
{
  if (this.length >= maxlen)
  {
    return this.substring (0, maxlen) + "...";
  }
  else
  {
    return this;
  }
}

String.prototype.capitalize = function()
{
  return this.charAt(0).toUpperCase() + this.slice(1);
}

Number.prototype.commify = function ()
{
  x = String(this);
  if (x<10000) { return x; }

  var result = "";
  for (var i=x.length-1; i>=0; i--)
  {
    var pos = x.length - i - 1;
    if (pos > 0 && (pos)%3==0 ) { result = "," + result; }
    result = x.charAt(i) + result;
  }
  return result;
}


// return thing padded with character until it is of length len.
function prepad(character, len, thing)
{
  thing = String(thing);
  if (thing.length<len)
  {
    for (var i=0; i<=len-thing.length; i++)
    {
      thing = character + thing;
    }
  }
  return thing;
}


/* Show elements with id's in the to_show list by setting its display style to showstate
   Hide elements with id's in the to_hide list by setting its display style to hidestate
*/
function showhide(showstate, to_show, hidestate, to_hide)
{
  if (document.getElementById)
  {
    for (i=0; i<to_show.length; i++)
    {
      document.getElementById(to_show[i]).style.display = showstate;
    }
    for (i=0; i<to_hide.length; i++)
    {
      document.getElementById(to_hide[i]).style.display = hidestate;
    }
  } 
  // return false so if called from an <a onClick=...>, 
  // browser will remain on the same page
  return false
}

// see http://www.experts-exchange.com/Web/Web_Languages/JavaScript/Q_10095089.html for original code
function form2url(f)
{
  var s="";
  var i;
  for(i=0; i<f.length; i++)
  {
    var n=f.elements[i].name;
    var v=f.elements[i].value;
    var t=f.elements[i].type;

    if((t=="text") || (t=="hidden") || (t=="password") || (t=="textarea"))
    {
      v=f.elements[i].value;
      s+=escape(n)+"="+escape(v)+"&";
    }
    else if(t=="select-one")
    {
      v=f.elements[i].options[f.elements[i].selectedIndex].value;
      s+=escape(n)+"="+escape(v)+"&";
    }
    else if(t=="checkbox")
    {
      if(f.elements[i].checked)
      {
        v = v || "on";
        s+=escape(n)+"="+escape(v)+"&";
      }
    }
    else if(t=="select-multiple")
    {
      if(f.elements[i].selectedIndex!=-1)
      {
        l=f.elements[i].options.length;
        if(f.elements[i].selectedIndex>=0)
        {
          s+=escape(n)+"=";
        }
        vsel="";
        for(j=f.elements[i].selectedIndex;j<l;j++)
        {
          if(f.elements[i].options[j].selected)
          {
            v=f.elements[i].options[j].value;
            // escape , or not?
            vsel+=","+escape(v);
            //vsel+=escape(","+v);
          }
          
        }
        if(f.elements[i].selectedIndex>=0)
        {
          // escape , or not?        
          vsel=vsel.substring(1);
          //vsel=vsel.substring(1);
          s+=vsel+"&"
        }
      }
    }

    else if(t=="radio")
    {
      if(f.elements[i].checked)
      {
         v=f.elements[i].value;
         s+=escape(n)+"="+escape(v)+"&";
      }
    }
  }
  s=s.substring(0,s.length-1);

  while((inx=s.indexOf("%20"))!=-1)
  {
    //check for %%20
    if(inx>0)
    {
      if(s.charAt(inx-1)=='%')
      { //do nothing
      }
      else
      {
        s=s.substring(0,inx)+"+"+s.substring(inx+3);
      }
    }
    else
    {
      s=s.substring(0,inx)+"+"+s.substring(inx+3);
    }
  }
 
  loc=f.action+"";
  inx=loc.indexOf("?");
  if(inx!=-1) loc=loc.substring(0,inx);
  s=loc+"?"+s;
  return s;
}
