/**
 * 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 = 'Click to sort';
            // add class - pjcj
            linkEl.className = 'sortheader';

            // 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);

            // Don't add space for arrow until we sort - pjcj
            // 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;

            // alert("sorting on " + column);

            var newRows = new Array();

            for (var j = 0; j < table.tBodies[0].rows.length; j++) {
                newRows[j] = table.tBodies[0].rows[j];
                // alert("element " + j + " is " + that.getInnerText(newRows[j].cells[that.sortColumnIndex]));
            }

            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
            // alert("node " + i + " is [" + cs[i].nodeType + "] [" + cs[i].nodeValue + "] [" + cs[i].childNodes.length + "]");
            if (cs[i].childNodes.length)
            {
                str += this.getInnerText(cs[i]);
            }
            else if (1 == cs[i].nodeType) { // ELEMENT NODE
                str += this.getInnerText(cs[i]);
            } else if (3 == cs[i].nodeType) { //TEXT_NODE
                str += cs[i].nodeValue;
            }
        }

        // 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;

        /*

        Only need the modified numeric column

        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;
        }
        */

        // alert("sorting on [" + itm + "]");
        if (itm.match(/\d+\.\d+/) || itm == "n/a") {
            sortfn = this.sortNumeric;
        }

        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 = that.getInnerText(a.cells[that.sortColumnIndex]) == 'n/a'
            ? -1
            : parseFloat(that.getInnerText(a.cells[that.sortColumnIndex]));
        if (isNaN(aa))
            aa = 0;

        var bb = that.getInnerText(b.cells[that.sortColumnIndex]) == 'n/a'
            ? -1
            : 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)
