/**
 * Written by Neil Crosby. 
 * http://www.workingwith.me.uk/articles/scripting/standardista_table_sorting
 *
 * This module is based on Stuart Langridge's "sorttable" code.  Specifically, 
 * the determineSortFunction, sortCaseInsensitive, sortDate, sortNumeric, and
 * sortCurrency functions are heavily based on his code.  This module would not
 * have been possible without Stuart's earlier outstanding work.
 *
 * Use this wherever you want, but please keep this comment at the top of this file.
 *
 * Copyright (c) 2006 Neil Crosby
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal 
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 * SOFTWARE.
 **/
var standardistaTableSorting = {

        that: false,
        isOdd: false,

        sortColumnIndex : -1,
        lastAssignedId : 0,
        newRows: -1,
        lastSortedTable: -1,

        /**
         * Initialises the Standardista Table Sorting module
         **/
        init : function() {
                // first, check whether this web browser is capable of running this script
                if (!document.getElementsByTagName) {
                        return;
                }
                
                this.that = this;
                
                this.run();
                
        },
        
        /**
         * Runs over each table in the document, making it sortable if it has a class
         * assigned named "sortable" and an id assigned.
         **/
        run : function() {
                var tables = document.getElementsByTagName("table");
                
                for (var i=0; i < tables.length; i++) {
                        var thisTable = tables[i];
                        
                        if (css.elementHasClass(thisTable, 'sortable')) {
                                this.makeSortable(thisTable);
                        }
                }
        },
        
        /**
         * Makes the given table sortable.
         **/
        makeSortable : function(table) {
        
                // first, check if the table has an id.  if it doesn't, give it one
                if (!table.id) {
                        table.id = 'sortableTable'+this.lastAssignedId++;
                }
                
                // if this table does not have a thead, we don't want to know about it
                if (!table.tHead || !table.tHead.rows || 0 == table.tHead.rows.length) {
                        return;
                }
                
                // we'll assume that the last row of headings in the thead is the row that 
                // wants to become clickable
                var row = table.tHead.rows[table.tHead.rows.length - 1];
                
                for (var i=0; i < row.cells.length; i++) {
                
                        // create a link with an onClick event which will 
                        // control the sorting of the table
                        var linkEl = createElement('a');
                        linkEl.href = '#';
                        linkEl.onclick = this.headingClicked;
                        linkEl.setAttribute('columnId', i);
                        linkEl.title = 'für Sortierung klicken';
                        
                        // move the current contents of the cell that we're 
                        // hyperlinking into the hyperlink
                        var innerEls = row.cells[i].childNodes;
                        for (var j = 0; j < innerEls.length; j++) {
                                linkEl.appendChild(innerEls[j]);
                        }
                        
                        // and finally add the new link back into the cell
                        row.cells[i].appendChild(linkEl);

                        var spanEl = createElement('span');
                        spanEl.className = 'tableSortArrow';
                        spanEl.appendChild(document.createTextNode('\u00A0\u00A0'));
                        row.cells[i].appendChild(spanEl);

                }
        
                if (css.elementHasClass(table, 'autostripe')) {
                        this.isOdd = false;
                        var rows = table.tBodies[0].rows;
                
                        // We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
                        for (var i=0;i<rows.length;i++) { 
                                this.doStripe(rows[i]);
                        }
                }
        },
        
        headingClicked: function(e) {
                
                var that = standardistaTableSorting.that;
                
                // linkEl is the hyperlink that was clicked on which caused
                // this method to be called
                var linkEl = getEventTarget(e);
                
                // directly outside it is a td, tr, thead and table
                var td     = linkEl.parentNode;
                var tr     = td.parentNode;
                var thead  = tr.parentNode;
                var table  = thead.parentNode;
                
                // if the table we're looking at doesn't have any rows
                // (or only has one) then there's no point trying to sort it
                if (!table.tBodies || table.tBodies[0].rows.length <= 1) {
                        return false;
                }

                // the column we want is indicated by td.cellIndex
                var column = linkEl.getAttribute('columnId') || td.cellIndex;
                //var column = td.cellIndex;
                
                // find out what the current sort order of this column is
                var arrows = css.getElementsByClass(td, 'tableSortArrow', 'span');
                var previousSortOrder = '';
                if (arrows.length > 0) {
                        previousSortOrder = arrows[0].getAttribute('sortOrder');
                }
                
                // work out how we want to sort this column using the data in the first cell
                // but just getting the first cell is no good if it contains no data
                // so if the first cell just contains white space then we need to track
                // down until we find a cell which does contain some actual data
                var itm = ''
                var rowNum = 0;
                while ('' == itm && rowNum < table.tBodies[0].rows.length) {
                        itm = that.getInnerText(table.tBodies[0].rows[rowNum].cells[column]);
                        rowNum++;
                }
                var sortfn = that.determineSortFunction(itm);

                // if the last column that was sorted was this one, then all we need to 
                // do is reverse the sorting on this column
                if (table.id == that.lastSortedTable && column == that.sortColumnIndex) {
                        newRows = that.newRows;
                        newRows.reverse();
                // otherwise, we have to do the full sort
                } else {
                        that.sortColumnIndex = column;
                        var newRows = new Array();

                        for (var j = 0; j < table.tBodies[0].rows.length; j++) { 
                                newRows[j] = table.tBodies[0].rows[j]; 
                        }

                        newRows.sort(sortfn);
                }

                that.moveRows(table, newRows);
                that.newRows = newRows;
                that.lastSortedTable = table.id;
                
                // now, give the user some feedback about which way the column is sorted
                
                // first, get rid of any arrows in any heading cells
                var arrows = css.getElementsByClass(tr, 'tableSortArrow', 'span');
                for (var j = 0; j < arrows.length; j++) {
                        var arrowParent = arrows[j].parentNode;
                        arrowParent.removeChild(arrows[j]);

                        if (arrowParent != td) {
                                spanEl = createElement('span');
                                spanEl.className = 'tableSortArrow';
                                spanEl.appendChild(document.createTextNode('\u00A0\u00A0'));
                                arrowParent.appendChild(spanEl);
                        }
                }
                
                // now, add back in some feedback 
                var spanEl = createElement('span');
                spanEl.className = 'tableSortArrow';
                if (null == previousSortOrder || '' == previousSortOrder || 'DESC' == previousSortOrder) {
                        spanEl.appendChild(document.createTextNode(' \u2191'));
                        spanEl.setAttribute('sortOrder', 'ASC');
                } else {
                        spanEl.appendChild(document.createTextNode(' \u2193'));
                        spanEl.setAttribute('sortOrder', 'DESC');
                }
                
                td.appendChild(spanEl);
                
                return false;
        },

        getInnerText : function(el) {
                
                if ('string' == typeof el || 'undefined' == typeof el) {
                        return el;
                }
                
                if (el.innerText) {
                        return el.innerText;  // Not needed but it is faster
                }

                var str = el.getAttribute('standardistaTableSortingInnerText');
                if (null != str && '' != str) {
                        return str;
                }
                str = '';

                var cs = el.childNodes;
                var l = cs.length;
                for (var i = 0; i < l; i++) {
                        // 'if' is considerably quicker than a 'switch' statement, 
                        // in Internet Explorer which translates up to a good time 
                        // reduction since this is a very often called recursive function
                        if (1 == cs[i].nodeType) { // ELEMENT NODE
                                str += this.getInnerText(cs[i]);
                                break;
                        } else if (3 == cs[i].nodeType) { //TEXT_NODE
                                str += cs[i].nodeValue;
                                break;
                        }
                }
                
                // set the innertext for this element directly on the element
                // so that it can be retrieved early next time the innertext
                // is requested
                el.setAttribute('standardistaTableSortingInnerText', str);
                
                return str;
        },

        determineSortFunction : function(itm) {
                
                var sortfn = this.sortCaseInsensitive;
                
                if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/)) {
                        sortfn = this.sortDate;
                }
                if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/)) {
                        sortfn = this.sortDate;
                }
                if (itm.match(/^[£$]/)) {
                        sortfn = this.sortCurrency;
                }
                if (itm.match(/^\d?\.?\d+$/)) {
                        sortfn = this.sortNumeric;
                }
                if (itm.match(/^[+-]?\d*\.?\d+([eE]-?\d+)?$/)) {
                        sortfn = this.sortNumeric;
                }
                    if (itm.match(/^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$/)) {
                        sortfn = this.sortIP;
                   }

                return sortfn;
        },
        
        sortCaseInsensitive : function(a, b) {
                var that = standardistaTableSorting.that;
                
                var aa = that.getInnerText(a.cells[that.sortColumnIndex]).toLowerCase();
                var bb = that.getInnerText(b.cells[that.sortColumnIndex]).toLowerCase();
                if (aa==bb) {
                        return 0;
                } else if (aa<bb) {
                        return -1;
                } else {
                        return 1;
                }
        },
        
        sortDate : function(a,b) {
                var that = standardistaTableSorting.that;

                // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
                var aa = that.getInnerText(a.cells[that.sortColumnIndex]);
                var bb = that.getInnerText(b.cells[that.sortColumnIndex]);
                
                var dt1, dt2, yr = -1;
                
                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;
                } else if (dt1<dt2) {
                        return -1;
                }
                return 1;
        },

        sortCurrency : function(a,b) { 
                var that = standardistaTableSorting.that;

                var aa = that.getInnerText(a.cells[that.sortColumnIndex]).replace(/[^0-9.]/g,'');
                var bb = that.getInnerText(b.cells[that.sortColumnIndex]).replace(/[^0-9.]/g,'');
                return parseFloat(aa) - parseFloat(bb);
        },

        sortNumeric : function(a,b) { 
                var that = standardistaTableSorting.that;

                var aa = parseFloat(that.getInnerText(a.cells[that.sortColumnIndex]));
                if (isNaN(aa)) { 
                        aa = 0;
                }
                var bb = parseFloat(that.getInnerText(b.cells[that.sortColumnIndex])); 
                if (isNaN(bb)) { 
                        bb = 0;
                }
                return aa-bb;
        },

        makeStandardIPAddress : function(val) {
                var vals = val.split('.');

                for (x in vals) {
                        val = vals[x];

                        while (3 > val.length) {
                                val = '0'+val;
                        }
                        vals[x] = val;
                }

                val = vals.join('.');

                return val;
        },

        sortIP : function(a,b) { 
                var that = standardistaTableSorting.that;

                var aa = that.makeStandardIPAddress(that.getInnerText(a.cells[that.sortColumnIndex]).toLowerCase());
                var bb = that.makeStandardIPAddress(that.getInnerText(b.cells[that.sortColumnIndex]).toLowerCase());
                if (aa==bb) {
                        return 0;
                } else if (aa<bb) {
                        return -1;
                } else {
                        return 1;
                }
        },

        moveRows : function(table, newRows) {
                this.isOdd = false;

                // We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
                for (var i=0;i<newRows.length;i++) { 
                        var rowItem = newRows[i];

                        this.doStripe(rowItem);

                        table.tBodies[0].appendChild(rowItem); 
                }
        },
        
        doStripe : function(rowItem) {
                if (this.isOdd) {
                        css.addClassToElement(rowItem, 'odd');
                } else {
                        css.removeClassFromElement(rowItem, 'odd');
                }
                
                this.isOdd = !this.isOdd;
        }

}

function standardistaTableSortingInit() {
        standardistaTableSorting.init();
}

addEvent(window, 'load', standardistaTableSortingInit)
