/*
http://www.frequency-decoder.com/2006/09/16/unobtrusive-table-sort-script-revisited
http://dynamic-tools.net/toolbox/
http://www.oreillynet.com/pub/a/javascript/2003/05/06/dannygoodman.html
http://www.jorendorff.com/articles/javascript/speed-test.html

  written by: Kenneth Massey
  last update: 8-7-2010

todo,questions:
 1) show/hide columns
 2) compress data?
 4) reloads on back button if php login, use cookie to remember sorted cols
 5) click to highlight row?

notes:
 cf mid=1, 700 tm table takes about 5.5 seconds for 4 sorts
   chrome loads way faster (1.5 sec) vs firefox (3.5 sec)
 see tbenchmark.htm
 string catenation is the main factor in speed


table layout
  divHead:  divHL  | divHM  | divHR 
             tblHL |  tblHM |  tblHR 
            ------------------------
  divBody:  divBL  | divBM  | divBR
             tblBL |  tblBM |  tblBR 
            -------------------------
  divFoot:  divFL  | divFM  | divFR
             tblFL |  tblFM |  tblFR 


tbody scroll only in Firefox, use divs instead
use alert(string) for debugging
var isNet = (navigator.appName == "Netscape") && (parseFloat(navigator.appVersion) >= 5);
*/ 

var npages = 0;
var activepage = -1;
var candelimit = 0;
var delimited = 0;
var pageinfo = new Array();
var pagetbls = new Array();

//  0         1          2         3        4          5     6      7    8    9     10      
// [sortedrow,sortedcol, bitfield, widthbuf,heightbuf, fleft,fright,ftop,fbot,minhr,initsort]
// bitfield not used right now
var _TI = new Array();  
var tn = 0;  // current table number
var TI;      // current table info

var _initRow = new Array();

// 0                         1            2                   3                           4    5         6         7 
//[heading, fullname, url], align l/r/c, sort asc/desc/none, numeric/text/sortlink/chart, span, bitfield, decimals, haslink
// bitfield 1=( ) 2=correlation
var _CI = new Array();  // _CI[tn][sjj]
var CI = new Array();                 // current column info

// _RI:  1=sortdescending, 2=correlation, 4=top25 correlation
var _RI = new Array();  // _RI[tn][sii]
var RI;                 // current row info

var _CS = new Array();    // header colspan
var csi;
var _D = new Array();     // cell data
var si_i = new Array();   // sorting indices
var si_j = new Array();
var sii;   // si_i[tn]
var sjj;




function newpage(title) {
  npages++;
  pageinfo.push(title);
  pagetbls.push(new Array());
}

// set sorted row/col
function setsr(k,i) { _TI[k][0] = i; }
function setsc(k,j) { _TI[k][1] = j; }
function newtable(ti) {
  _TI.push(ti);
  _CI.push(new Array());
  _RI.push(new Array());
  _CS.push(new Array());
  _D.push(new Array());
  tn = _TI.length-1;
  pagetbls[npages-1].push(tn);
  TI = _TI[tn];
  CI = _CI[tn];   // reference arrays for php
  RI = _RI[tn];
  CS = _CS[tn];
  DI = _D[tn];
  //  _CI[tn] = CI.slice(0);  // makes a clone (not assign reference)
}

function correlation(x,y) {
  var n = x.length;
  var sx=0,sy=0,sxx=0,syy=0,sxy=0;
  for (var i=0;i<n;i++) {
    sx += x[i];
    sy += y[i];
    sxx += x[i]*x[i];
    syy += y[i]*y[i];
    sxy += x[i]*y[i];
  }
  return (n*sxy-sx*sy) / Math.sqrt( (n*sxx-sx*sx) * (n*syy-sy*sy) );
}
function getcor(j,vmin,vmax) {
  i1 = TI[7]; i2 = _D[tn].length - TI[8];
  var x = Array();
  var y = Array();
  var n = 0;
  for (var i=i1;i<i2;i++) {
    var valx = Dijval(i,TI[1],0);
    var valy = Dijval(i,j,0);
    if ( valx && valy && (valy >= vmin) && (valy <= vmax) ) {
      x[n] = valx;
      y[n] = valy;
      n++;
    }
  }
  return Math.round(1000*correlation(x,y));
}
function setcor(ic,vmin,vmax) {
  for (var j=0;j<CI.length;j++) {
    if (CI[j][5] & 2)
      _D[tn][ic][j] = getcor(j,vmin,vmax);
  }
}
function setrank(j) {
  i1 = 0; i2 = _D[tn].length - TI[8];
  for (var i=i1;i<i2;i++)
    _D[tn][sii[i]][j] = i+1;
}


// buttons = 0 (pull-down), 1 (buttons), 2 (decide based on length)
function pagemenu(buttons) {
  var x = document.getElementById("pagemenu");
  if (!x)
    return;

  var s = "";
  if (candelimit)
    s += "<button onclick=\"delimited=(delimited+1)%2;showpage(-1);\">Delimited</button>";

  if (npages > 1) {
    if (buttons==2 && npages>5)
      buttons = 0 ;
    if (!buttons)
      s += "<select onchange=\"showpage(this.selectedIndex)\">";
    for (var i=0;i<npages;i++) {
      if (buttons)
        s += "<button onclick=\"showpage(" + i + ");\">" + pageinfo[i] + "</button>";
      else
        s += "<option value=" + i + ">" + pageinfo[i];
    }
    if (!buttons)
      s + "</select>";
  }
  x.innerHTML = s;
}


function showpage(n) {
  if (n < 0)
    n = activepage;
  if (n != activepage) {
    if (activepage >= 0)
      document.getElementById('pagediv'+activepage).style.display='none';
    activepage = n;
    document.getElementById('pagediv'+activepage).style.display='block';
  }
  for (var i in pagetbls[activepage]) {
    settn(pagetbls[activepage][i]);
    maketable();

    if (_initRow[tn]) {
      var divBody = document.getElementById("divBody"+tn);  
      divBody.scrollTop = _initRow[tn]*Math.round(divBody.scrollHeight / (i2-i1));
    }
  }      
}

// set tablenumber
function settn(k) {
  tn = k;
  TI = _TI[tn];
  sii = si_i[tn];
  sjj = si_j[tn];

  var n = _CI[tn].length;
  CI = new Array(n);
  for (var j=0;j<n;j++)
    CI[j] = _CI[tn][sjj[j]];

  n = _RI[tn].length;
  RI = new Array(n);
  for (var i=0;i<n;i++)
    RI[i] = _RI[tn][sii[i]];
}

function Dij(i,j) {
  return _D[tn][sii[i]][sjj[j]];
}

function Dijval(i,j,smap) {
  if (i >= 0) {
    if (smap) {
      i = sii[i];
      j = sjj[j];
    }
    d = _D[tn][i][j]
    if (d==undefined) d="";
    return (d instanceof Array) ? d[0] : d;
  }
  else {   // header row if i<0
    d = CI[j][0];   
    if (d==undefined) d="";
    if (d instanceof Array) 
      return d[1] ? d[1] : d[0];
    else 
      return d;
  }
}

function getcell(i,j) {
  var ri = RI[i];
  var ci = CI[j];
  d = Dij(i,j);
  if (d==undefined) d="";
  s_classes = 's';
  var url = "";
  if (d instanceof Array) {   // [displayval, hiddenval/url, classes]
    s = d[0];
    if (ci[7])
      url = d[1];
    if (d.length > 2)
      s_classes += " " + d[2];
  }
  else
    s = d;
  if (delimited)
    return s;
  if (! (ri & 2)) {
    if (ci[6] && s!=='')
      s = s.toFixed(ci[6]);
    if (ci[5]&1) 
      s = "(" + s + ")";
  }
  if (ci[3]=='s')
    s = "<a href=\"\" onclick=\"this.blur();return sortbyrow("+i+","+tn+");\">&gt&gt</a>";
  if (ci[3]=='c')
      s = "<a href=# onclick='timeseries("+i+");'>chart</a>";
  if (url)
    s = "<a href=\"" + url + "\">" + s + "</a>";
  var row = "<td class='";
  if (ci[1] != 'r')
    s_classes += " " + ci[1];
  var ii = sii[i];
  var jj = sjj[j];
  if ( (sii[i]==TI[0]) || ((jj >= TI[1]) && (jj < TI[1]+CI[TI[1]][4])) )
    s_classes += " sorted";
  if (ci[4] > 1)
    s_classes += " noright";
  row += s_classes + "'>" + s + "</td>";
  return row;
}


function dohead(j0,j1) {
  row = "<thead>";
  var cs = _CS[tn];

  row += dummyrow("th",j0,j1);  // dummy blank row at top for cellresize

  if (cs.length > 0) {
    
    row += "<tr>";
    for (var j=j0;j<j1;j+=cs[csi++][0]) {
      row += "<th class='s' colspan=" + cs[csi][0] + ">"
      if (TI[9] > 0) {
        row += "<table>";
        var n=0;
        for (var i=j;i<j+cs[csi][0] && i<j1;i++) {
          if (CI[i][0][2] !== undefined) {
	    if (CI[i][0][2])
              row += "<tr><td class='stdht'><a target=_blank href=\"" + CI[i][0][2] + "\">" + CI[i][0][1] + "</a>";
            else
              row += "<tr><td class='stdht'>"+CI[i][0][1];
            n++;
          }
        }
        while (n++ < TI[9])
          row += "<tr><td class='stdht'><br>";  // need br to be correct height ???
        row += "</table>";
      }
      else {
        if (cs[csi][1] instanceof Array)
  	  row += "<a href=\"" + cs[csi][1][1] + "\">" + cs[csi][1][0] + "</a>";
        else
          row += cs[csi][1];
      }
    }
  }

  row += "<tr>";
  for (var j=j0;j<j1;j+=CI[j][4]) {
    row += "<th class='s' colspan=" + CI[j][4] + ">";
    var s = CI[j][0];
    var heading = s[0];
    var title = (s.length > 1) ? s[1] : s[0];
    if (CI[j][3] == 's')
      row += "<a href=\"\" onclick=\"this.blur(); return sortbyrow(-1,"+tn+");\" title=\"Sort Row\">&gt&gt</a>";
    else
      row += "<a href=\"\" onclick=\"this.blur(); return sortbycol("+j+","+tn+");\" title=\"" + title +"\">" + heading + "</a>";
  }
  row += "<tbody>";
  return row;
}

window.onresize = pageresize;
window.onload = myonload;

function pageresize() {
  for (var i in pagetbls[activepage])
    divresize(pagetbls[activepage][i]);
}

// necessary since IE doesn't support window.innerWidth/Height
// return smallest non 0 (null) value
function IEhack(x,y,z) {
  if (!x) x=9;
  if (!y) y=9;
  if (!z) z=9;
  return Math.max(x,y,z);
}
function windowWidth() {
  return IEhack(window.innerWidth,document.documentElement.clientWidth,document.body.clientWidth);
}
function windowHeight() {
  return IEhack(window.innerHeight,document.documentElement.clientHeight,document.body.clientHeight);
}

function divresize(k) {
  var divBody = document.getElementById("divBody"+k);  
  var divHM = document.getElementById("divHM"+k);
  var divBM = document.getElementById("divBM"+k);
  var divFM = document.getElementById("divFM"+k);
  var Lw = TI[5] ? document.getElementById("tblHL"+k).offsetWidth : 0;
  var Mw = document.getElementById("tblHM"+k).offsetWidth;
  var Rw = TI[6] ? document.getElementById("tblHR"+k).offsetWidth : 0;
  var Hh = document.getElementById("tblHM"+k).offsetHeight;
  var Bh = document.getElementById("tblBM"+k).offsetHeight;
  var Fh = document.getElementById("tblFM"+k).offsetHeight;
  var maxw = windowWidth() - TI[3];
  if (TI[4] > 50)
    TI[4] = 20;   // max rows to show
  var maxh = windowHeight() - 200;  // TI[4];
  var neww = Math.min(maxw-Lw-Rw,Mw);
  var newh = Math.min(maxh-Hh-Fh,Bh,TI[4]*18);
  divBody.style.height = newh + 'px';
  divHM.style.width = divBM.style.width = divFM.style.width = neww+'px';

  divBody.style.overflowY = (newh < Bh) ? "scroll" : "hidden";
  divFM.style.overflowX = (neww < Mw) ? "scroll" : "hidden";
}


function cellresize(LMR) {
  var HT = document.getElementById("tblH"+LMR+tn);
  var BT = document.getElementById("tblB"+LMR+tn);
  var FT = document.getElementById("tblF"+LMR+tn);
  var w = new Array();
  var n = HT.rows[0].cells.length;
  for(var j=0;j<n;j++)
    w[j] = Math.max(HT.rows[0].cells[j].offsetWidth,BT.rows[0].cells[j].offsetWidth,FT.rows[0].cells[j].offsetWidth);
  HT.style.tableLayout = BT.style.tableLayout = FT.style.tableLayout = 'fixed';
  for(var j=0;j<n;j++)
    HT.rows[0].cells[j].width = BT.rows[0].cells[j].width = FT.rows[0].cells[j].width = w[j]+0;
}

function dummyrow(tag,j0,j1) {
  var row = "<tr>";
  for (var j=j0;j<j1;j+=CI[j][4])
    row += "<" + tag + " class='dummy' colspan=" + CI[j][4] + ">";
  return row;
}

function maketable() {
  var ilen = _D[tn].length;
  var jlen = CI.length;
  if (ilen == 0) {
    document.getElementById("tablediv"+tn).innerHTML = "";
    return;
  }

  i0 = 0; i1 = TI[7]; i2 = ilen-TI[8]; i3 = ilen;
  j0 = 0; j1 = TI[5]; j2 = jlen-TI[6]; j3 = jlen;

  var output = new Array();
  var row;

  csi = 0;

  for (var i=0;i<ilen;i++) {
    if (RI[i] & 2)
      setcor(i,-99999,99999);
    else if (RI[i] & 4)
      setcor(i,1,25);
  }


  ///////////// delimited table //////////////////////
  if (delimited) {
    output.push("<pre>");
    row = "";
    for (var j=j0;j<j3;j+=CI[j][4]) {
      var s = Dijval(-1,j,0);
      row += s;
      for (var jj=0;jj<CI[j][4];jj++)
	row += "\t";
    }
    row += "\n";
    output.push(row);

    for (var i=i0;i<i3;i++) {
      row = "";
      for (var j=j0;j<j3;j++) {
	s = getcell(i,j);
	row += s + "\t";
      }
      row += "\n";
      output.push(row);
    }
    output.push("</pre>");

    document.getElementById("tablediv"+tn).innerHTML = output.join("");
    return;
  }

  /////////////  header  ///////////////////////
  output.push("<div id='divHead"+tn+"' class='floatheader'>");

  if (TI[5]) {
    output.push("<div id='divHL"+tn+"' class='floatleft'><table class='sortable' id='tblHL"+tn+"'>");
    output.push(dohead(j0,j1));
    for (var i=i0; i<i1; i++) {
      row = "<tr>";
      for (var j=j0; j<j1; j++)
        row += getcell(i,j);
      output.push(row);
    }
    output.push("</table></div>");
  }

  output.push("<div id='divHM"+tn+"'  class='floatleft'><table class='sortable givewidth' id='tblHM"+tn+"'>");
  output.push(dohead(j1,j2));
  for (var i=i0; i<i1; i++) {
    row = "<tr>";
    for (var j=j1; j<j2; j++)
      row += getcell(i,j);
    output.push(row);
  }
  output.push("</table></div>");

  if (TI[6]) {
    output.push("<div id='divHR"+tn+"' class='floatleft'><table class='sortable' id='tblHR"+tn+"'>");
    output.push(dohead(j2,j3));
    for (var i=i0; i<i1; i++) {
      row = "<tr>";
      for (var j=j2; j<j3; j++)
        row += getcell(i,j);
      output.push(row);
    }
    output.push("</table></div>");
  }


  output.push("<div class='floatclear'></div></div>");

  /////////////  body  ///////////////////////

  output.push("<div id='divBody"+tn+"' class='floatbody'>");


  if (TI[5]) {
    output.push("<div id='divBL"+tn+"' class='floatleft'><table class='sortable' id='tblBL"+tn+"'>");
    output.push(dummyrow("td",j0,j1));
    for (var i=i1; i<i2; i++) {
      row = "<tr class='tr" + (i%2) + "'>";
      for (var j=j0; j<j1; j++)
        row += getcell(i,j);
      output.push(row);
    }
    output.push("</table></div>");
  }

  output.push("<div id='divBM"+tn+"' class='floatleft'><table class='sortable givewidth' id='tblBM"+tn+"'>");
  output.push(dummyrow("td",j1,j2));
  for (var i=i1; i<i2; i++) {
    row = "<tr class='tr" + (i%2) + "'>";
    for (var j=j1; j<j2; j++)
      row += getcell(i,j);
    output.push(row);
  }
  output.push("</table></div>");

  if (TI[6]) {
    output.push("<div id='divBR"+tn+"' class='floatleft'><table class='sortable' id='tblBR"+tn+"'>");
    output.push(dummyrow("td",j2,j3));
    for (var i=i1; i<i2; i++) {
      row = "<tr class='tr" + (i%2) + "'>";
      for (var j=j2; j<j3; j++)
        row += getcell(i,j);
      output.push(row);
    }
    output.push("</table></div>");
  }

  output.push("<div class='floatclear'></div></div>");

  /////////////  footer  ///////////////////////

  output.push("<div id='divFoot"+tn+"' class='floatfooter'>");

  if (TI[5]) {
    output.push("<div id='divFL"+tn+"' class='floatleft'><table class='sortable' id='tblFL"+tn+"'>");
    output.push(dummyrow("td",j0,j1));
    for (var i=i2; i<i3; i++) {
      row = "<tr>";
      for (var j=j0; j<j1; j++)
        row += getcell(i,j);
      output.push(row);
    }

    output.push("</table></div>");
  }

  output.push("<div id='divFM"+tn+"' class='floatleft' onscroll=\"document.getElementById('divHM"+tn+"').scrollLeft=document.getElementById('divBM"+tn+"').scrollLeft=this.scrollLeft;\"><table class='sortable givewidth' id='tblFM"+tn+"'>");
  output.push(dummyrow("td",j1,j2));
  for (var i=i2; i<i3; i++) {
    row = "<tr>";
    for (var j=j1; j<j2; j++)
      row += getcell(i,j);
    output.push(row);
  }

  output.push("</table></div>");

  if (TI[6]) {
    output.push("<div id='divFR"+tn+"' class='floatleft'><table class='sortable' id='tblFR"+tn+"'>");
    output.push(dummyrow("td",j2,j3));
    for (var i=i2; i<i3; i++) {
      row = "<tr>";
      for (var j=j2; j<j3; j++)
        row += getcell(i,j);
      output.push(row);
    }

    output.push("</table></div>");
  }

  output.push("<div class='floatclear'></div></div>");


  document.getElementById("tablediv"+tn).innerHTML = output.join("");



  divresize(tn);  // necessary in Chrome ???
    if (TI[5])
      cellresize('L');
    cellresize('M');
    if (TI[6])
      cellresize('R');
  divresize(tn);

}




function myonload() {
  pagemenu(2);

  for (var k = 0; k < _TI.length; k++) {
    var n = _D[k].length;
    si_i[k] = new Array(n);
    for (var i=0;i<n;i++)
      si_i[k][i] = i;

    n = _CI[k].length;
    si_j[k] = new Array(n);
    for (var j=0;j<n;j++)
      si_j[k][j] = j;

    settn(k);

    if (TI[10]) {  // sort on load
      var i = TI[0];
      if (i >= 0) {
        TI[0] = -1;
        sortbyrow(i,k,1);
      }
      var j = TI[1];
      TI[1] = -1;
      sortbycol(j,k,1);
      if (TI[10] < 0)    // clumsy way to make it set rankings for sorted column
        setrank(-TI[10]);
    }
  }

  showpage(0);
}


function numsort(a,b,mult) {
  if (a==undefined)
    return (b==undefined) ? 0 : -1;  // blanks go last
  if (b==undefined) return 1;
    /*
  if (a==='')
    return (b==='') ? 0 : -1;  // blanks go last
  if (b==='') return 1;
    */
  return mult*(b-a);
}
function numsort2(a,b,mult) {  // treat blank as zero
  if (a==='') a=0;
  if (b==='') b=0;
  return mult*(b-a);
}
function textsort(a,b,mult) {
  x = (b < a) ? -1 : ((a < b) ? 1 : 0);
  return mult*x;
}

// crude sort, but it's stable (built in javascript array sort function is not)
// sorts based on v and returns sorted index vi
function mysort(v,scomp,ad) {
  var n = v.length;
  var vi = new Array(n);
  for (var i=0;i<n;i++)
    vi[i] = i;
  var mult = (ad=='a') ? 1 : -1;

  for (var i=1;i<n;i++) {
    var ii = vi[i];
    var junk = v[ii];
    var j = i;
    while ( (--j >= 0) && (scomp(junk,v[vi[j]],mult) > 0) ) {
      vi[j+1] = vi[j];
      vi[j] = ii;
    }
  }
  return vi;
}

function remake() {
  var x=0,y=0;
  if (TI[5]) {  // restore scroll positions
    x = document.getElementById('divHM'+tn).scrollLeft;
    y = document.getElementById('divBody'+tn).scrollTop;
  }
  maketable();
  if (x+y) {
    document.getElementById('divHM'+tn).scrollLeft = x;
    document.getElementById('divBody'+tn).scrollTop = y;
  }
}

// jth column of table k
function sortbycol(j,k,nomake) {
  settn(k);
  if (CI[j][2] == 'n')
    return false;
  jj = sjj[j];
  var i0 = 0;  // row indices to be sorted
  var i1 = _D[tn].length-TI[8];
  var n = i1-i0;
  var si = sii.slice(i0,i1);
  var vi;
  if (jj != TI[1]) {
    TI[1] = jj;   
    var sorter = numsort;
    var ctype = CI[j][3];
    if (ctype == '2')  
      sorter = numsort2;
    else if (ctype == 't')
      sorter = textsort;

    var v = new Array();
    var junk;
    for (var i=i0;i<i1;i++) {
      junk = Dij(i,j);
      v[i-i0] = (junk instanceof Array) ? ( (ctype=='1') ? junk[1] : junk[0] ) : junk;
    }
    vi = mysort(v,sorter,CI[j][2]);
  }
  else {  // reverse
    vi = new Array(n);
    for (var i=0;i<n;i++)
      vi[i]=n-1-i;
  }
  for (var i=0;i<n;i++)
    sii[i0+i] = si[vi[i]];

  settn(k);  // reorders RI
  if (!nomake)
    remake();
  return false;  // necessary so onclick won't reload page
}

// ith row of table k
// if i<0 then sort by header row
function sortbyrow(i,k,nomake) {
  settn(k);
  var nothead = (i >= 0) ? 1 : 0;
  ii = nothead ? sii[i] : -1;
  var j0 = TI[5];
  var j1 = CI.length-TI[6];
  var n = j1-j0;
  var sj = sjj.slice(j0,j1);
  var vi;
  if (ii != TI[0]) {
    TI[0] = ii;   
    var sorter = nothead ? numsort : textsort;

    var v = new Array();

    for (var j=j0;j<j1;j++)
      v[j-j0] = Dijval(i,j,1);
    var sdir = (nothead && (RI[i] & 1)) ? 'd' : 'a';
    vi = mysort(v,sorter,sdir);
  }
  else {  // reverse
    vi = new Array(n);
    for (var j=0;j<n;j++)
      vi[j]=n-1-j;
  }
  for (var j=0;j<n;j++)
    sjj[j0+j] = sj[vi[j]];

  settn(k);  // reorders CI
  if (!nomake)
    remake();
  return false;
}

