(function ($, w, undefined) {
    if (w.footable === undefined || w.footable === null)
        throw new Error('Please check and make sure footable.js is included in the page and is loaded prior to this script.');

    var defaults = {
        sort: true,
        sorters: {
            alpha: function (a, b) {
                if (a === b) return 0;
                if (a < b) return -1;
                return 1;
            },
            numeric: function (a, b) {
                return a - b;
            }
        },
        classes: {
            sort: {
                sortable: 'footable-sortable',
                sorted: 'footable-sorted',
                descending: 'footable-sorted-desc',
                indicator: 'footable-sort-indicator'
            }
        },
        events: {
            sort: {
                sorting: 'footable_sorting',
                sorted: 'footable_sorted'
            }
        }
    };

    function Sort() {
        var p = this;
        p.name = 'Footable Sortable';
        p.init = function (ft) {
            p.footable = ft;
            if (ft.options.sort === true) {
                $(ft.table)
                    .unbind('.sorting')
                    .bind({
                        'footable_initialized.sorting': function (e) {
                            var $table = $(ft.table),
                                $tbody = $table.find('> tbody'),
                                cls = ft.options.classes.sort,
                                column, $th;

                            if ($table.data('sort') === false) return;

                            $table.find('> thead > tr:last-child > th, > thead > tr:last-child > td').each(function (ec) {
                                $th = $(this), column = ft.columns[$th.index()];
                                if (column.sort.ignore !== true && !$th.hasClass(cls.sortable)) {
                                    $th.addClass(cls.sortable);
                                    $('<span />').addClass(cls.indicator).appendTo($th);
                                }
                            });

                            $table.find('> thead > tr:last-child > th.' + cls.sortable + ', > thead > tr:last-child > td.' + cls.sortable).unbind('click.footable').bind('click.footable', function (ec) {
                                ec.preventDefault();
                                $th = $(this);
                                var ascending = !$th.hasClass(cls.sorted);
                                p.doSort($th.index(), ascending);
                                return false;
                            });

                            var didSomeSorting = false;
                            for (var c in ft.columns) {
                                column = ft.columns[c];
                                if (column.sort.initial) {
                                    var ascending = (column.sort.initial !== 'descending');
                                    p.doSort(column.index, ascending);
                                    break;
                                }
                            }
                            if (didSomeSorting) {
                                ft.bindToggleSelectors();
                            }
                        },
                        'footable_redrawn.sorting': function(e) {
                            var $table = $(ft.table),
                                cls = ft.options.classes.sort;
                            if ($table.data('sorted') >= 0) {
                                $table.find('> thead > tr:last-child > th').each(function(i){
                                    var $th = $(this);
                                    if ($th.hasClass(cls.sorted) || $th.hasClass(cls.descending)) {
                                        p.doSort(i);
                                        return;
                                    }
                                });
                            }
                        },
                        'footable_column_data.sorting': function (e) {
                            var $th = $(e.column.th);
                            e.column.data.sort = e.column.data.sort || {};
                            e.column.data.sort.initial = $th.data('sort-initial') || false;
                            e.column.data.sort.ignore = $th.data('sort-ignore') || false;
                            e.column.data.sort.selector = $th.data('sort-selector') || null;

                            var match = $th.data('sort-match') || 0;
                            if (match >= e.column.data.matches.length) match = 0;
                            e.column.data.sort.match = e.column.data.matches[match];
                        }
                    })
                //save the sort object onto the table so we can access it later
                .data('footable-sort', p);
            }
        };

        p.doSort = function(columnIndex, ascending) {
            var ft = p.footable;
            if ($(ft.table).data('sort') === false) return;

            var $table = $(ft.table),
                $tbody = $table.find('> tbody'),
                column = ft.columns[columnIndex],
                $th = $table.find('> thead > tr:last-child > th:eq(' + columnIndex + ')'),
                cls = ft.options.classes.sort,
                evt = ft.options.events.sort;

            ascending = (ascending === undefined) ? $th.hasClass(cls.sorted) :
                (ascending === 'toggle') ? !$th.hasClass(cls.sorted) : ascending;

            if (column.sort.ignore === true) return true;

            //raise a pre-sorting event so that we can cancel the sorting if needed
            var event = ft.raise(evt.sorting, { column: column, direction: ascending ? 'ASC' : 'DESC' });
            if (event && event.result === false) return;

            $table.data('sorted', column.index);

            $table.find('> thead > tr:last-child > th, > thead > tr:last-child > td').not($th).removeClass(cls.sorted + ' ' + cls.descending);

            if (ascending === undefined) {
                ascending = $th.hasClass(cls.sorted);
            }

            if (ascending) {
                $th.removeClass(cls.descending).addClass(cls.sorted);
            } else {
                $th.removeClass(cls.sorted).addClass(cls.descending);
            }

            p.sort(ft, $tbody, column, ascending);

            ft.bindToggleSelectors();
            ft.raise(evt.sorted, { column: column, direction: ascending ? 'ASC' : 'DESC' });
        };

        p.rows = function (ft, tbody, column) {
            var rows = [];
            tbody.find('> tr').each(function () {
                var $row = $(this), $next = null;
                if ($row.hasClass(ft.options.classes.detail)) return true;
                if ($row.next().hasClass(ft.options.classes.detail)) {
                    $next = $row.next().get(0);
                }
                var row = { 'row': $row, 'detail': $next };
                if (column !== undefined) {
                    row.value = ft.parse(this.cells[column.sort.match], column);
                }
                rows.push(row);
                return true;
            }).detach();
            return rows;
        };

        p.sort = function (ft, tbody, column, ascending) {
            var rows = p.rows(ft, tbody, column);
            var sorter = ft.options.sorters[column.type] || ft.options.sorters.alpha;
            rows.sort(function (a, b) {
                if (ascending) {
                    return sorter(a.value, b.value);
                } else {
                    return sorter(b.value, a.value);
                }
            });
            for (var j = 0; j < rows.length; j++) {
                tbody.append(rows[j].row);
                if (rows[j].detail !== null) {
                    tbody.append(rows[j].detail);
                }
            }
        };
    }

    w.footable.plugins.register(Sort, defaults);

})(jQuery, window);