/**
 * colTypes must be a string array that is the same length as the number of
 *   columns in the table - it must specify what type of data is in each
 *   column.
 * Valid column type names are: String, CaseInsensitiveString, Number,
 *                              NumberWithCommas, Date, DateTime, and None
 * The last two parameters are optional.
 * Specify colNumber and isAscending (true/false) if you want one of
 * the columns to start out already have an arrow in it (because you have
 * pre-sorted that column yourself). Specify 0 for the first column.
 */
function SortTable(blankImageURL, upImageURL, downImageURL, table, colTypes,
                   colNumber, isAscending)
{
  SortTable.prototype.blankImageURL = blankImageURL;
  SortTable.prototype.upImageURL = upImageURL;
  SortTable.prototype.downImageURL = downImageURL;
  SortTable.prototype.table = table;
  SortTable.prototype.colTypes = colTypes;
  SortTable.prototype.currentSortIndex = -1;
  SortTable.prototype.currentSortDir = "";
  SortTable.prototype.rowSortArray = null;

  if (table == null) {
    alert("null table passed into SortTable ctor");
    return;
  }
  if (colTypes == null) {
    alert("null colTypes passed into SortTable ctor");
    return;
  }
  
  if (isAscending == null)  isAscending = true;

  // put blank image and attach event handlers to col headers that are not type "None"
  // look at first row of the table only

  var row = table.rows[0];
  for (var i = 0; i < row.cells.length; i++) {
    var cell = row.cells[i];
    var type = colTypes[i];
    if (type != "None") {
      var image = document.createElement("img");

      if (colNumber == i) {
        var url = isAscending ? upImageURL : downImageURL;
        image.setAttribute("src", url);
        SortTable.prototype.currentSortIndex = colNumber;
        SortTable.prototype.currentSortDir = isAscending ? "up" : "down";
      }
      else {
        image.setAttribute("src", SortTable.prototype.blankImageURL);
      }

      cell.appendChild(image);
      cell.style.cursor = "pointer";

      if (cell.addEventListener != null) {
        cell.addEventListener("click", this.headerClicked, true);
      }
      else {
        cell.attachEvent("onclick", this.headerClicked);
      }
    }
  }

  // register compare functions with sort types
  var compareArray = new Array();
  compareArray.push(new ComparePair("String", SortTable.compareString));
  compareArray.push(new ComparePair("CaseInsensitiveString",
    SortTable.compareCaseInsensitiveString));
  compareArray.push(new ComparePair("Number", SortTable.compareNumber));
  compareArray.push(new ComparePair("NumberWithCommas", SortTable.compareNumberWithCommas));
  compareArray.push(new ComparePair("Date", SortTable.compareDate));
  compareArray.push(new ComparePair("DateTime", SortTable.compareDateTime));
  SortTable.prototype.compareArray = compareArray;
}

/**
 * This function should be treated as private.
 */
SortTable.prototype.changeImage = function(index, imageURL) {
  var row = SortTable.prototype.table.rows[0];
  var cell = row.cells[index];
  for (var i = 0; i < cell.childNodes.length; i++) {
    var node = cell.childNodes[i];
    if (node.nodeName == "img" || node.nodeName == "IMG") {
      node.setAttribute("src", imageURL);
      break;
    }
  }
}

/**
 * This function should be treated as private.
 */
SortTable.prototype.copyFromTable = function() {
  // This operation only needs to be done once - if it's already been
  // done then exit.
  // we manually do a deep clone of each row because IE 6.0 doesn't
  // seem to properly do a deep clone (loses the cells)
  // but when we do the clone we use an array of CellCopy objects
  // for the cells instead of duplicating the whole row object structure

  if (SortTable.prototype.rowSortArray != null)  return;
  
  var rowSortArray = new Array();
  var table = SortTable.prototype.table;

  for (var i = 1; i < table.rows.length; i++) {
    var row = table.rows[i];
    var cellArray = new Array();
    
    for (var c = 0; c < row.cells.length; c++) {
      var innerHTML = row.cells[c].innerHTML;
      var innerText = SortTable.prototype.getInnerText(row.cells[c]);
      cellArray.push(new CellCopy(innerHTML, innerText));
    }
    
    rowSortArray.push(cellArray);
  }
  
  SortTable.prototype.rowSortArray = rowSortArray;
}

SortTable.prototype.headerClicked = function(event) {
  var startTime = new Date();

  // Firefox 1.0 uses target and IE 6.0 uses srcElement
  var target = (event.target == null) ? event.srcElement : event.target;
  var oldSortIndex = SortTable.prototype.currentSortIndex;
  while (target.nodeName != "TH" && target.nodeName != "TD"
         && target.nodeName != "th" && target.nodeName != "td")
  {
    if (target.parentNode == null) {
      // this should never happen
      return;
    }
    target = target.parentNode;
  }
  SortTable.prototype.currentSortIndex = target.cellIndex;
  var currentSortIndex = target.cellIndex;
  if (oldSortIndex != currentSortIndex && oldSortIndex != -1) {
    SortTable.prototype.changeImage(oldSortIndex, SortTable.prototype.blankImageURL);
  }
  var type = SortTable.prototype.colTypes[target.cellIndex];

  // set currentCompareFunc to the appropriate one for the column clicked
  SortTable.prototype.currentCompareFunc = null;
  if (type != "None") {
    var compareArray = SortTable.prototype.compareArray;
    for (var i = 0; i < compareArray.length; i++) {
      if (compareArray[i].getName() == type) {
        SortTable.prototype.currentCompareFunc = compareArray[i].getFunction();
        break;
      }
    }
    if (SortTable.prototype.currentCompareFunc == null)  {
      alert("Type " + type + " not supported.");
      return;
    }
  }

  var table = SortTable.prototype.table;
  if (table.rows.length < 2)  return;

  SortTable.prototype.copyFromTable();
  var rowSortArray = SortTable.prototype.rowSortArray;

  var newImageURL = SortTable.prototype.upImageURL;
  if (currentSortIndex == oldSortIndex) {
    if (SortTable.prototype.currentSortDir == "up") {
      SortTable.prototype.currentSortDir = "down";
      newImageURL = SortTable.prototype.downImageURL;
    }
    else  SortTable.prototype.currentSortDir = "up";
  }
  else  SortTable.prototype.currentSortDir = "up";

  // Note sort time is fast, even with 1000+ array
  //var sortStartTime = new Date();
  rowSortArray.sort(SortTable.prototype.compareWrapperFunc);
  //var sortTime = new Date() - sortStartTime;
  //alert("sort time was " + sortTime + " ms.");

  // Now copy the rows from the sorted array back to the original table
  var backStartTime = new Date();

  for (var i = 1; i < table.rows.length; i++) {
    var row = table.rows[i];
    var cellArray = rowSortArray[rowSortArray.length - i];

    for (var c = 0; c < row.cells.length; c++) {
      row.cells[c].innerHTML = cellArray[c].getInnerHTML();
    }
  }

  var backTime = new Date() - backStartTime;
  //alert("back time was " + backTime + " ms.");

  SortTable.prototype.changeImage(currentSortIndex, newImageURL);

  var time = new Date() - startTime;
  //alert("time was " + time + " ms.");
}

SortTable.prototype.compareWrapperFunc = function(x, y) {
  var currentSortIndex = SortTable.prototype.currentSortIndex;

  x = x[currentSortIndex].getInnerText();
  y = y[currentSortIndex].getInnerText();

  //alert("x, y = " + x + ", " + y);
  var z = SortTable.prototype.currentCompareFunc(x, y);
  if (SortTable.prototype.currentSortDir == "up")  z = -z;
  return z;
}

/**
 * Get the plain text inside an element.
 * Returns the empty string if none is found.
 * (does the same thing as the non-standard innerText property)
 */
SortTable.prototype.getInnerText = function(element) {
  // check for innerText - because for some reason the switch
  // statement below doesn't seem to work in IE 6.0
  if (element.innerText != null)  return element.innerText;

  var nodeList = element.childNodes;
  
  for (var i = 0; i < nodeList.length; i++) {
    switch (nodeList[i].nodeType) {
      case Node.ELEMENT_NODE:
        return SortTable.prototype.getInnerText(nodeList[i]);
      case Node.TEXT_NODE:
        return nodeList[i].nodeValue;
    }
  }
  
  return "";
}

SortTable.compareString = function(x, y) {
  return (x < y) ? -1 : ((x > y) ? 1 : 0);
}

SortTable.compareCaseInsensitiveString = function(x, y) {
  x = x.toLowerCase();
  y = y.toLowerCase();
  return (x < y) ? -1 : ((x > y) ? 1 : 0);
}

SortTable.compareNumber = function(x, y) {
  return Number(x) - Number(y);
}

SortTable.compareNumberWithCommas = function(x, y) {
  return Number(x.replace(",", "")) - Number(y.replace(",", ""));
}

/**
 * Assumes that x and y are of the form dd-MMM-yyyy (ex 05-Jun-2005).
 * Also has logic so that if no dashes are present (not a date), will
 * sort before all actual dates.
 */
SortTable.compareDate = function(x, y) {
  var xa = x.split('-');
  var ya = y.split('-');

  if (xa.length == 1 || ya.length == 1) {
    // One or both of the dates is actually not a date
    return xa.length - ya.length;
  }

  if (xa[2] != ya[2]) {
    // years are different
    return Number(xa[2]) - Number(ya[2]);
  }

  if (xa[1] != ya[1]) {
    // months are different
    var xMonth = SortTable.convertMonthToNumber(xa[1]);
    var yMonth = SortTable.convertMonthToNumber(ya[1]);
    return Number(xMonth) - Number(yMonth);
  }

  // At this point we know that both the year and month are the same
  // so compare the days. Stick with String comparison since day will
  // be padded with leading 0 if only a single digit.
  return SortTable.compareString(xa[0], ya[0]);
}

/**
 * Assumes that x and y are of the form dd-MMM-yyyy HH:mm zzz
 * (ex 05-Jun-2005 22:05 PDT).
 * - Except we ignore the timezone in sorting (i.e. we assume both
 * timezones are the same.)
 * Also has logic so that if no dashes are present (not a date), will
 * sort before all actual dates.
 */
SortTable.compareDateTime = function(x, y) {
  var xa = x.split(' ');
  var ya = y.split(' ');
  
  if (xa.length == 1 || ya.length == 1) {
    // One or both of the date/times is actually not a date/time
    return xa.length - ya.length;
  }
  
  // Compare the date portion and if the dates are different then
  // we don't need to look at the time.
  var d = SortTable.compareDate(xa[0], ya[0]);
  if (d != 0)  return d;
  
  xa = xa[1].split(':');
  ya = ya[1].split(':');
  
  if (xa[0] != ya[0]) {
    // hours are different
    // Use string comparison since the hour will be padded with
    // leading 0 if only a single digit.
    return SortTable.compareString(xa[0], ya[0]);
  }
  
  // At this point we know everything is the same except maybe
  // the minutes. Use string compare again since the minute will
  // be 0-padded.
  return SortTable.compareString(xa[1], ya[1]);
}

SortTable.convertMonthToNumber = function(month) {
  var m = month.toLowerCase();
  if (m == "jan")  return 1;
  if (m == "feb")  return 2;
  if (m == "mar")  return 3;
  if (m == "apr")  return 4;
  if (m == "may")  return 5;
  if (m == "jun")  return 6;
  if (m == "jul")  return 7;
  if (m == "aug")  return 8;
  if (m == "sep")  return 9;
  if (m == "oct")  return 10;
  if (m == "nov")  return 11;
  if (m == "dec")  return 12;
}

function ComparePair(name, func) {
  this.name = name;
  this.func = func;
}

ComparePair.prototype.getName = function() {
  return this.name;
}

ComparePair.prototype.getFunction = function() {
  return this.func;
}

function CellCopy(innerHTML, innerText) {
  this.innerHTML = innerHTML;
  this.innerText = innerText;
}

CellCopy.prototype.getInnerHTML = function() {
  return this.innerHTML;
}

CellCopy.prototype.getInnerText = function() {
  return this.innerText;
}

