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