/*
 * SelSo 1.0 - Client-side selection sorter
 * Version 1.0
 * 
 * Copyright (c) 2007 Guillaume Andrieu
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 * 
 */

/**
 *
 * @description Takes a selection, for example some li elements in a ul, 
 * and sorts them according to the value of some classed element 
 * they all share. The elements of the selection must have an id attribute.
 *
 * @example $('li').selso({type:'num' , orderBy:'span.value'});
 * @desc Will sort all the li's elements in their respective ul, according to 
 * the numerical value in the span.value contained in the li.
 *
 * @example $('li').selso({type:'num' , orderBy:'span.value',direction:'desc'});
 * @desc Will sort all the li's elements in their respective ul, according to 
 * the numerical value in the span.value contained in the li, in descending order.
 *
 * @example $('li').selso({type:'alpha' , extract:function(obj){ return $(obj).attr('id'); }});
 * @desc Will sort all the li's elements in their respective ul, according to 
 * the result of the extract function, here giving the id attribute of each li.
 *
 * 
 * @example $('#table').tablesorter({ headers: { 0: { sorter: false}, 1: {sorter: false} } });
 * @desc Create a tablesorter interface and disableing the first and secound column headers.
 * 
 * @example $('#table').tablesorter({ 0: {sorter:"integer"}, 1: {sorter:"currency"} });
 * @desc Create a tablesorter interface and set a column parser for the first and secound column.
 * 
 * 
 * @param Object settings An object literal containing key/value pairs to provide optional settings.
 * 
 * @option String type (optional) 			A String giving the type of ordering : for the moment, only 'alpha' or 'num' 
 * 												Default value: "alpha"
 * 
 * @option String orderBy (optional) 			A String representing a jQuery selector. This selector will be applied inside each selected element. 
 * 												Default value: "span.value"
 * 
 * @option String direction (optional) 			A string to know in which order to sort : 'asc'ending or descending. 
 * 												'asc' means ascending, anything else means descending
 * 												Default value: "asc"
 * 
 * @option Function extract (optional) 	A Function taking an element, and sending back the value given to that object in the ordering process.
 * 												Default value: none
 * 
 * @option Function orderFn (optional) 	A function that will be given as the parameter of the .sort() function. 
 * 												Its parameters are two objects of type {id:'...',val:'...'}, and returns a number, 
 * 												positive if the first object is greater, 
 * 												negative is the second is greater, 
 * 												0 otherwise.
 * 												Default value: none
 * @type jQuery
 *
 * @name selso
 * 
 * @cat Plugins/Selso
 * 
 * @author Guillaume Andrieu/subtenante@yahoo.fr
 */

(function ($) {

    $.extend({
        selso: {
            defaults: {
                type: 'alpha', // type of sorting : alpha, num, date, ip, ...
                orderBy: 'span.value', // selector of the elements containing the value to order by
                direction: 'asc'
            },
            extractVal: function (type, text) {
                if (type == 'num') {
                    return 1 * text;
                }
                return text;
            },
            accentsTidy: function (s) {
                var r = s.toLowerCase();
                r = r.replace(new RegExp(/\s/g), "");
                r = r.replace(new RegExp(/[àáâãäå]/g), "a");
                r = r.replace(new RegExp(/æ/g), "ae");
                r = r.replace(new RegExp(/ç/g), "c");
                r = r.replace(new RegExp(/[èéêë]/g), "e");
                r = r.replace(new RegExp(/[ìíîï]/g), "i");
                r = r.replace(new RegExp(/ñ/g), "n");
                r = r.replace(new RegExp(/[òóôõö]/g), "o");
                r = r.replace(new RegExp(/œ/g), "oe");
                r = r.replace(new RegExp(/[ùúûü]/g), "u");
                r = r.replace(new RegExp(/[ýÿ]/g), "y");
                r = r.replace(new RegExp(/\W/g), "");
                return r;
            },
            orderAlpha: function (a, b) {
                var l = Math.min(a.length, b.length);
                for (var i = 0; i < l; i++) {
                    if (a.charAt(i) < b.charAt(i))
                        return -1;
                    else if (a.charAt(i) > b.charAt(i))
                        return 1;
                }
                if (a.length > b.length)
                    return 1;
                if (a.length < b.length)
                    return -1;
                return 0;
            },
            alphaGreaterThan: function (s1, s2) {
                return this.orderAlpha(s1, s2);
            },
            stablesort: function (a, of) {
                var r = a;
                var s;
                for (var i = 1; i < r.length; i++) {
                    var j = 1;
                    while (i >= j && of(r[i - j], r[i]) > 0) {
                        j++;
                    }
                    if (j > 0) {
                        s = r.slice(0, i - j + 1);
                        s.push(r[i]);
                        s = s.concat(r.slice(i - j + 1, i));
                        if (i < r.length - 1) {
                            s = s.concat(r.slice(i + 1));
                        }
                        r = s;
                    }
                }
                return r;
            }
        }
    });

    $.fn.extend({
        outhtml: function () {
            if (this.length)
                return $('<div>').append($(this[0]).clone()).html();
            return null;
        },
        prependToParent: function () {
            return this.each(function () {
                $(this).parent().prepend($(this).remove());
            });
        },
        selso: function (settings) {

            var tt = settings.type || $.selso.defaults.type;
            var to = settings.orderBy || $.selso.defaults.orderBy;
            var td = settings.direction || $.selso.defaults.direction;
            var te = settings.extract; // function that reads and parses the value in the selected element, if setted, orderBy will be ignored
            var of = settings.orderFn; // of : ordering function

            if (!$.isFunction(te)) {
                te = function (obj) {
                    return $.selso.extractVal(tt, $(to, obj).text());
                };
            }

            var arr = [];
            this.each(function () {
                arr.unshift({
                    id: this.id,
                    val: te(this)
                });
            });

            // Setting the ordering function if not given in the settings
            if ($.isFunction(of)) {
            }
            else if (tt == 'num') {
                of = function (a, b) {
                    return a.val - b.val;
                };
            }
            else { // alpha by default...
                if (tt == 'accents') {
                    $.map(arr, function (n) {
                        n.val = $.selso.accentsTidy(n.val);
                    });
                }
                of = function (a, b) {
                    return $.selso.alphaGreaterThan(a.val, b.val);
                };
            }
            var off = of;
            if (td == 'asc') {
                off = function (a, b) {
                    return -1 * of(a, b);
                }
            }
            arr = $.selso.stablesort(arr, off);

            for (var i = 0; i < arr.length; i++) {
                $('#' + arr[i].id).prependToParent();
            }

            return this;

        },

        // sorts all the combo select boxes
        sortBoxes: function (setteings) {
            if (!setteings) {
                setteings = {
                    type: 'alpha',
                    extract: function (o) {
                        return $(o).text();
                    }
                };
            }
            $('option', this).selso(setteings).removeAttr('selected');
        }
    });

})(jQuery);

$(function () {
    $('#letters select option:empty').remove();
    $('select.left option').addClass('new');
    $('select.right option').addClass('old');
    
    // add/remove buttons for combo select boxes
    $('button.add').click(function () {
        var left = $(this).closest('fieldset').find('select.left option:selected');
        var right = $(this).closest('fieldset').find('select.right');
        right.append(left);
        $('select.left').sortBoxes({
            type:'num' ,
            extract: function (o) {
                return $(o).attr('data-number') || $(o).text();
            }
        }); //, select.right
        right.find('option:selected').removeAttr('selected');
        return false;
    });
    
    $('button.remove').click(function () {
        var left = $(this).closest('fieldset').find('select.left');
        var right = $(this).closest('fieldset').find('select.right option:selected');
        left.append(right);
        $('select.left').sortBoxes({
            type:'num' ,
            extract: function (o) {
                return $(o).attr('data-number') || $(o).text();
            }
        }); //, select.right
        right.find('option:selected').removeAttr('selected');
        return false;
    });

    // add_all/remove_all buttons for combo select boxes
    $('button.add.all').click(function () {
        var left = $(this).closest('fieldset').find('select.left option');
        var right = $(this).closest('fieldset').find('select.right');
        right.append(left);
        $('select.left').sortBoxes({
            type:'num' ,
            extract: function (o) {
                return $(o).attr('data-number') || $(o).text();
            }
        }); //, select.right
        right.find('option:selected').removeAttr('selected');
        return false;
    });
    
    $('button.remove.all').click(function () {
        var left = $(this).closest('fieldset').find('select.left');
        var right = $(this).closest('fieldset').find('select.right option');
        left.append(right);
        $('select.left').sortBoxes({
            type:'num' ,
            extract: function (o) {
                return $(o).attr('data-number') || $(o).text();
            }
        }); //, select.right
        right.find('option:selected').removeAttr('selected');
        return false;
    });
    $(document).on('dblclick', 'select.left option, select.right option', function(){
        var fromclass = $(this).closest('select').hasClass('left')? 'left': 'right';
        var toclass = fromclass == 'left'? 'right' : 'left';
        var o_from = $(this).closest('select.' + fromclass).find('option:selected, option:focus');
        var o_to = $(this).closest('fieldset').find('select.' + toclass);
        o_to.append(o_from);
        $('select.left').sortBoxes({
            type:'num' ,
            extract: function (o) {
                return $(o).attr('data-number') || $(o).text();
            }
        }); //, select.right
        $('select.right').find('option:selected').removeAttr('selected');
    });
});