// -*- c-basic-offset:3; -*-

function doContFn(contFn) {
   if(contFn) {
      window.setTimeout(contFn,0);
   }
}

var ContainerSorter = function(domContainer, valueFn ) {
   this.container = domContainer;
   this.valueFn = valueFn;
};

//maximum number of items that should be sorted in place without
//removing the container from the dom first and readding.
ContainerSorter.prototype.maxInPlaceRows = 32;
ContainerSorter.prototype.pauseAfterRows = 16;

ContainerSorter.prototype.skipFirstRow = true;
ContainerSorter.prototype._itemsFn = function(arr) {
   if (this.skipFirstRow ) {
      if(arr.length < 2)
	 arr = [];
      else {
	 var sliced = new Array(arr.length-1);
	 for(var i = 1; i< arr.length; i++ )
	    sliced[i-1] = arr[i];
	 arr= sliced;
      }
   }
   return arr;
};
ContainerSorter.prototype.itemsFn = function() {
   return this._itemsFn(this.container["childNodes"]);
};

//a function for comparing two values, the sign of this functions return
//value should give the appropriate order for the two objects.
//a couple of available options are:
// ContainerSorter.prototype.{sortDate | sortCaseInsensitive | sortNumeric }
ContainerSorter.prototype.sortFn = null;

//based on the items and valueFn, try to automatically determine a good sortFn
ContainerSorter.prototype.findSorter = function() {
   //find the first non-null item
   var items= this.itemsFn();
   var itm = null;
   for(var i = 0; i < items.length; i++){
      if( this.textFrom(this.valueFn(items[i]))) {
	 itm = this.textFrom(this.valueFn(items[i]));
	 break;
      }
   }

   ///based on that item, what type of sorter does it look like we should use.
   //determine what type of sort we are doing
   var sortfn = this.sortCaseInsensitive;
   var str = String(new Date(itm));
   var strNum = String(new Number(itm));
   if ( str != "Invalid Date" && str != "NaN") sortfn = this.sortDate;
   else if (itm.match(this.currencyRegex)) sortfn = this.sortCurrency;
   else if (!isNaN(new Number(itm))) sortfn = this.sortNumeric;
   return sortfn;
};

//This function is used to append to the end of the container in sorted order.
ContainerSorter.prototype.appendFn = function (element) {
   return this.container.appendChild(element);
};

ContainerSorter.prototype.logTimeSinceStart = function(msg) {
   var d2 = new Date();
   if(console)console.debug("time: ", (d2 - this.startTime), msg);
};


ContainerSorter.prototype.startSort = function(target) {
   //console.log("Here in startsort");
   this.startTime = new Date();
   //which direction are we sorting this time.
   this.sortOrder = (!this.sortOrder || this.sortOrder == "asc") ? "desc" : "asc";
   var newRows = this.mergeSort();
   this.logTimeSinceStart("merge fin.");

   var restoreFn;

   if(this.itemsFn().length > this.maxInPlaceRows) {
      this.originalScrollX = window.scrollX || window.pageXOffset || document.documentElement.scrollLeft || 0;
      this.originalScrollY = window.scrollY || window.pageYOffset || document.documentElement.scrollTop || 0;
      this.containerOriginalParent = this.container.parentNode;
      this.containerOriginalNextSibling = this.container.nextSibling;
      restoreFn = dojo.hitch(this,function () {
	    this.containerOriginalParent.insertBefore(this.container, this.containerOriginalNextSibling);
	    window.scrollTo(this.originalScrollX,this.originalScrollY);
	    this.logTimeSinceStart("Done restoring container.");
	 });

      this.container.parentNode.style.width = this.container.parentNode.clientWidth + "px";
      this.container.parentNode.style.height = this.container.parentNode.clientHeight + "px";
      this.container.parentNode.removeChild(this.container);
      this.logTimeSinceStart("Removed and stashed.");
   }

   doContFn(dojo.hitch(this, this.finishSort, newRows, restoreFn));
};

ContainerSorter.prototype.finishSort = function(newRows, contFn) {
   //rows for totals and such that should remain on the bottom
   var bottomRows = new Array();

   var styleIndex = 0;
   var addRows,addBottomRows;

   addRows = function(i,contFn) {
      //starting at whatever index we are given
      //append the elements in the correct order,
      //skipping over any rows marked for bottom.
      for (; i < newRows.length; i++) {
	 if (!newRows[i].className || newRows[i].className.indexOf('sortbottom') == -1){
	    if (this.rowStyleFn)
	       this.rowStyleFn(newRows[i], styleIndex++);

	    this.appendFn(newRows[i]);
	 }
	 else {
	    //we know that this must be a sort bottom row, so save it for appending later
	    bottomRows.push(newRows[i]);
	 }
	 if( (i % this.pauseAfterRows) == 0) {
	    //Every n rows, take a break to allow the browser to do other stuff
	    //have addRows continue at the next index
	    doContFn(dojo.hitch(this,addRows,i+1,contFn));
	    return;
	 }
      }
      doContFn(contFn);
   };
   addBottomRows = function(contFn) {
      // finally sortbottom rows only
      for (i = 0; i < bottomRows.length; i++) {
	 this.appendFn(bottomRows[i]);
	 if (this.rowStyleFn)
	    this.rowStyleFn(bottomRows[i], styleIndex++);
      }
      doContFn(contFn);
   };
   doContFn(dojo.hitch(this,addRows,0, //0 is starting idx for addRows
		       dojo.hitch(this,addBottomRows, contFn)));
};


/**** Comparison code and sortFn implementations ****/

ContainerSorter.prototype.textFrom = function(box) {
    var val = box.textContent || box.innerText;
    //console.log("textFrom:", box, val);
    return val ? dojo.trim(val) : "";
};

ContainerSorter.prototype.dateRegexp =  /^(\d{1,2})[\/-](\d{1,2})[\/-](\d{4})$/;
ContainerSorter.prototype.sortDate = function(aa, bb) {
   // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
   aa = new Date(this.textFrom(aa));
   bb = new Date(this.textFrom(bb));
   if(aa < bb)
      return -1;
   else if (aa > bb)
      return 1;
   else
      return 0;
};

ContainerSorter.prototype.currencyRegex = /^[£$]\s*-?\d/;
ContainerSorter.prototype.sortCurrency = function(aa, bb) {
   //we matched currency because it has a first character of $ (or similar)
   aa = this.textFrom(aa).substring(1);
   bb = this.textFrom(bb).substring(1);
   return parseFloat(aa) - parseFloat(bb);
};

ContainerSorter.prototype.sortNumeric = function(aa, bb) {
   aa=this.textFrom(aa);
   bb=this.textFrom(bb);
   if (isNaN(aa)) aa = 0;
   if (isNaN(bb)) bb = 0;
   return aa-bb;
};

ContainerSorter.prototype.sortNumericOrdinalAttr = function(aa, bb) {
   aa = Number(aa.getAttribute("ordinal"));
   bb = Number(bb.getAttribute("ordinal"));
   if (isNaN(aa)) aa = 0;
   if (isNaN(bb)) bb = 0;
   return aa-bb;
};

ContainerSorter.prototype.sortCaseInsensitive = function(aa, bb) {
   aa = this.textFrom(aa).toLowerCase();
   bb = this.textFrom(bb).toLowerCase();
   if (aa == bb) return 0;
   if (aa < bb) return -1;
   else return 1;
};

ContainerSorter.prototype.sortNumberOfChildren = function(aa, bb) {
   aa = aa.childNodes ? aa.childNodes.length : 0;
   bb = bb.childNodes ? bb.childNodes.length : 0;
   if (aa == bb) return 0;
   if (aa < bb) return -1;
   else return 1;
};

ContainerSorter.prototype.sortDefault = function(aa, bb) {
    if (aa == bb) return 0;
    if (aa < bb) return -1;
    return 1;
};

ContainerSorter.prototype.mergeSort = function() {
   //the lambda functions need a reference to the current 'this' (a containersorter)
   // because w
   var self =this;

   if (!this.sortFn)
      this.sortFn = this.findSorter();

   var array = this.itemsFn();
   //sorting nothing should return nothing.
   if(!array || array.length == 0) return [];

   //only do the string comparison once.
   var ascOrder = this.sortOrder == "asc";

   var merge = function(arr1, arr2) {
      //self is a ContainerSorter.
      var i=0, j1=0, j2=0;
      var result = new Array(arr1.length + arr2.length);
      var comparisonResult;
      for(i = 0; i < result.length; i++) {
	 if (j1 < arr1.length && j2 < arr2.length) {
	    comparisonResult = (ascOrder) ? self.sortFn(self.valueFn(arr1[j1]), self.valueFn(arr2[j2]))
	       : self.sortFn(self.valueFn(arr2[j2]), self.valueFn(arr1[j1]));
	    if(comparisonResult < 0)
	       result[i] = arr1[j1++];
	    else if (comparisonResult > 0)
	       result[i] = arr2[j2++];
	    else // if they were equal, we want to maintain left to right order.
	       result[i] = arr1[j1++];
	 }
	 else if (j1 < arr1.length)
	    result[i] = arr1[j1++];
	 else
	    result[i] = arr2[j2++];
      }
      return result;
   };

   var innerSort = function(low, high) {
      if( high - low < 1 )
	 return [ (array[low]) ];
      else {
	 var mid = low + Math.floor((high - low) / 2);
	 return merge(innerSort(low, mid), innerSort(mid+1, high));
      }
   };

   return innerSort(0, array.length-1);
};


/**** Specialized Column Sorters ****/

//If we are sorting a column in a table body and don't need to
//watch for header rows we can be a bit more efficient
var TBodyColumnSorter = function(tbody,valueFn, rowStyleFn) {
   this.container = tbody;
   this.valueFn = valueFn;
   this.rowStyleFn = rowStyleFn;
};

TBodyColumnSorter.prototype = ContainerSorter.prototype;
TBodyColumnSorter.prototype.itemsFn = function() {
   return this.container.rows;
};

/**** Used to create sorters for all columns of a table,
 * tbl: the table to sort-- a Dom table object, or an ID as string
 * rowStyleFn: fn to style the rows after reordering, nil to autogenerate, false to supress.
 */
var TableSorter = function(tbl, rowStyleFn) {
   //console.log("Creating table sorter for:"+tbl);
   if(typeof tbl == "string")
      tbl = dojo.byId(tbl);
   if(!tbl)
      return;
   this.tbl = tbl;

   if(!rowStyleFn && rowStyleFn != false)
      rowStyleFn = this.rowStyleFnMaker("altrow", "row");
   if(rowStyleFn instanceof Array && rowStyleFn.length > 0)
      rowStyleFn = this.rowStyleFnMaker.apply(this,rowStyleFn);

   var headerRow;
   var makerFun;
   var tbodyToSort = tbl.tBodies && tbl.tBodies[0] || tbl;
   if(tbl.tHead) {
      headerRow = tbl.tHead.rows[0];
      makerFun = function(valFn) {
	 return new TBodyColumnSorter(tbodyToSort, valFn, rowStyleFn);
      };
   }
   else {
      headerRow = tbl.rows[0];
      var itemsFn = function() {
	 return this._itemsFn(this.container.rows); //this will be a ContainerSorter when it gets called.
      };
      makerFun = function(valFn) {
         var cs;
	 cs = new ContainerSorter(tbodyToSort, valFn);
	 cs.itemsFn = itemsFn;
	 cs.rowStyleFn = rowStyleFn;
	 return cs;
      };
   }

   this.containerSorters = new Array(headerRow.cells.length);
   //console.log("About to add header event handlers:"+tbl);
   for (var i = headerRow.cells.length - 1 ; i >= 0; i--) {
      var sortfn= headerRow.cells[i].getAttribute('sortfn');
      if(sortfn == null || sortfn != "none") {
	 var cs = makerFun(this.valueFnMaker(i));
	 this.containerSorters[i] = cs;
	 headerRow.cells[i].style.cursor="pointer";
	 if(sortfn) cs.sortFn = ContainerSorter.prototype[sortfn];
	 dojo.connect(headerRow.cells[i], "onclick", cs, "startSort");
      }
   };
};
TableSorter.prototype.valueFnMaker = function(idx) {
   return function(tr) {
      return tr.cells[idx];
   };
};


//a function of n arguments that will return a function
//that when applied to an element and index, will apply the class
//at argument index % n to the element.
//For alternating styles: e.g. rowStyleFnMaker("row","altrow")
TableSorter.prototype.rowStyleFnMaker = function() {
   var count = arguments.length;
   if(count == 0)
      return null;

   //I don't know if the arguments array can be closed or not
   //so just copy it and keep a references to that.
   var arr= new Array(count);
   for(var i=0; i< count; i++)
      arr[i] = arguments[i];

   return function(element,index) {
      //remove any style classes
      var i;
      for(i=arr.length-1; i>=0; i--) {
	 if((index % arr.length) == i)
	    dojo.addClass(element, arr[i]);
	 else
	    dojo.removeClass(element, arr[i]);
      }
   };
};

/* MultiRow Sorters*/
var MultiRowTBodyColumnSorter = function(tbody, valueFn, rowStyleFn, rowMod) {
   this.container = tbody;
   this.valueFn = valueFn;
   this.rowStyleFn = rowStyleFn;
   this.rowMod = rowMod || 2;
};

MultiRowTBodyColumnSorter.prototype = new ContainerSorter();
MultiRowTBodyColumnSorter.prototype.itemsFn = function() {
  //this will be a ContainerSorter when it gets called.
  //console.log("ItemsFn: ", this, this.container, "Rows:", this.container.rows.length);
  var row, rowidx, items=[], currentItem=[];
  for( rowidx=0 ; row=this.container.rows[rowidx] ; rowidx++){
    if(rowidx>0 && (rowidx % this.rowMod)==0){
      items.push(currentItem);
      currentItem =[];
    }
    currentItem.push(row);
  }
  if(currentItem.length > 0) items.push(currentItem);
  return items;
};
MultiRowTBodyColumnSorter.prototype.appendFn = function (item) {
  //console.log("Appending: ",this);
  var i, d;
  for(i=0 ; d=item[i] ; i++) this.container.appendChild(d);
};

// Table sorter where we want to sort in blocks of N rows
var MultiRowTableSorter = function(tbl, n, rowStyleFn) {
   //window.multiRow = this;
   //console.log("Creating table sorter for:"+tbl);
   this.rowMod = n;
   if(typeof tbl == "string")
      tbl = dojo.byId(tbl);
   if(!tbl)
      return;
   this.tbl = tbl;

   if(!rowStyleFn && rowStyleFn != false)
      rowStyleFn = this.rowStyleFnMaker("altrow", "row");
   if(rowStyleFn instanceof Array && rowStyleFn.length > 0)
      rowStyleFn = this.rowStyleFnMaker.apply(this,rowStyleFn);

   var tbodyToSort = tbl.tBodies && tbl.tBodies[0] || tbl;
   if(!tbl.tHead) throw new Exception("Please use thead/tbody for multirow sorting");

   this.containerSorters = new Array();
   //console.log("About to add header event handlers:"+tbl);
   var row, rowIdx, cellIdx, cell, idx=0;
   for (rowIdx=0; row=tbl.tHead.rows[rowIdx]; rowIdx++){
     for (cellIdx = 0 ; cell = row.cells[cellIdx] ; cellIdx++, idx++) {
       var sortfn= cell.getAttribute('sortfn');
       if(sortfn != "none") {
	 var cs = new MultiRowTBodyColumnSorter(tbodyToSort, this.valueFnMaker(idx),
	   rowStyleFn, n);
	 this.containerSorters[idx] = cs;
	 cell.style.cursor="pointer";
	 if(sortfn) cs.sortFn = ContainerSorter.prototype[sortfn];
	 dojo.connect(cell, "onclick", cs, "startSort");
       }
     };
   };
};
MultiRowTableSorter.prototype = new TableSorter();
MultiRowTableSorter.prototype.valueFnMaker = function(idx) {
   return function(item) {
     var row, i, cnt=0;
     for(i=0 ; row=item[i] ; i++){
       if(idx >= row.cells.length+cnt) cnt+=row.cells.length;
       else return row.cells[idx-cnt];
     }
     return null;
   };
};