//Original from http://www.kryogenix.org/code/browser/sorttable/, distributed under MIT license
// Significant changes by:
// Made use of the prototype.js library.
// Converted to an OO style like prototype.js.
// Allowed asscending/descending text/image to be specified.
// Allowed alternating row classes to be specified (e.g. to cause alternating row background colors).
// Blur the link after sorting is complete.
// A column will only sort descending if clicked twice in a row (mimics Windws Explorer behavior).
// Currency columns will sort correctly if blank (just like numeric).
// An initial column to sort on can be specified.
// Headers and footers are now specified using standard HTML elements THEAD and TFOOT
// Supports heterogeneous column types (numbers always sort above text)
// Recognizes negative numbers
// Stricter matching of numeric types (e.g. 4.5.6 will not match as a number)
// Uses "NaturalOrder" algorithm to sort strings

TableSort = Class.create();

TableSort.prototype = {
  initialize: function(table, options) {
    this.setOptions(options);
    this._makeSortable($(table));
  },

  setOptions: function(options) {
    this.options = {
      ascString:       '<div style="position:relative;top:-10px;right:15%;text-align:right">&#x25B2;</div>',
      descString:      '<div style="position:relative;top:-10px;right:15%;text-align:right">&#x25BC;</div>',
      rowClasses:      {},
      initialColNum:   0,    // set to -1 to leave table sorted as the server sent it
      useNaturalSort:  true,
      selectedColumnClass: 'selected_col'
    }
    Object.extend(this.options, options || {});
  },

  _makeSortable: function(table) {
      if (table.tHead && table.tHead.rows.length > 0) {
          var firstRow = table.tHead.rows[0];
      }
      if (!firstRow) return;
      var initialSortCol;
      // We have a first row: assume it's the header, and make its contents clickable links
      for (var i=0;i<firstRow.cells.length;i++) {
          var cell = firstRow.cells[i];
          if (this.options.initialColNum == i) {
            initialSortCol = cell;
          }
          //var txt = TableSort.Helper.getInnerText(cell);
          //cell.innerHTML = '';
          //var lnk = document.createElement('a');
          //lnk.setAttribute('href', '#');
          //lnk.setAttribute('title', 'Click to sort table on this column');
          //lnk.innerHTML = txt;
          //cell.appendChild(lnk);
          var span = document.createElement('span');
          Element.addClassName(span, 'sortarrow');
          span.innerHTML = this.options.ascString;
          cell.appendChild(span);
          TableSort.Helper.hideElement(span);
          cell.style.cursor = "pointer";

          Event.observe(cell,'click',this._handleSortEvent.bind(this));
      }

      if (initialSortCol) {
        this.resortTable(initialSortCol);
      }
  },

  _handleSortEvent: function(event) {
     //alert("beep");
     var th = Event.findElement(event, 'th');
     if (!th) {
       th = Event.findElement(event, 'td');
     }
     this.resortTable(th);
     //lnk.blur();
  },


  resortTable: function(td) {
      // get the span
      var span;

      var childSpans = td.getElementsByTagName("span");
      for (var ci=0;ci<childSpans.length;ci++) {
        if (Element.hasClassName(childSpans[ci], 'sortarrow')) {
          span = childSpans[ci];
        }
      }

      var table = TableSort.Helper.getParent(td,'table');

      if (this.options.selectedColumnClass) {
        var tr = TableSort.Helper.getParent(td, 'tr');
        for (var i=0; i< tr.childNodes.length; i++) {
          if (tr.childNodes[i].className) {
            Element.removeClassName(tr.childNodes[i], this.options.selectedColumnClass);
          }
        }
      }
      
      if (table.tBodies.length <= 0) return;

      // this looks redundant, but it should work around a problem with hiding columns
      for (var i=0; i<table.rows[0].cells.length; i++) {
        if (table.rows[0].cells[i].cellIndex == td.cellIndex) {
          this.sortColumnIndex = i;
        }
      }
      var newRows = new Array();
      for (var i=0;i<table.tBodies.length;i++) {
        var thisBody = table.tBodies[i];
        for (j=0;j<thisBody.rows.length;j++) {
          newRows.push(thisBody.rows[j]);
        }
      }
  
      var sortfn = this.compareRows.bind(this);
      newRows.sort(sortfn);
      
      var newDirection;
      var arrow = '';
      if (span.getAttribute("sortdir") == 'asc') {
        arrow = this.options.descString;
        newDirection = 'desc';
        newRows = newRows.reverse();
      } else {
        arrow = this.options.ascString;
        newDirection = 'asc';
      }
  
      var rowClasses = this.options.rowClasses || [];
      // We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
      for (i=0;i<newRows.length;i++) { 
        for (j=0;j<rowClasses.length;j++) {
           Element.removeClassName(newRows[i], rowClasses[j]);
        }
        if (rowClasses.length > 0) {
          Element.addClassName(newRows[i], rowClasses[i % rowClasses.length]);
        }
        table.tBodies[0].appendChild(newRows[i]);
      }
  
      // Hide any other arrows that may be showing
      var allspans = table.tHead.getElementsByTagName("span");
      for (var ci=0;ci<allspans.length;ci++) {
        if (Element.hasClassName(allspans[ci], 'sortarrow')) {
          TableSort.Helper.hideElement(allspans[ci]);
          allspans[ci].setAttribute('sortdir', '');
        }
      }
          
      span.innerHTML = arrow;
      TableSort.Helper.showElement(span);
      span.setAttribute('sortdir', newDirection);
      if (this.options.selectedColumnClass) {
        Element.addClassName(td, this.options.selectedColumnClass);
      }
      
  },

  compareRows: function(a,b) {
    var aStr = TableSort.Helper.getInnerText(a.cells[this.sortColumnIndex]);
    aStr = aStr.replace(/\s+/g, "");
    var bStr = TableSort.Helper.getInnerText(b.cells[this.sortColumnIndex]);
    bStr = bStr.replace(/\s+/g, "");
    return TableSort.Helper.sortVarious(aStr, bStr, this.options.useNaturalSort);
  }


};

TableSort.Helper = {
  sortVarious: function(a,b, useNatural) {
    var a_type = TableSort.Helper.getDataType(a);
    var b_type = TableSort.Helper.getDataType(b);
    if (a_type != b_type) {
      return (a_type < b_type) ? -1 : 1;
    } else {
      switch(a_type) {
        case "a_date":
          return TableSort.Helper.compareDates(a,b);
        case "b_numeric":
          return TableSort.Helper.compareNumeric(a,b);
        case "c_currency":
          return TableSort.Helper.compareCurrency(a,b);
        case "d_text":
          if (useNatural) {
            tmp_result = NaturalOrder.compare(a,b);
            // there is a note about doing an additional check when NaturalOrder
            // returns zero in the comments for the NaturalOrder algorithm
            if (tmp_result == 0) {
              return TableSort.Helper.compareText(a,b);
            } else {
              return tmp_result;
            }
          } else {
            return TableSort.Helper.compareText(a,b);
          }
      }
    }
    alert("Got an unknown type");
  },
  
  getDataType: function(a) {
    if (a.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/)) {
      type = "a_date";
    } else if (a.match(/^\d\d[\/-]\d\d[\/-]\d\d$/)) {
      type = "a_date";
    } else if (a.match(/^[£$]/)) {
      type = "c_currency";
    } else if (a.match(/^-?(\d+\.?\d*|\.\d+)$/)) {
      type = "b_numeric";
    } else {
      type = "d_text";
    }
    return type;
  },

  getInnerText: function(el) {
      if (typeof el == "string") return el;
      if (typeof el == "undefined") { return el };
      if (el.innerText) return el.innerText;    //Not needed but it is faster
      var str = "";
      
      var cs = el.childNodes;
      var l = cs.length;
      for (var i = 0; i < l; i++) {
          switch (cs[i].nodeType) {
              case 1: //ELEMENT_NODE
                  str += TableSort.Helper.getInnerText(cs[i]);
                  break;
              case 3:   //TEXT_NODE
                  str += cs[i].nodeValue;
                  break;
          }
      }
      return str;
  },

  getParent: function(el, pTagName) {
      if (el == null) {
        return null;
      } else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase()) { // Gecko bug, supposed to be uppercase
        return el;
      } else {
        return TableSort.Helper.getParent(el.parentNode, pTagName);
      }
  },

  hideElement: function(element) {
     $(element).style.visibility = 'hidden';
  },

  showElement: function(element) {
     $(element).style.visibility = 'visible';
  },
  
  compareDates: function(aa,bb) {
      // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
      if (aa.length == 10) {
          dt1 = aa.substr(6,4)+aa.substr(3,2)+aa.substr(0,2);
      } else {
          yr = aa.substr(6,2);
          if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
          dt1 = yr+aa.substr(3,2)+aa.substr(0,2);
      }
      if (bb.length == 10) {
          dt2 = bb.substr(6,4)+bb.substr(3,2)+bb.substr(0,2);
      } else {
          yr = bb.substr(6,2);
          if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
          dt2 = yr+bb.substr(3,2)+bb.substr(0,2);
      }
      if (dt1==dt2) return 0;
      if (dt1<dt2) return -1;
      return 1;
  },
  
  compareCurrency: function(a,b) { 
      var aa = a.replace(/[^0-9.]/g,'');
      var bb = b.replace(/[^0-9.]/g,'');
      return TableSort.Helper.compareNumeric(aa,bb);
  },
  
  compareNumeric: function(a,b) { 
      var aa = parseFloat(a);
      if (isNaN(aa)) aa = 0;
      var bb = parseFloat(b); 
      if (isNaN(bb)) bb = 0;
      return aa-bb;
  },
  
  compareText: function(a,b) {
      var aa = a.toLowerCase();
      var bb = b.toLowerCase();
      if (aa==bb) return 0;
      return (aa < bb) ? -1 : 1;
  }
  

}

