/**
 * This jQuery plugin displays pagination links inside the selected elements.
 *
 * @author Gabriel Birke (birke *at* d-scribe *dot* de)
 * @modified by Paweł Sroczyński
 * 
 * @version 1.2
 * @param {int} maxentries Number of entries to paginate
 * @param {Object} opts Several options (see README for documentation)
 * @return {Object} jQuery Object
 */
jQuery.fn.pagination = function(maxentries, opts) {
    opts = jQuery.extend({
        load_data_on_init: true,
        items_per_page: 10,
        num_display_entries: 10,
        current_page: 0,
        num_edge_entries: 0,
        link_to: "#",
        prev_text: "Prev",
        next_text: "Next",
        links: {
            prev_link: null,
            next_link: null,
            prev_link_disabled: null,
            next_link_disabled: null
        },

        ellipse_text: "...",
        prev_show_always: true,
        next_show_always: true,
        callback: function() { return false; }
    }, opts || {});

    var panel = null;
    var current_page = null;

    /**
    * Calculate the maximum number of pages
    */
    function numPages() {
        return Math.ceil(maxentries / opts.items_per_page);
    }

    /**
    * Calculate start and end point of pagination links depending on 
    * current_page and num_display_entries.
    * @return {Array}
    */
    function getInterval() {
        var ne_half = Math.ceil(opts.num_display_entries / 2);
        var np = numPages();
        var upper_limit = np - opts.num_display_entries;
        var start = current_page > ne_half ? Math.max(Math.min(current_page - ne_half, upper_limit), 0) : 0;
        var end = current_page > ne_half ? Math.min(current_page + ne_half, np) : Math.min(opts.num_display_entries, np);
        return [start, end];
    }

    /**
    * This is the event handling function for the pagination links. 
    * @param {int} page_id The new page number
    */
    function pageSelected(page_id, evt) {
        current_page = page_id;

        drawLinks();

        var continuePropagation = opts.callback(page_id, panel);
        if (!continuePropagation) {
            if (evt.stopPropagation) {
                evt.stopPropagation();
            }
            else {
                evt.cancelBubble = true;
            }
        }
        return continuePropagation;
    }

    /**
    * This function inserts the pagination links into the container element
    */
    function drawLinks() {

        panel.empty();

        var interval = getInterval();
        var np = numPages();

        // This helper function returns a handler function that calls pageSelected with the right page_id
        var getClickHandler = function(page_id) {
            return function(evt) {
                return pageSelected(page_id, evt);
            }
        }

        // Helper function for generating a single link (or a span tag if it's the current page)
        var appendItem = function(page_id, appendopts) {
            page_id = page_id < 0 ? 0 : (page_id < np ? page_id : np - 1); // Normalize page id to sane value
            appendopts = jQuery.extend({ text: page_id + 1, classes: "" }, appendopts || {});

            if (page_id == current_page) {
                var lnk = jQuery("<span class='current'>" + (appendopts.text) + "</span>");
                if (appendopts.previous === true && appendopts.links != undefined && appendopts.links.prev_link_disabled) {
                    lnk = jQuery(appendopts.links.prev_link_disabled);
                }
                else if (appendopts.previous === false && appendopts.links != undefined && appendopts.links.next_link_disabled) {
                    lnk = jQuery(appendopts.links.next_link_disabled);
                }
            }
            else {
                var lnk = jQuery("<a class='paginationPage'>" + (appendopts.text) + "</a>");
                if (appendopts.previous === true && appendopts.links != undefined && appendopts.links.prev_link) {
                    lnk = jQuery(appendopts.links.prev_link);
                }
                else if (appendopts.previous === false && appendopts.links != undefined && appendopts.links.next_link) {
                    lnk = jQuery(appendopts.links.next_link);
                }

                lnk.bind("click", getClickHandler(page_id))
                    .attr('href', opts.link_to.replace(/__id__/, page_id));
            }

            if (appendopts.classes) { lnk.addClass(appendopts.classes); }
            panel.append(lnk);
        }

        // Generate "Previous"-Link
        if (opts.prev_text && (current_page > 0 || opts.prev_show_always)) {
            appendItem(current_page - 1, { text: opts.prev_text, classes: "prev", links: opts.links, previous: true });
        }

        // Generate starting points
        if (interval[0] > 0 && opts.num_edge_entries > 0) {
            var end = Math.min(opts.num_edge_entries, interval[0]);
            for (var i = 0; i < end; i++) {
                appendItem(i);
            }
            if (opts.num_edge_entries < interval[0] && opts.ellipse_text) {
                jQuery("<span>" + opts.ellipse_text + "</span>").appendTo(panel);
            }
        }

        // Generate interval links
        for (var i = interval[0]; i < interval[1]; i++) {
            appendItem(i);
        }

        // Generate ending points
        if (interval[1] < np && opts.num_edge_entries > 0) {
            if (np - opts.num_edge_entries > interval[1] && opts.ellipse_text) {
                jQuery("<span>" + opts.ellipse_text + "</span>").appendTo(panel);
            }
            var begin = Math.max(np - opts.num_edge_entries, interval[1]);
            for (var i = begin; i < np; i++) {
                appendItem(i);
            }
        }
        
        // Generate "Next"-Link
        if (opts.next_text && (current_page < np - 1 || opts.next_show_always)) {
            appendItem(current_page + 1, { text: opts.next_text, classes: "next", links: opts.links, previous: false });
        }
    }

    function init(obj, redrawOnly) {

        // Extract current_page from options
        current_page = opts.current_page;

        // Create a sane value for maxentries and items_per_page
        maxentries = (!maxentries || maxentries < 0) ? 1 : maxentries;
        opts.items_per_page = (!opts.items_per_page || opts.items_per_page < 0) ? 1 : opts.items_per_page;

        // Store DOM element for easy access from all inner functions
        panel = jQuery(obj);

        // Attach control functions to the DOM element
        obj.selectPage = function(page_id) { pageSelected(page_id); }

        obj.prevPage = function() {
            if (current_page > 0) {
                pageSelected(current_page - 1);
                return true;
            }
            else {
                return false;
            }
        }

        obj.nextPage = function() {
            if (current_page < numPages() - 1) {
                pageSelected(current_page + 1);
                return true;
            }
            else {
                return false;
            }
        }

        // When all initialisation is done, draw the links
        if (maxentries != 0) {
            drawLinks();
        }

        if (opts.load_data_on_init && redrawOnly !== true) {
            // call callback function
            opts.callback(current_page, obj);
        }

    }

    jQuery.fn.pagination.redrawPages = function(totalRows, activePageIndex) {
        maxentries = totalRows;

        // If page number is defined
        if (activePageIndex != undefined) {
            // If defined page number is grater than number of pages
            if (Math.ceil(totalRows / opts.items_per_page) >= activePageIndex + 1) {
                pageIndex = activePageIndex;
                opts.current_page = pageIndex;
            }
            else {
                pageIndex = activePageIndex - 1;
                opts.current_page = pageIndex;
            }
        }

        init(panel, true);
        opts.load_data_on_init = true;
    };

    return this.each(function() {
        init(this);
        opts.load_data_on_init = true;
    });
}

