define(['jquery', 'underscore', 'common/js/components/utils/view_utils', 'js/views/container', 'js/utils/module', 'gettext',
    'common/js/components/views/feedback_notification', 'js/views/paging_header', 'common/js/components/views/paging_footer'],
    function($, _, ViewUtils, ContainerView, ModuleUtils, gettext, NotificationView, PagingHeader, PagingFooter) {
        var PagedContainerView = ContainerView.extend({

            initialize: function(options) {
                var self = this;
                ContainerView.prototype.initialize.call(this);
                this.page_size = this.options.page_size;
                // Reference to the page model
                this.page = options.page;
                // XBlocks are rendered via Django views and templates rather than underscore templates, and so don't
                // have a Backbone model for us to manipulate in a backbone collection. Here, we emulate the interface
                // of backbone.paginator so that we can use the Paging Header and Footer with this page. As a
                // consequence, however, we have to manipulate its members manually.
                this.collection = {
                    currentPage: 0,
                    totalPages: 0,
                    totalCount: 0,
                    sortDirection: 'desc',
                    start: 0,
                    _size: 0,
                    // Paging header and footer expect this to be a Backbone model they can listen to for changes, but
                    // they cannot. Provide the bind function for them, but have it do nothing.
                    bind: function() {},
                    // size() on backbone collections shows how many objects are in the collection, or in the case
                    // of paginator, on the current page.
                    size: function() { return self.collection._size; },
                    // Toggles the functionality for showing and hiding child previews.
                    showChildrenPreviews: true,

                    // PagingFooter expects to be able to control paging through the collection instead of the view,
                    // so we just make these functions act as pass-throughs
                    setPage: function(page) {
                        self.setPage(page - 1);
                    },

                    nextPage: function() {
                        self.nextPage();
                    },

                    previousPage: function() {
                        self.previousPage();
                    },

                    getPage: function() {
                        return self.collection.currentPage + 1;
                    },

                    hasPreviousPage: function() {
                        return self.collection.currentPage > 0;
                    },

                    hasNextPage: function() {
                        return self.collection.currentPage < self.collection.totalPages - 1;
                    },

                    getTotalPages: function() {
                        return this.totalPages;
                    },

                    getPageNumber: function() {
                        return this.getPage();
                    },

                    getTotalRecords: function() {
                        return this.totalCount;
                    },

                    getPageSize: function() {
                        return self.page_size;
                    }
                };
            },

            new_child_view: 'container_child_preview',

            render: function(options) {
                options = options || {};
                options.page_number = typeof options.page_number !== 'undefined'
                    ? options.page_number
                    : this.collection.currentPage;
                return this.renderPage(options);
            },

            renderPage: function(options) {
                var self = this,
                    view = this.view,
                    xblockInfo = this.model,
                    xblockUrl = xblockInfo.url();

                return $.ajax({
                    url: decodeURIComponent(xblockUrl) + '/' + view,
                    type: 'GET',
                    cache: false,
                    data: this.getRenderParameters(options.page_number, options.force_render),
                    headers: {Accept: 'application/json'},
                    success: function(fragment) {
                        self.handleXBlockFragment(fragment, options);
                        self.processPaging({requested_page: options.page_number});
                        self.page.updatePreviewButton(self.collection.showChildrenPreviews);
                        self.page.renderAddXBlockComponents();
                        if (options.force_render) {
                            var $target = $('.studio-xblock-wrapper[data-locator="' + options.force_render + '"]');
                            // Scroll us to the element with a little buffer at the top for context.
                            ViewUtils.setScrollOffset($target, ($(window).height() * 0.10));
                        }
                    }
                });
            },

            getRenderParameters: function(page_number, force_render) {
                // Options should at least contain page_number.
                return {
                    page_size: this.page_size,
                    enable_paging: true,
                    page_number: page_number,
                    force_render: force_render
                };
            },

            getPageCount: function(total_count) {
                if (total_count === 0) {
                    return 1;
                }
                return Math.ceil(total_count / this.page_size);
            },

            setPage: function(page_number, additional_options) {
                additional_options = additional_options || {};
                var options = _.extend({page_number: page_number}, additional_options);
                this.render(options);
            },

            nextPage: function() {
                var collection = this.collection,
                    currentPage = collection.currentPage,
                    lastPage = collection.totalPages - 1;
                if (currentPage < lastPage) {
                    this.setPage(currentPage + 1);
                }
            },

            previousPage: function() {
                var collection = this.collection,
                    currentPage = collection.currentPage;
                if (currentPage > 0) {
                    this.setPage(currentPage - 1);
                }
            },

            processPaging: function(options) {
                // We have the Django template sneak us the pagination information,
                // and we load it from a div here.
                var $element = this.$el.find('.xblock-container-paging-parameters'),
                    total = $element.data('total'),
                    displayed = $element.data('displayed'),
                    start = $element.data('start'),
                    previews = $element.data('previews');

                this.collection.currentPage = options.requested_page;
                this.collection.totalCount = total;
                this.collection.totalPages = this.getPageCount(total);
                this.collection.start = start;
                this.collection._size = displayed;
                this.collection.showChildrenPreviews = previews;

                this.processPagingHeaderAndFooter();
            },

            processPagingHeaderAndFooter: function() {
                // Rendering the container view detaches the header and footer from the DOM.
                // It's just as easy to recreate them as it is to try to shove them back into the tree.
                if (this.pagingHeader) { this.pagingHeader.undelegateEvents(); }
                if (this.pagingFooter) { this.pagingFooter.undelegateEvents(); }

                this.pagingHeader = new PagingHeader({
                    view: this,
                    el: this.$el.find('.container-paging-header')
                });
                this.pagingFooter = new PagingFooter({
                    collection: this.collection,
                    el: this.$el.find('.container-paging-footer')
                });

                this.pagingHeader.render();
                this.pagingFooter.render();
            },

            refresh: function(xblockView, block_added, is_duplicate) {
                if (!block_added) {
                    return;
                }
                if (is_duplicate) {
                    // Duplicated blocks can be inserted onto the current page.
                    var xblock = xblockView.xblock.element.parents('.studio-xblock-wrapper').first();
                    var all_xblocks = xblock.parent().children('.studio-xblock-wrapper');
                    var index = all_xblocks.index(xblock);
                    if ((index + 1 <= this.page_size) && (all_xblocks.length > this.page_size)) {
                        // Pop the last XBlock off the bottom.
                        all_xblocks[all_xblocks.length - 1].remove();
                        return;
                    }
                }
                this.collection.totalCount += 1;
                this.collection._size += 1;
                if (this.collection.totalCount == 1) {
                    this.render();
                    return;
                }
                this.collection.totalPages = this.getPageCount(this.collection.totalCount);
                var target_page = this.collection.totalPages - 1;
                // If we're on a new page due to overflow, or this is the first item, set the page.
                if (((this.collection.currentPage) != target_page) || this.collection.totalCount == 1) {
                    var force_render = xblockView.model.id;
                    if (is_duplicate) {
                        // The duplicate should be on the next page if we've gotten here.
                        target_page = this.collection.currentPage + 1;
                    }
                    this.setPage(
                        target_page,
                        {force_render: force_render}
                    );
                } else {
                    this.pagingHeader.render();
                    this.pagingFooter.render();
                }
            },

            acknowledgeXBlockDeletion: function(locator) {
                this.notifyRuntime('deleted-child', locator);
                this.collection._size -= 1;
                this.collection.totalCount -= 1;
                var current_page = this.collection.currentPage;
                var total_pages = this.getPageCount(this.collection.totalCount);
                this.collection.totalPages = total_pages;
                // Starts counting from 0
                if ((current_page + 1) > total_pages) {
                    // The number of total pages has changed. Move down.
                    // Also, be mindful of the off-by-one.
                    this.setPage(total_pages - 1);
                } else if ((current_page + 1) != total_pages) {
                    // Refresh page to get any blocks shifted from the next page.
                    this.setPage(current_page);
                } else {
                    // We're on the last page, just need to update the numbers in the
                    // pagination interface.
                    this.pagingHeader.render();
                    this.pagingFooter.render();
                }
            },

            sortDisplayName: function() {
                return gettext('Date added');  // TODO add support for sorting
            },

            togglePreviews: function() {
                var self = this,
                    xblockUrl = this.model.url();
                return $.ajax({
                    // No runtime, so can't get this via the handler() call.
                    url: '/preview' + decodeURIComponent(xblockUrl) + '/handler/trigger_previews',
                    type: 'POST',
                    data: JSON.stringify({showChildrenPreviews: !this.collection.showChildrenPreviews}),
                    dataType: 'json'
                })
                .then(self.render).promise();
            }
        });

        return PagedContainerView;
    }); // end define();
