/*
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: 3-9-2010

todo,questions:
 1) show/hide columns
 2) compress data?
 3) dynamically generate rank column 1...n
 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
 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 npages = 0;
var activepage = -1;
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=1 (delimited)
var tblinfo = new Array();  
var tn = 0;  // current table number
var TI;      // current tblinfo

// 0                         1            2                   3                      4    5         6         7 
//[heading, fullname, url], align l/r/c, sort asc/desc/none, numeric/text/sortlink, 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) { tblinfo[k][0] = i; }
function setsc(k,j) { tblinfo[k][1] = j; }
function newtable(ti) {
  tblinfo.push(ti);
  _CI.push(new Array());
  _RI.push(new Array());
  _CS.push(new Array());
  _D.push(new Array());
  tn = tblinfo.length-1;
  pagetbls[npages-1].push(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(imax,j,vmin,vmax) {
  var x = Array();
  var y = Array();
  var n = 0;
  for (var i=0;i<imax;i++) {
    var valx = i+1;
    var valy = Dij(i,j);
    if ( (valy >= vmin) && (valy <= vmax) ) {
      x[n] = valx;
      y[n] = valy;
      n++;
    }
  }
  return Math.round(1000*correlation(x,y));
}
function setcor(ic,imax,vmin,vmax) {
  for (var j=0;j<CI.length;j++) {
    if (CI[j][5] & 2)
      _D[tn][ic][sjj[j]] = getcor(imax,j,vmin,vmax);
  }
}


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

  if (buttons==2 && npages>5)
    buttons = 0 ;
  var s = "";
  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 (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();
  }      
}

// set tablenumber
function settn(k) {
  tn = k;
  TI = tblinfo[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 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 (TI[2] & 1)
    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</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]) {
            row += "<tr><td class='stdht'><a href=\"" + CI[i][0][2] + "\">" + CI[i][0][1] + "</a>";
            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];
    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]);
}

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 = window.innerWidth - TI[3];
  var maxh = window.innerHeight - TI[4];
  var neww = Math.min(maxw-Lw-Rw,Mw);
  var newh = Math.min(maxh-Hh-Fh,Bh);
  divBody.style.height = newh
  divHM.style.width = divBM.style.width = divFM.style.width = neww

  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,ilen-TI[8],1,99999);
    else if (RI[i] & 4)
      setcor(i,ilen-TI[8],1,25);
  }


  ///////////// delimited table //////////////////////
  if (TI[2] & 1) {
    output.push("<pre>");
    row = "";
    for (var j=j0;j<j3;j+=CI[j][4]) {
      var s = CI[j][0];
      row += s[0];
      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 < tblinfo.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);
    }
  }

  showpage(0);
}


function numsort(a,b,mult) {
  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]];

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

// ith row of table k
function sortbyrow(i,k,nomake) {
  settn(k);
  ii = sii[i];
  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 = numsort;

    var v = new Array();

    for (var j=j0;j<j1;j++)
      v[j-j0] = Dij(i,j);
    var sdir = (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]];

  if (!nomake)
    remake();
  return false;
}
