/*
 * A simple paginator to paginate content with multiple elements
 * usage:
 *  var availPagination = new Paginator('.elements' [, {itemsPerPage: 10, showLimit: 10, initPage: 3}])
 */

var Paginator = function (selector, config) {
    // apply eventual config
    this.config = {
        target: '',
        itemsPerPage: 20,
        showLimit: 5,
        initPage: 0,
        filter: function() {
            return true;
        },
        renderProps: {},

    };
    AVutil.obj.extend(this.config, config || {});
    this.selector = selector;
    if (AVutil.obj.isEmpty(this.config.renderProps)) {
        this.$items = this.$itemsBuffer = jQuery(this.selector);
        this.vitemsBuffer = [];
    } else {
        this.$items = this.$itemsBuffer = [];
        this.vitemsBuffer = this.getRenderItems('all');
        // fold items object into string to avoid memory overflow
        this.config.renderProps.items = JSON.stringify(this.config.renderProps.items);
    }
    this.$target = this.config.target.length ? jQuery(this.config.target) : this.$items.parent();
    this.currentPage = this.config.initPage;
    this.currentItems = [];
    this.first = this.currentPage = this.config.initPage;
    this.last = Math.ceil((this.$itemsBuffer.length || this.parseVItems().length) / this.config.itemsPerPage) - 1;
    this.halfCut = Math.floor(this.config.showLimit / 2);
    this.$el = jQuery('<div class="paginator sp-stack" data-currentpage=""></div>');
    this.ui = {
        container: '<table class="paginator__nav" role="presentation"><tbody><tr></tr></tbody></table>',
        first: [
            '<td class="paginator__navend"><span class="paginator__switch inactive" aria-hidden="true">&laquo;</span></td>',
            '<td class="paginator__navend"><a href="#" class="paginator__switch active js-paginatorSwitch" data-switchpage="first">&laquo;</a></td>'
        ],
        prev: [
            '<td class="paginator__navend"><span class="paginator__switch inactive" aria-hidden="true">&lsaquo;</span></td>',
            '<td class="paginator__navend"><a href="#" class="paginator__switch active js-paginatorSwitch" data-switchpage="prev">&lsaquo;</a></td>'
        ],
        last: [
            '<td class="paginator__navend"><span class="paginator__switch inactive" aria-hidden="true">&raquo;</span></td>',
            '<td class="paginator__navend"><a href="#" class="paginator__switch active js-paginatorSwitch" data-switchpage="last">&raquo;</a></td>'
        ],
        next: [
            '<td class="paginator__navend"><span class="paginator__switch inactive" aria-hidden="true">&rsaquo;</span></td>',
            '<td class="paginator__navend"><a href="#" class="paginator__switch active js-paginatorSwitch" data-switchpage="next">&rsaquo;</a></td>'
        ]
    };
    // overridable render method
    this._renderItem = function (num) {
        if (num === this.currentPage) {
            return '<td><span class="paginator__switch current" role="button" aria-pressed="true">' + (num + 1) + '</span></td>';
        }
        return '<td><a href="#" class="paginator__switch active js-paginatorSwitch" data-switchpage="' + num + '">' + (num + 1) + '</a></td>';
    };
    // init paginator
    this.init();
};
AVutil.obj.extend(Paginator.prototype, {
    init: function () {
        this.$target.css({
            display: 'block'
        });
        if (
            (this.$itemsBuffer.length !== 0 && this.$itemsBuffer.length > this.config.itemsPerPage) ||
            (this.parseVItems().length !== 0 && this.parseVItems().length > this.config.itemsPerPage)
        ) {
            this.$el.append(this.ui.container).appendTo(this.$target);
        }
        this.switchTo(this.config.initPage, true).listen();
        return this;
    },
    destroy: function () {
        this.$el.remove();
    },
    updateItems: function (selector) {
        selector = selector || '';
        if (AVutil.obj.isEmpty(this.config.renderProps)) {
            if (selector.length) {
                this.$itemsBuffer = this.$items.filter(selector);
            } else {
                this.$items = this.$itemsBuffer = jQuery(this.selector);
            }
        } else {
            this.vitemsBuffer = this.getRenderItems('all-filter');
        }
        this.last = Math.ceil((this.$itemsBuffer.length || this.parseVItems().length) / this.config.itemsPerPage) - 1;
        this.$el.empty();
        this.init();
        return this;
    },
    listen: function () {
        var self = this;
        self.$el.on('click', '.js-paginatorSwitch', function (e) {
            if (!self.$el.hasClass('idle')) {
                self.triggerSwitch.call(self, jQuery(this).data('switchpage'));
            }
            return false;
        });
    },
    triggerSwitch: function (page) {
        switch (page) {
            case 'next':
                this.nextPage();
                break;
            case 'prev':
                this.prevPage();
                break;
            case 'first':
                this.firstPage();
                break;
            case 'last':
                this.lastPage();
                break;
            default:
                this.switchTo(+page);
        }
        return this;
    },
    switchTo: function (page, init) {
        this.setCurrentPage(page).draw();
        if (!init) {
            var self = this;
            self.$el.addClass('idle');
            setTimeout(function () {
                self.scrollBackTop(page);
            }, 180);
        } else {
            this.applySwitch(page);
        }
        return this;
    },
    recursiveFilter: function (stack, limit) {
        this.currentItems = [];
        limit = limit || Infinity;
        stack = stack.reverse();
        for (var i = stack.length - 1; i >= 0; i--) {
            if (this.config.filter(stack[i])) {
                this.currentItems.push(stack[i]);
            }
            stack.splice(i, 1);
            if (this.currentItems.length === limit) {
                break;
            }
        }
        if (this.currentItems.length < limit && stack.length > 0) {
            return this.recursiveFilter(stack);
        }
        return this.currentItems;
    },
    applySwitch: function (page) {
        var pageItems = this.getRenderItems(+page);
        if (AVutil.obj.isEmpty(this.config.renderProps)) {
            this.$itemsBuffer.css({
                display: 'none'
            });
            jQuery(pageItems).css({
                display: ''
            });
        } else {
            jQuery(this.config.renderProps.target)
                .empty()
                .html(this.config.renderProps.template(
                    AVutil.obj.extend(this.config.renderProps.data, {
                        [this.config.renderProps.itemsName]: pageItems
                    })))
                .find(this.selector).show();
            delete this.vitemsBuffer;
        }
        if (typeof this.config.callback === 'function') {
            this.config.callback(pageItems);
        }
        return this;
    },
    firstIndex: function (page) {
        return (page || this.currentPage || this.config.initPage) * this.config.itemsPerPage;
    },
    parseVItems: function () {
        if (typeof this.vitemsBuffer === 'undefined') {
            var data = this.config.renderProps.items;
            if (typeof data === 'string') {
                data = JSON.parse(data);
            }
            this.vitemsBuffer = data;
        }
        return this.vitemsBuffer;
    },
    getRenderItems: function (page) {
        var data;
        if (AVutil.obj.isEmpty(this.config.renderProps)) {
            data = this.$itemsBuffer.toArray();
        } else {
            data = AVutil.clone(this.parseVItems());
        }
        if (page === 'all') {
            return data;
        } else if (page === 'all-filter') {
            return this.recursiveFilter(data);
        }
        return this.recursiveFilter(
            data.slice(
                this.firstIndex(+page)
            ), this.config.itemsPerPage
        );
    },
    getCurrentItems: function () {
        return this.currentItems;
    },
    getCurrentPage: function () {
        return this.currentPage;
    },
    setCurrentPage: function (page) {
        this.currentPage = (+page);
        this.$el.data('currentpage', page);
        return this;
    },
    nextPage: function () {
        this.switchTo(this.currentPage + 1);
        return this;
    },
    prevPage: function () {
        this.switchTo(this.currentPage - 1);
        return this;
    },
    firstPage: function () {
        this.switchTo(this.first);
        return this;
    },
    lastPage: function () {
        this.switchTo(this.last);
        return this;
    },
    draw: function () {
        var pages = this.$el.find('tr').empty(),
            lCut = this.currentPage - this.halfCut,
            rCut = this.currentPage + this.halfCut,
            start,
            end;
        if (lCut > this.first && rCut > this.last)
            lCut -= rCut - this.last;
        if (rCut < this.last && lCut < this.first)
            rCut += Math.abs(lCut - this.first);
        start = lCut > this.first ? lCut : this.first;
        end = rCut < this.last ? rCut : this.last;
        if (end > 0) {
            this.$el.show();
            if (this.currentPage !== this.first) {
                pages.append([this.ui.first[1], this.ui.prev[1]]);
            } else {
                pages.append([this.ui.first[0], this.ui.prev[0]])
            }
            for (var i = start; i <= end; i++) {
                pages.append(this._renderItem(i));
            }
            if (this.currentPage !== this.last) {
                pages.append([this.ui.next[1], this.ui.last[1]]);
            } else {
                pages.append([this.ui.next[0], this.ui.last[0]]);
            }
        } else {
            this.$el.hide();
        }
        return this;
    },
    scrollBackTop: function (page) {
        var $target = (this.$items.length !== 0) ? this.$items.parent() : jQuery(this.config.renderProps.target);
        var complete = AVutil.handleOnce(function () {
            this.applySwitch(page);
            this.$el.removeClass('idle');
        }.bind(this));
        jQuery('html, body').stop().animate({
            scrollTop: $target.offset().top - 18
        }, {
            complete
        });
        return this;
    }
});