// IE fuck
if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function(elt) {
        var len = this.length;
        for (var i = 0; i< len; i++) {
            if (this[i] == elt) {
                return i;
            }
        }
        return -1;
    }
}

function max_with(array, key) {
    return most_with(array, key, function(a,b){ return a >= b });
}

function min_with(array, key) {
    return most_with(array, key, function(a,b){ return a < b });
}

function most_with(array, key, compare_fn) {
    if (!array.length) return undefined;
    var max = array[1];
    var max_key = max[key];
    var len = array.length;
    for (var i = 1; i < len; i++) {
        if (compare_fn(array[i][key], max_key))
            max = array[i];
    }
    return max;
}

// cookie functions
function createCookie(name,value,days) {
    var days = days || 700;
    var date = new Date();
    date.setTime(date.getTime()+(days*24*60*60*1000));
    var expires = "; expires="+date.toGMTString();
    document.cookie = name+"="+value+expires+"; path=/";
}

function readCookie(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for(var i=0;i < ca.length;i++) {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1,c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
    }
    return null;
}

function eraseCookie(name) {
    createCookie(name,"",-1);
}

// paginator
function Paginator(records, record_per_page) {
    this.records = records;
    this.all_records = new Array(records.length);
    
    for (var i = 0; i < records.length; i++) {
        this.all_records[i] = records[i];
    }
    
    if (!record_per_page) {
        // Try to get from cookie
        var saved_record_per_page = parseInt(readCookie('record_per_page_report'));
        if (!isNaN(saved_record_per_page))
        this.set_record_per_page(saved_record_per_page);
        else
        this.set_record_per_page(10); // default
    } else {
        this.set_record_per_page(record_per_page);
    }
}

Paginator.prototype.set_record_per_page = function(value) {
    this.record_per_page = value;
    this.current_page = 1;
    this.page_count = Math.ceil(this.records.length / value);
    createCookie('record_per_page_report', value, 1000);
}

Paginator.prototype.get_page = function(page) {
    var start = (page - 1) * this.record_per_page;
    var end = start + this.record_per_page;
    this.current_page = page;
    return this.records.slice(start, end);
}

Paginator.prototype.previous = function() {
    if (this.current_page > 1) {
        return this.current_page - 1;
    }
    return undefined;
}

Paginator.prototype.next = function() {
    if (this.current_page + 1 <= this.page_count) {
        return this.current_page + 1;
    }
    return undefined;
}

Paginator.prototype.filter = function(fns) {
    var le = this.all_records.length;
    var lf = fns.length;
    this.records = [];
    
    for (var i = 0; i < le; i++) {
        var el = this.all_records[i];
        var ok = true;
        
        for (var j = 0; j < lf; j++) {
            if (!fns[j](el)) {
                ok = false;
            }
        }
        if (ok)
            this.records.push(el);
    }
    this.page_count = Math.ceil(this.records.length / this.record_per_page);
}

// table
function Table(conf) {
    var conf = conf || {};
    this.name = conf.name || "table";
    this.records = conf.records || [];
    this.row_fn = conf.row_fn || function(){ return $('<tr>') };
    this.table = conf.table || undefined;
    this.default_sort_field = conf.sort_field || undefined;
    this.sort_fields = conf.sort_fields || undefined;
    this.keep_first_on_top = conf.keep_first_on_top || false;
    
    this.page = 1;
    this.tbody = $('tbody', this.table);
    this.paginator = new Paginator(this.records);
    this.add_sorters();
    this.add_selector();
    
    var sort = readCookie(this.name);
    if (sort) {
        var sort_args = sort.split(':');
        var sort_field = sort_args[0];
        var sort_direction = sort_args[1];
        
        this.direction = sort_direction;
        this.sort_by(sort_field);
    } else if (this.default_sort_field) {
        this.sort_by(this.default_sort_field);
    }
}

Table.prototype.add_sorters = function() {
    var table = this;
    $('th[name]', this.table).click(function() {
        table.sort_by($(this).attr('name'));
    });
}

Table.prototype.sort_by = function(field) {
    var direction = undefined;
    
    if (!this._last_direction) {
        direction = "down";
    } else {
        if (this._last_sorted_field == field) {
            direction = this._last_direction == 'down' ? 'up' : 'down';
        } else {
            direction = this._last_direction;
        }
    } 
    
    // apply dom changes
    $('thead th', this.table)
        .removeClass('sorted')
        .children('span')
            .removeClass(direction, oppositeDirection(direction))
        .end()
        .siblings('[name=' + field + ']')
        .addClass('sorted')
        .children('span')
            .addClass(direction).removeClass(oppositeDirection(direction));
    
    this._sort_by(field, direction);
    this._last_sorted_field = field;
    this._last_direction = direction;
    createCookie(this.name, field + ':' + direction);
    this.go(1);
}

function oppositeDirection(direction) {
    return direction == 'up' ? 'down' : 'up';
}

Table.prototype._switch_direction = function() {
    this.direction = this.direction == 'down' ? 'up' : 'down';
}

Table.prototype.add_selector = function() {
    var self = this;
    var td = $('tfoot .record_count_selector_container', this.table);
    var options = [5,10,25,50];
    var select = $('<select>').change(function() 
    {
        self.set_record_per_page(parseInt($(this).val()));
    }).appendTo(td);
    var rec_per_page = self.paginator.record_per_page;
    for (var i = 0; i < options.length; i++) {
        var j = options[i];
        var opt = $('<option>').attr('value', j).text(j).appendTo(select);
        if (j + '' == rec_per_page) {
            opt.attr('selected', 'selected');
        }
    }
}

Table.prototype.render = function() {
    this.clear();
    this.render_body();
    this.render_footer();
}

Table.prototype.clear = function() {
    $('tbody>*', this.table).remove();
    $('tfoot .page_link_container>*', this.table).remove();
}

Table.prototype.render_body = function() {
    var records = this.paginator.get_page(this.page);
    var length = records.length;
    
    if (!length) {
        var tr = $('<tr>');
        tr.append($('<td>')
            .attr('colspan', $('th', this.table).length)
            .html('<h4>Nothing to show</h4>')).appendTo(this.tbody);
    }

    for (var i = 0; i < length; i++) {
        var row = records[i];
        this.tbody.append(this.row_fn(row, i));
    }
}

Table.prototype.render_footer = function() {
    var page_link = function(self, text, page) {
        return $('<div>').attr({
            index: page
        })
        .click(function() {
            self.go(parseInt($(this).attr('index')));
            return false;
        })
        .css({
            display: "inline",
            cursor: "pointer"
        })
        .addClass('page_link')
        .text(text);
    };
    var paginator = this.paginator;
    var td = $('tfoot .page_link_container', this.table);
    var self = this;

    // page links
    if (paginator.page_count > 1) {
        var prev = paginator.previous();
        if (prev) {
            page_link(self, "« Previous", prev).appendTo(td);
        }
        
        var current = paginator.current_page;
        var start, end;
        if (current >= 1 && current <= 10) {
            start = 1;
            end = Math.min(10, paginator.page_count);
        } else {
            var t = current % 10 + 1;
            start = current - t;
            end = Math.min(current + 9, paginator.page_count);
        }
        for (var i = start; i <= end; i++) {
            var lnk = page_link(self, i, i).appendTo(td);
            if (i == paginator.current_page) {
                lnk.addClass('current').css({
                    "color": "black",
                    "font-weight": "bold"
                });
            }
            if (i != paginator.page_count) {
                td.append("<span>|</span>");
            }
        }

        var next = paginator.next();
        if (next) {
            page_link(self, "Next »", next).appendTo(td);
        }
    }
}

Table.prototype.go = function(page) {
    this.page = page;
    this.render();
}

Table.prototype._sort_by = function(field, direction) {
    var direction = direction == 'down' ? false : true;
    
    if (this.sort_fields) {
        var sorter = make_sorter(this.sort_fields.indexOf(field), direction);
    } else {
        var sorter = make_sorter(field, direction);
    }
    if (this.keep_first_on_top) {
        var first = this.paginator.records[0];
        this.paginator.records.splice(0,1);
        this.paginator.records.sort(sorter);
        this.paginator.records.unshift(first);
    } else {
        this.paginator.records.sort(sorter);
    }
}

Table.prototype.set_record_per_page = function(value) {
    this.paginator.set_record_per_page(value);
    this.go(1);
}

Table.prototype.filter = function(fn) {
    this.paginator.filter(fn);
    this.go(1);
}

function make_sorter(field, direction, extra_fn) {
    var f = direction ? -1 : 1;
    return function(s1, s2) {
        if (extra_fn) return extra_fn(s1, s2);
        if (s1[field] > s2[field]) return f * -1;
        if (s1[field] < s2[field]) return f * 1;
        return 0;
    };
}

function goTo(url) {
    document.location.href = url;
    return false;
}

function str_repeat(i, m) { for (var o = []; m > 0; o[--m] = i); return(o.join('')); }

function sprintf () {
    var i = 0, a, f = arguments[i++], o = [], m, p, c, x;
    while (f) {
        if (m = /^[^\x25]+/.exec(f)) o.push(m[0]);
        else if (m = /^\x25{2}/.exec(f)) o.push('%');
        else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
            if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) throw("Too few arguments.");
            if (/[^s]/.test(m[7]) && (typeof(a) != 'number'))
            throw("Expecting number but found " + typeof(a));
            switch (m[7]) {
                case 'b': a = a.toString(2); break;
                case 'c': a = String.fromCharCode(a); break;
                case 'd': a = parseInt(a); break;
                case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
                case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
                case 'o': a = a.toString(8); break;
                case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
                case 'u': a = Math.abs(a); break;
                case 'x': a = a.toString(16); break;
                case 'X': a = a.toString(16).toUpperCase(); break;
            }
            a = (/[def]/.test(m[7]) && m[2] && a > 0 ? '+' + a : a);
            c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
            x = m[5] - String(a).length;
            p = m[5] ? str_repeat(c, x) : '';
            o.push(m[4] ? a + p : p + a);
        }
        else throw ("Huh ?!");
        f = f.substring(m[0].length);
    }
    return o.join('');
}

// some formatting helpers
function format_pct(value) {
    return sprintf("%4.2f%%", value * 100);
}
