/*
* jQuery TinySort - A plugin to sort child nodes by (sub) contents or attributes.
*
* Version: 1.0.4
*
* Copyright (c) 2008 Ron Valstar
*
* Dual licensed under the MIT and GPL licenses:
*   http://www.opensource.org/licenses/mit-license.php
*   http://www.gnu.org/licenses/gpl.html
*
* description
*   - A plugin to sort child nodes by (sub) contents or attributes.
*
* Usage:
*   $("ul#people>li").tsort();
*   $("ul#people>li").tsort("span.surname");
*   $("ul#people>li").tsort("span.surname",{order:"desc"});
*   $("ul#people>li").tsort({place:"end"});
*
* Change default like so:
*   $.tinysort.defaults.order = "desc";
*
* in this update:
*       - changed setArray to pushStack
*
* in last update:
*       - tested with jQuery 1.4.1
*       - correct isNum return
*
* Todos
*   - fix mixed literal/numeral values
*   - determine if I have to use pushStack or pushStack
*
*/
;(function($) {
        // default settings
        $.tinysort = {
                 id: "TinySort"
                ,version: "1.0.4"
                ,defaults: {
                         order: "asc"   // order: asc, desc or rand
                        ,attr: ""               // order by attribute value
                        ,place: "start" // place ordered elements at position: start, end, org (original position), first
                        ,returns: false // return all elements or only the sorted ones (true/false)
                }
        };
        $.fn.extend({
                tinysort: function(_find,_settings) {
                        if (_find&&typeof(_find)!="string") {
                                _settings = _find;
                                _find = null;
                        }

                        var oSettings = $.extend({}, $.tinysort.defaults, _settings);

                        var oElements = {}; // contains sortable- and non-sortable list per parent
                        this.each(function(i) {
                                // element or sub selection
                                var mElm = (!_find||_find=="")?$(this):$(this).find(_find);
                                // text or attribute value
                                var sSort = oSettings.order=="rand"?""+Math.random():(oSettings.attr==""?mElm.text():mElm.attr(oSettings.attr));
                                // to sort or not to sort
                                var mParent = $(this).parent();
                                if (!oElements[mParent]) oElements[mParent] = {s:[],n:[]};      // s: sort, n: not sort
                                if (mElm.length>0)      oElements[mParent].s.push({s:sSort,e:$(this),n:i}); // s:string, e:element, n:number
                                else                            oElements[mParent].n.push({e:$(this),n:i});
                        });
                        //
                        // sort
                        for (var sParent in oElements) {
                                var oParent = oElements[sParent];
                                oParent.s.sort(
                                        function zeSort(a,b) {
                                                var x = a.s.toLowerCase?a.s.toLowerCase():a.s;
                                                var y = b.s.toLowerCase?b.s.toLowerCase():b.s;
                                                if (isNum(a.s)&&isNum(b.s)) {
                                                        x = parseFloat(a.s);
                                                        y = parseFloat(b.s);
                                                }
                                                return (oSettings.order=="asc"?1:-1)*(x<y?-1:(x>y?1:0));
                                        }
                                );
                        }
                        //
                        // order elements and fill new order
                        var aNewOrder = [];
                        for (var sParent in oElements) {
                                var oParent = oElements[sParent];
                                var aOrg = []; // list for original position
                                var iLow = $(this).length;
                                switch (oSettings.place) {
                                        case "first": $.each(oParent.s,function(i,obj) { iLow = Math.min(iLow,obj.n) }); break;
                                        case "org": $.each(oParent.s,function(i,obj) { aOrg.push(obj.n) }); break;
                                        case "end": iLow = oParent.n.length; break;
                                        default: iLow = 0;
                                }
                                var aCnt = [0,0]; // count how much we've sorted for retreival from either the sort list or the non-sort list (oParent.s/oParent.n)
                                for (var i=0;i<$(this).length;i++) {
                                        var bSList = i>=iLow&&i<iLow+oParent.s.length;
                                        if (contains(aOrg,i)) bSList = true;
                                        var mEl = (bSList?oParent.s:oParent.n)[aCnt[bSList?0:1]].e;
                                        mEl.parent().append(mEl);
                                        if (bSList||!oSettings.returns) aNewOrder.push(mEl.get(0));
                                        aCnt[bSList?0:1]++;
                                }
                        }
                        //
                        return this.pushStack(aNewOrder); // pushStack or pushStack?
                }
        });
        // is numeric
        function isNum(n) {
                var x = /^\s*?[\+-]?(\d*\.?\d*?)\s*?$/.exec(n);
                return x&&x.length>0?x[1]:false;
        };
        // array contains
        function contains(a,n) {
                var bInside = false;
                $.each(a,function(i,m) {
                        if (!bInside) bInside = m==n;
                });
                return bInside;
        };
        // set functions
        $.fn.TinySort = $.fn.Tinysort = $.fn.tsort = $.fn.tinysort;
})(jQuery);