/**
 * Created by Michael on 04/05/14.
 */

describe('dirPagination directive', function() {

    var $compile;
    var $scope;
    var containingElement;
    var myCollection;

    beforeEach(module('angularUtils.directives.dirPagination'));
    beforeEach(module('templates-main'));

    beforeEach(inject(function($rootScope, _$compile_) {

        $compile = _$compile_;
        $scope = $rootScope.$new();
        containingElement = angular.element('<div></div>');

        myCollection = [];
        for(var i = 1; i <= 100; i++) {
            myCollection.push('item ' + i);
        }
    }));

    function compileElement(collection, itemsPerPage, currentPage, customExpression, totalItems) {
        var expression;
        var totalItemsHtml;
        var html;
        $scope.collection = collection;
        $scope.itemsPerPage = itemsPerPage;
        $scope.currentPage = currentPage || 1;
        $scope.totalItems = totalItems || undefined;
        totalItemsHtml =  (typeof totalItems !== 'undefined') ? 'total-items="totalItems"' : '';
        expression = customExpression || "item in collection | itemsPerPage: itemsPerPage";
        html = '<ul class="list"><li dir-paginate="'+ expression + '" current-page="currentPage" ' + totalItemsHtml + ' >{{ item }}</li></ul> ' +
            '<dir-pagination-controls></dir-pagination-controls>';
        containingElement.append($compile(html)($scope));
        $scope.$apply();
    }

    function getListItems() {
        return containingElement.find('ul.list li').map(function() {
            return $(this).text().trim();
        }).get();
    }

    function getPageLinksArray() {
        return containingElement.find('ul.pagination li').map(function() {
            return $(this).text().trim();
        }).get();
    }

    describe('paginated list', function() {

        it('should throw an exception if itemsPerPage filter not set', function() {
            function compile() {
                var customExpression = "item in collection";
                compileElement(myCollection, 5, 1, customExpression);
            }
            expect(compile).toThrow("pagination directive: the 'itemsPerPage' filter must be set.");
        });

        it('should allow a space after itemsPerPage and before the colon', function() {
            function compile() {
                var customExpression = "item in collection | itemsPerPage : 10";
                compileElement(myCollection, 5, 1, customExpression);
            }
            expect(compile).not.toThrow();
        });

        it('should repeat the items like ng-repeat', function() {
            compileElement(myCollection);
            var listItems = getListItems();

            expect(listItems.length).toBe(100);
        });

        it('should limit the items to match itemsPerPage = 10', function() {
            var listItems;

            compileElement(myCollection, 10);
            listItems = getListItems();
            expect(listItems.length).toBe(10);
        });

        it('should limit the items to match itemsPerPage = 50', function() {
            var listItems;

            compileElement(myCollection, 50);
            listItems = getListItems();
            expect(listItems.length).toBe(50);
        });

        it('should not mutate the collection itself ', function() {
            compileElement(myCollection);
            expect($scope.collection.length).toBe(100);
            compileElement(myCollection, 50);
            expect($scope.collection.length).toBe(100);
            compileElement(myCollection, 5);
            expect($scope.collection.length).toBe(100);
        });

        it('should work correctly with other filters (filter)', function() {
            $scope.filterBy = '2';
            var customExpression = "item in collection | filter: filterBy | itemsPerPage: itemsPerPage";
            compileElement(myCollection, 5, 1, customExpression);

            var listItems = getListItems();
            expect(listItems).toEqual(['item 2', 'item 12', 'item 20', 'item 21', 'item 22']);
        });

        it('should work correctly with other filters (orderBy)', function() {
            var customExpression = "item in collection | orderBy:'toString()':true | itemsPerPage: itemsPerPage";
            compileElement(myCollection, 5, 1, customExpression);

            var listItems = getListItems();
            expect(listItems).toEqual(['item 99', 'item 98', 'item 97', 'item 96', 'item 95']);
        });

        it('should work inside a transcluded directive (ng-if)', function() {
            $scope.collection = myCollection;
            var html = '<div ng-if="true">' +
                            '<ul class="list"><li dir-paginate="item in collection | itemsPerPage: 5">{{ item }}</li></ul> ' +
                            '<dir-pagination-controls></dir-pagination-controls>' +
                        '</div>';
            containingElement.append($compile(html)($scope));
            $scope.$apply();

            var listItems = getListItems();
            expect(listItems).toEqual(['item 1', 'item 2', 'item 3', 'item 4', 'item 5']);
        });

        it('should display the second page when compiled with currentPage = 2', function() {
            var listItems;
            compileElement(myCollection, 3, 2);
            listItems = getListItems();
            expect(listItems).toEqual(['item 4', 'item 5', 'item 6']);
        });

        it('should display the next page when the currentPage changes', function() {
            var listItems;
            compileElement(myCollection, 3);
            listItems = getListItems();
            expect(listItems).toEqual(['item 1', 'item 2', 'item 3']);

            $scope.$apply(function() {
                $scope.currentPage = 2;
            });
            listItems = getListItems();
            expect(listItems).toEqual(['item 4', 'item 5', 'item 6']);

            $scope.$apply(function() {
                $scope.currentPage = 3;
            });
            listItems = getListItems();
            expect(listItems).toEqual(['item 7', 'item 8', 'item 9']);
        });


        it('should work if itemsPerPage is a literal value', function() {
            var customExpression = "item in collection | itemsPerPage: 2";
            compileElement(myCollection, null, 1, customExpression);

            var listItems = getListItems();
            expect(listItems.length).toEqual(2);
            expect(listItems).toEqual(['item 1', 'item 2']);
        });

    });

    describe('if currentPage attribute is not set', function() {

        beforeEach(function() {
            $scope.collection = myCollection;
            html = '<ul class="list"><li dir-paginate="item in collection | itemsPerPage: 3">{{ item }}</li></ul> ' +
                '<dir-pagination-controls></dir-pagination-controls>';
            containingElement.append($compile(html)($scope));
            $scope.$apply();
        });

        it('should compile', function() {
            var listItems = getListItems();
            expect(listItems).toEqual(['item 1', 'item 2', 'item 3']);
        });

        it('should page correctly', function() {
            var pagination = containingElement.find('ul.pagination');

            pagination.children().eq(3).find('a').triggerHandler('click');
            $scope.$apply();
            expect($scope.__default__currentPage).toBe(3);
            var listItems = getListItems();
            expect(listItems).toEqual(['item 7', 'item 8', 'item 9']);
        });
    });

    describe('pagination controls', function() {

        it('should throw an exception if the dir-paginate directive has not been set up', function() {
            function compile() {
                var html = '<dir-pagination-controls></dir-pagination-controls>';
                containingElement.append($compile(html)($scope));
                $scope.$apply();
            }

            expect(compile).toThrow("pagination directive: the pagination controls cannot be used without the corresponding pagination directive.");
        });

        it('should not display pagination if all rows fit on one page', function() {
            compileElement(myCollection, 9999);
            var paginationLinks = getPageLinksArray();

            expect(paginationLinks.length).toBe(0);
        });

        it('should paginate by default if all items do not fit on page', function() {
            compileElement(myCollection, 40);
            var paginationLinks = getPageLinksArray();

            expect(paginationLinks).toEqual(['‹','1', '2', '3', '›']);
        });

        it('should update the currentPage property of $scope when links clicked', function() {
            compileElement(myCollection, 40);
            var pagination = containingElement.find('ul.pagination');

            pagination.children().eq(3).find('a').triggerHandler('click');
            $scope.$apply();
            expect($scope.currentPage).toBe(3);

            pagination.children().eq(2).find('a').triggerHandler('click');
            $scope.$apply();
            expect($scope.currentPage).toBe(2);

            pagination.children().eq(1).find('a').triggerHandler('click');
            $scope.$apply();
            expect($scope.currentPage).toBe(1);
        });

        it('should set the pagination.current value to 5 when compiled with currentPage = 5', function() {
            compileElement(myCollection, 3, 5);
            var activePageItem = containingElement.find('li.active').eq(0);
            var activePage = activePageItem.text().trim();

            expect(activePage).toEqual('5');
        });

        it('should show the correct pagination links at start of sequence', function() {
            compileElement(myCollection, 1);
            var pageLinks = getPageLinksArray();

            expect(pageLinks).toEqual(['‹','1', '2', '3', '4', '5', '6', '7', '...', '100', '›']);
        });

        it('should show the correct pagination links in middle sequence', function() {
            compileElement(myCollection, 1);
            $scope.$apply(function() {
                $scope.currentPage = 50;
            });
            var pageLinks = getPageLinksArray();

            expect(pageLinks).toEqual(['‹','1', '...', '48', '49', '50', '51', '52', '...', '100', '›']);
        });

        it('should show the correct pagination links at end of sequence', function() {
            compileElement(myCollection, 1);
            $scope.$apply(function() {
                $scope.currentPage = 99;
            });
            var pageLinks = getPageLinksArray();

            expect(pageLinks).toEqual(['‹','1', '...', '94', '95', '96', '97', '98', '99', '100', '›']);
        });

        it('should calculate pages based off collection after all filters are applied', function() {
            $scope.filterBy = '2';
            var customExpression = "item in collection | filter: filterBy | itemsPerPage: itemsPerPage";
            compileElement(myCollection, 5, 1, customExpression);

            var pageLinks = getPageLinksArray();
            expect(pageLinks.length).toEqual(6);
        });

        it('should update the active page to reflect the value of the current-page property', function() {
            compileElement(myCollection, 10, 3);

            var activeLink = containingElement.find('ul.pagination li.active');
            expect(activeLink.html()).toContain(3);

            $scope.$apply(function() {
                $scope.currentPage = 1;
            });

            activeLink = containingElement.find('ul.pagination li.active');
            expect(activeLink.html()).toContain(1);
        });

        describe('optional attributes', function() {

            function compileWithAttributes(attributes) {
                $scope.collection = myCollection;
                $scope.currentPage = 1;
                html = '<ul class="list"><li dir-paginate="item in collection | itemsPerPage: 10" current-page="currentPage">{{ item }}</li></ul> ' +
                    '<dir-pagination-controls ' + attributes + ' ></dir-pagination-controls>';
                containingElement.append($compile(html)($scope));
                $scope.$apply();
            }


            it('should accept a max-size attribute to limit the length of the control', function() {
                compileWithAttributes(' max-size="5" ');

                var pageLinks = getPageLinksArray();

                expect(pageLinks).toEqual(['‹','1', '2', '3', '...', '10', '›']);
            });

            it('should impose a minimum max-size of 5', function() {
                compileWithAttributes(' max-size="2" ');

                var pageLinks = getPageLinksArray();

                expect(pageLinks).toEqual(['‹','1', '2', '3', '...', '10', '›']);
            });

            it('should go to the last page when clicking the end arrow', function() {
                compileWithAttributes(' boundary-links="true" ');
                var pagination = containingElement.find('ul.pagination');

                pagination.children().eq(10).find('a').triggerHandler('click');
                $scope.$apply();
                expect($scope.currentPage).toBe(10);
            });

            it('should go to the first page when clicking the end arrow', function() {
                compileWithAttributes(' boundary-links="true" ');
                var pagination = containingElement.find('ul.pagination');

                $scope.$apply(function() {
                    $scope.currentPage = 5;
                });
                expect($scope.currentPage).toBe(5);

                pagination.children().eq(0).find('a').triggerHandler('click');
                $scope.$apply();
                expect($scope.currentPage).toBe(1);
            });

            it('should page forward', function() {
                compileWithAttributes('  ');
                var pagination = containingElement.find('ul.pagination');

                pagination.children().eq(10).find('a').triggerHandler('click');
                $scope.$apply();
                expect($scope.currentPage).toBe(2);
            });

            describe('on-page-change callback', function() {

                beforeEach(function() {
                    $scope.myCallback = function(currentPage) {
                        return "The current page is " + currentPage;
                    };
                    spyOn($scope, 'myCallback').and.callThrough();
                    compileWithAttributes(' on-page-change="myCallback(newPageNumber)" ');
                });

                it('should call the callback once when page link clicked', function() {
                    var pagination = containingElement.find('ul.pagination');

                    expect($scope.myCallback.calls.count()).toEqual(0);
                    pagination.children().eq(2).find('a').triggerHandler('click');
                    $scope.$apply();
                    expect($scope.myCallback).toHaveBeenCalled();
                    expect($scope.myCallback.calls.count()).toEqual(1);
                });

                it('should pass the current page number to the callback', function() {
                    var pagination = containingElement.find('ul.pagination');

                    pagination.children().eq(2).find('a').triggerHandler('click');
                    $scope.$apply();
                    expect($scope.myCallback).toHaveBeenCalledWith(2);
                });
            });

            describe('total-items attribute', function() {

                it('should give correct pagination at 200', function() {
                    compileElement(myCollection, 100, 1, false, 200);

                    var pageLinks = getPageLinksArray();
                    expect(pageLinks).toEqual(['‹','1', '2', '›']);
                });

                it('should give correct pagination at 500', function() {
                    compileElement(myCollection, 100, 1, false, 500);

                    var pageLinks = getPageLinksArray();
                    expect(pageLinks).toEqual(['‹','1', '2','3','4','5', '›']);
                });

                it('should correctly display the second page of results', function() {
                    compileElement(myCollection, 100, 2, false, 500);
                    listItems = getListItems();
                    expect(listItems.length).toEqual(100);
                });
            });
        });

    });

    describe('multiple pagination instances per page', function() {

        var collection1, collection2, currentPage1, currentPage2;

        beforeEach(function() {
            collection1 = [];
            collection2 = [];
            for (var i = 0; i < 20; i++) {
                collection1.push('c1:' + i);
                collection2.push('c2:' + i);
            }
        });

        /**
         * Compile function for multiple pagination directives on a single page
         * @param collection
         * @param itemsPerPage
         * @param currentPage
         * @param paginationId
         * @param customExpression
         */
        function compileMultipleInstance(collection, itemsPerPage, currentPage, paginationId, customExpression) {
            var expression;
            var html;
            if ($scope.collection === undefined) {
                $scope.collection = {};
            }
            if ($scope.itemsPerPage === undefined) {
                $scope.itemsPerPage = {};
            }
            if ($scope.currentPage === undefined) {
                $scope.currentPage = {};
            }
            $scope.collection[paginationId] = collection;
            $scope.itemsPerPage[paginationId] = itemsPerPage;
            $scope.currentPage[paginationId] = currentPage || 1;
            expression = customExpression || "item in collection." + paginationId + " | itemsPerPage: itemsPerPage." + paginationId + ": '" + paginationId + "'";
            html = '<ul class="list"><li dir-paginate="'+ expression + '" current-page="currentPage.' + paginationId + '" pagination-id="' + paginationId + '" >{{ item }}</li></ul> ' +
                '<dir-pagination-controls pagination-id="' + paginationId + '"></dir-pagination-controls>';
            containingElement.append($compile(html)($scope));
            $scope.$apply();
        }

        function clickPaginationLink(paginationId, index) {
            var pagination = containingElement.find('dir-pagination-controls[pagination-id="' + paginationId + '"]');

            pagination.find('li').eq(index).find('a').triggerHandler('click');
            $scope.$apply();
        }

        function getMultiPageLinksArray(paginationId) {
            return containingElement.find('dir-pagination-controls[pagination-id="' + paginationId + '"] li').map(function() {
                return $(this).text().trim();
            }).get();
        }

        function getMultiListItems(paginationId) {
            return containingElement.find('li[pagination-id="' + paginationId + '"]').map(function() {
                return $(this).text().trim();
            }).get();
        }

        it('should allow pagination-id to control a specific collection', function() {
            compileMultipleInstance(collection1, 5, 1, "c1" );
            compileMultipleInstance(collection2, 5, 1, "c2" );

            clickPaginationLink("c1", 2);
            clickPaginationLink("c2", 4);

            expect($scope.currentPage.c1).toEqual(2);
            expect($scope.currentPage.c2).toEqual(4);
        });

        it('should allow independent changing of items per page', function() {
            compileMultipleInstance(collection1, 5, 1, "c1" );
            compileMultipleInstance(collection2, 5, 1, "c2" );

            expect(getMultiPageLinksArray("c1").length).toBe(6);
            expect(getMultiPageLinksArray("c2").length).toBe(6);

            $scope.$apply(function() {
                $scope.itemsPerPage.c1 = 10;
            });

            expect(getMultiPageLinksArray("c1").length).toBe(4);
            expect(getMultiPageLinksArray("c2").length).toBe(6);

            $scope.$apply(function() {
                $scope.itemsPerPage.c2 = 7;
            });

            expect(getMultiPageLinksArray("c1").length).toBe(4);
            expect(getMultiPageLinksArray("c2").length).toBe(5);
        });

        it('should allow independent filtering', function() {
            compileMultipleInstance(collection1, 5, 1, "c1", "item in collection.c1 | filter: filter1 | itemsPerPage: itemsPerPage.c1: 'c1'");
            compileMultipleInstance(collection2, 5, 1, "c2", "item in collection.c2 | filter: filter2 | itemsPerPage: itemsPerPage.c2: 'c2'" );

            $scope.$apply(function() {
                $scope.filter1 = "7";
                $scope.filter2 = "8";
            });

            expect(getMultiListItems("c1")).toEqual(['c1:7', 'c1:17']);
            expect(getMultiListItems("c2")).toEqual(['c2:8', 'c2:18']);

        });

        it('should allow independent setting of current-page externally', function() {
            compileMultipleInstance(collection1, 2, 1, "c1" );
            compileMultipleInstance(collection2, 2, 1, "c2" );

            $scope.$apply(function() {
                $scope.currentPage.c1 = 2;
                $scope.currentPage.c2 = 4;
            });

            expect(getMultiListItems("c1")).toEqual(['c1:2', 'c1:3']);
            expect(getMultiListItems("c2")).toEqual(['c2:6', 'c2:7']);
        });

        it('should throw an exception if a non-existant paginationId is set in the pagination-controls', function() {
            $scope.collection = [1,2,3,4,5];

            function compile() {
                var html = '<ul class="list"><li dir-paginate="item in collection | itemsPerPage: 3 : \'id1\'" pagination-id="id1" >{{ item }}</li></ul> ' +
                    '<dir-pagination-controls pagination-id="id2"></dir-pagination-controls>';

                containingElement.append($compile(html)($scope));
                $scope.$apply();
            }

            expect(compile).toThrow("pagination directive: the pagination controls (id: id2) cannot be used without the corresponding pagination directive.");
        });

        it('should throw an exception if a non-existant paginationId is set in the itemsPerPage filter', function() {
            $scope.collection = [1,2,3,4,5];

            function compile() {
                var html = '<ul class="list"><li dir-paginate="item in collection | itemsPerPage: 3 : \'id2\'" pagination-id="id1" >{{ item }}</li></ul> ' +
                    '<dir-pagination-controls pagination-id="id1"></dir-pagination-controls>';

                containingElement.append($compile(html)($scope));
                $scope.$apply();
            }

            expect(compile).toThrow("pagination directive: the itemsPerPage id argument (id: id2) does not match a registered pagination-id.");
        });

    });

    describe('multi element functionality', function() {

        function compileMultiElement(collection, itemsPerPage, currentPage) {
            var html;
            $scope.collection = collection;
            $scope.itemsPerPage = itemsPerPage || 10;
            $scope.currentPage = currentPage || 1;
            html = '<div>' +
                        '<div dir-paginate-start="item in collection | itemsPerPage: itemsPerPage" current-page="currentPage">header</div>' +
                        '<p>{{ item }}</p>' +
                        '<div dir-paginate-end>footer</div>' +
                    '</div> ';
            containingElement.append($compile(html)($scope));
            $scope.$apply();
        }

        it('should compile with multi element syntax', function() {
            function compile() {
                compileMultiElement([]);
            }
            expect(compile).not.toThrow();
        });

        it('should display the list correctly', function() {
            compileMultiElement(myCollection, 3);
            expect(containingElement.find('p').length).toEqual(3);
        });

        it('should page correctly', function() {
            compileMultiElement(myCollection, 3);

            expect(containingElement.find('p').eq(0).html()).toEqual('item 1');

            $scope.$apply(function() {
                $scope.currentPage = 2;
            });

            expect(containingElement.find('p').eq(0).html()).toEqual('item 4');
        });

        it('should work with data-dir-paginate-start syntax', function() {
            function compile() {
                var html = '<div>' +
                    '<h1 data-dir-paginate-start="item in collection | itemsPerPage: 3">{{ item }}</h1>' +
                    '<p data-dir-paginate-end>stuff</p>' +
                    '</div> ';
                $scope.collection = myCollection;
                containingElement.append($compile(html)($scope));
                $scope.$apply();
            }

            expect(compile).not.toThrow();
            expect(containingElement.find('h1').length).toEqual(3);
            expect(containingElement.find('p').length).toEqual(3);
        });
    });
});
