/*
 * pagination.js 2.1.5
 * A jQuery plugin to provide simple yet fully customisable pagination.
 * https://github.com/superRaytin/paginationjs
 *
 * Homepage: http://pagination.js.org
 *
 * Copyright 2014-2100, superRaytin
 * Released under the MIT license.
 */

(function (global, $) {

    if (typeof $ === 'undefined') {
        throwError('Pagination requires jQuery.');
    }

    var pluginName = 'pagination';

    var pluginHookMethod = 'addHook';

    var eventPrefix = '__pagination-';

    // Conflict, use backup
    if ($.fn.pagination) {
        pluginName = 'pagination2';
    }

    $.fn[pluginName] = function (options) {

        if (typeof options === 'undefined') {
            return this;
        }

        var container = $(this);

        var attributes = $.extend({}, $.fn[pluginName].defaults, options);

        var pagination = {

            initialize: function () {
                var self = this;

                // Cache attributes of current instance
                if (!container.data('pagination')) {
                    container.data('pagination', {});
                }

                if (self.callHook('beforeInit') === false) return;

                // Pagination has been initialized, destroy it
                if (container.data('pagination').initialized) {
                    $('.paginationjs', container).remove();
                }

                // Whether to disable Pagination at the initialization
                self.disabled = !!attributes.disabled;

                // Model will be passed to the callback function
                var model = self.model = {
                    pageRange: attributes.pageRange,
                    pageSize: attributes.pageSize
                };

                // dataSource`s type is unknown, parse it to find true data
                self.parseDataSource(attributes.dataSource, function (dataSource) {

                    // Currently in asynchronous mode
                    self.isAsync = Helpers.isString(dataSource);
                    if (Helpers.isArray(dataSource)) {
                        model.totalNumber = attributes.totalNumber = dataSource.length;
                    }

                    // Currently in asynchronous mode and a totalNumberLocator is specified
                    self.isDynamicTotalNumber = self.isAsync && attributes.totalNumberLocator;

                    var el = self.render(true);

                    // Add extra className to the pagination element
                    if (attributes.className) {
                        el.addClass(attributes.className);
                    }

                    model.el = el;

                    // Append/prepend pagination element to the container
                    container[attributes.position === 'bottom' ? 'append' : 'prepend'](el);

                    // Bind events
                    self.observer();

                    // Pagination is currently initialized
                    container.data('pagination').initialized = true;

                    // Will be invoked after initialized
                    self.callHook('afterInit', el);
                });
            },

            render: function (isBoot) {
                var self = this;
                var model = self.model;
                var el = model.el || $('<div class="paginationjs"></div>');
                var isForced = isBoot !== true;

                self.callHook('beforeRender', isForced);

                var currentPage = model.pageNumber || attributes.pageNumber;
                var pageRange = attributes.pageRange || 0;
                var totalPage = self.getTotalPage();

                var rangeStart = currentPage - pageRange;
                var rangeEnd = currentPage + pageRange;

                if (rangeEnd > totalPage) {
                    rangeEnd = totalPage;
                    rangeStart = totalPage - pageRange * 2;
                    rangeStart = rangeStart < 1 ? 1 : rangeStart;
                }

                if (rangeStart <= 1) {
                    rangeStart = 1;
                    rangeEnd = Math.min(pageRange * 2 + 1, totalPage);
                }

                el.html(self.generateHTML({
                    currentPage: currentPage,
                    pageRange: pageRange,
                    rangeStart: rangeStart,
                    rangeEnd: rangeEnd
                }));

                // There is only one page
                if (attributes.hideWhenLessThanOnePage) {
                    el[totalPage <= 1 ? 'hide' : 'show']();
                }

                self.callHook('afterRender', isForced);

                return el;
            },

            // Generate HTML of the pages
            generatePageNumbersHTML: function (args) {
                var self = this;
                var currentPage = args.currentPage;
                var totalPage = self.getTotalPage();
                var rangeStart = args.rangeStart;
                var rangeEnd = args.rangeEnd;
                var html = '';
                var i;

                var pageLink = attributes.pageLink;
                var ellipsisText = attributes.ellipsisText;

                var classPrefix = attributes.classPrefix;
                var activeClassName = attributes.activeClassName;
                var disableClassName = attributes.disableClassName;

                // Disable page range, display all the pages
                if (attributes.pageRange === null) {
                    for (i = 1; i <= totalPage; i++) {
                        if (i == currentPage) {
                            html += '<li class="' + classPrefix + '-page J-paginationjs-page ' + activeClassName + '" data-num="' + i + '"><a>' + i + '<\/a><\/li>';
                        } else {
                            html += '<li class="' + classPrefix + '-page J-paginationjs-page" data-num="' + i + '"><a href="' + pageLink + '">' + i + '<\/a><\/li>';
                        }
                    }
                    return html;
                }

                if (rangeStart <= 3) {
                    for (i = 1; i < rangeStart; i++) {
                        if (i == currentPage) {
                            html += '<li class="' + classPrefix + '-page J-paginationjs-page ' + activeClassName + '" data-num="' + i + '"><a>' + i + '<\/a><\/li>';
                        } else {
                            html += '<li class="' + classPrefix + '-page J-paginationjs-page" data-num="' + i + '"><a href="' + pageLink + '">' + i + '<\/a><\/li>';
                        }
                    }
                } else {
                    if (attributes.showFirstOnEllipsisShow) {
                        html += '<li class="' + classPrefix + '-page ' + classPrefix + '-first J-paginationjs-page" data-num="1"><a href="' + pageLink + '">1<\/a><\/li>';
                    }
                    html += '<li class="' + classPrefix + '-ellipsis ' + disableClassName + '"><a>' + ellipsisText + '<\/a><\/li>';
                }

                for (i = rangeStart; i <= rangeEnd; i++) {
                    if (i == currentPage) {
                        html += '<li class="' + classPrefix + '-page J-paginationjs-page ' + activeClassName + '" data-num="' + i + '"><a>' + i + '<\/a><\/li>';
                    } else {
                        html += '<li class="' + classPrefix + '-page J-paginationjs-page" data-num="' + i + '"><a href="' + pageLink + '">' + i + '<\/a><\/li>';
                    }
                }

                if (rangeEnd >= totalPage - 2) {
                    for (i = rangeEnd + 1; i <= totalPage; i++) {
                        html += '<li class="' + classPrefix + '-page J-paginationjs-page" data-num="' + i + '"><a href="' + pageLink + '">' + i + '<\/a><\/li>';
                    }
                } else {
                    html += '<li class="' + classPrefix + '-ellipsis ' + disableClassName + '"><a>' + ellipsisText + '<\/a><\/li>';

                    if (attributes.showLastOnEllipsisShow) {
                        html += '<li class="' + classPrefix + '-page ' + classPrefix + '-last J-paginationjs-page" data-num="' + totalPage + '"><a href="' + pageLink + '">' + totalPage + '<\/a><\/li>';
                    }
                }

                return html;
            },

            // Generate HTML content from the template
            generateHTML: function (args) {
                var self = this;
                var currentPage = args.currentPage;
                var totalPage = self.getTotalPage();

                var totalNumber = self.getTotalNumber();

                var showPrevious = attributes.showPrevious;
                var showNext = attributes.showNext;
                var showPageNumbers = attributes.showPageNumbers;
                var showNavigator = attributes.showNavigator;
                var showGoInput = attributes.showGoInput;
                var showGoButton = attributes.showGoButton;

                var pageLink = attributes.pageLink;
                var prevText = attributes.prevText;
                var nextText = attributes.nextText;
                var goButtonText = attributes.goButtonText;

                var classPrefix = attributes.classPrefix;
                var disableClassName = attributes.disableClassName;
                var ulClassName = attributes.ulClassName;

                var html = '';
                var goInput = '<input type="text" class="J-paginationjs-go-pagenumber">';
                var goButton = '<input type="button" class="J-paginationjs-go-button" value="' + goButtonText + '">';
                var formattedString;

                var formatNavigator = $.isFunction(attributes.formatNavigator) ? attributes.formatNavigator(currentPage, totalPage, totalNumber) : attributes.formatNavigator;
                var formatGoInput = $.isFunction(attributes.formatGoInput) ? attributes.formatGoInput(goInput, currentPage, totalPage, totalNumber) : attributes.formatGoInput;
                var formatGoButton = $.isFunction(attributes.formatGoButton) ? attributes.formatGoButton(goButton, currentPage, totalPage, totalNumber) : attributes.formatGoButton;

                var autoHidePrevious = $.isFunction(attributes.autoHidePrevious) ? attributes.autoHidePrevious() : attributes.autoHidePrevious;
                var autoHideNext = $.isFunction(attributes.autoHideNext) ? attributes.autoHideNext() : attributes.autoHideNext;

                var header = $.isFunction(attributes.header) ? attributes.header(currentPage, totalPage, totalNumber) : attributes.header;
                var footer = $.isFunction(attributes.footer) ? attributes.footer(currentPage, totalPage, totalNumber) : attributes.footer;

                // Whether to display header
                if (header) {
                    formattedString = self.replaceVariables(header, {
                        currentPage: currentPage,
                        totalPage: totalPage,
                        totalNumber: totalNumber
                    });
                    html += formattedString;
                }

                if (showPrevious || showPageNumbers || showNext) {
                    html += '<div class="paginationjs-pages">';

                    if (ulClassName) {
                        html += '<ul class="' + ulClassName + '">';
                    } else {
                        html += '<ul>';
                    }

                    // Whether to display the Previous button
                    if (showPrevious) {
                        if (currentPage <= 1) {
                            if (!autoHidePrevious) {
                                html += '<li class="' + classPrefix + '-prev ' + disableClassName + '"><a>' + prevText + '<\/a><\/li>';
                            }
                        } else {
                            html += '<li class="' + classPrefix + '-prev J-paginationjs-previous" data-num="' + (currentPage - 1) + '" title="Previous page"><a href="' + pageLink + '">' + prevText + '<\/a><\/li>';
                        }
                    }

                    // Whether to display the pages
                    if (showPageNumbers) {
                        html += self.generatePageNumbersHTML(args);
                    }

                    // Whether to display the Next button
                    if (showNext) {
                        if (currentPage >= totalPage) {
                            if (!autoHideNext) {
                                html += '<li class="' + classPrefix + '-next ' + disableClassName + '"><a>' + nextText + '<\/a><\/li>';
                            }
                        } else {
                            html += '<li class="' + classPrefix + '-next J-paginationjs-next" data-num="' + (currentPage + 1) + '" title="Next page"><a href="' + pageLink + '">' + nextText + '<\/a><\/li>';
                        }
                    }
                    html += '<\/ul><\/div>';
                }

                // Whether to display the navigator
                if (showNavigator) {
                    if (formatNavigator) {
                        formattedString = self.replaceVariables(formatNavigator, {
                            currentPage: currentPage,
                            totalPage: totalPage,
                            totalNumber: totalNumber
                        });
                        html += '<div class="' + classPrefix + '-nav J-paginationjs-nav">' + formattedString + '<\/div>';
                    }
                }

                // Whether to display the Go input
                if (showGoInput) {
                    if (formatGoInput) {
                        formattedString = self.replaceVariables(formatGoInput, {
                            currentPage: currentPage,
                            totalPage: totalPage,
                            totalNumber: totalNumber,
                            input: goInput
                        });
                        html += '<div class="' + classPrefix + '-go-input">' + formattedString + '</div>';
                    }
                }

                // Whether to display the Go button
                if (showGoButton) {
                    if (formatGoButton) {
                        formattedString = self.replaceVariables(formatGoButton, {
                            currentPage: currentPage,
                            totalPage: totalPage,
                            totalNumber: totalNumber,
                            button: goButton
                        });
                        html += '<div class="' + classPrefix + '-go-button">' + formattedString + '</div>';
                    }
                }

                // Whether to display footer
                if (footer) {
                    formattedString = self.replaceVariables(footer, {
                        currentPage: currentPage,
                        totalPage: totalPage,
                        totalNumber: totalNumber
                    });
                    html += formattedString;
                }

                return html;
            },

            // Find totalNumber from the remote response
            // Only available in asynchronous mode
            findTotalNumberFromRemoteResponse: function (response) {
                var self = this;
                self.model.totalNumber = attributes.totalNumberLocator(response);
            },

            // Go to the specified page
            go: function (number, callback) {
                var self = this;
                var model = self.model;

                if (self.disabled) return;

                var pageNumber = number;
                pageNumber = parseInt(pageNumber);

                // Page number is out of bounds
                if (!pageNumber || pageNumber < 1) return;

                var pageSize = attributes.pageSize;
                var totalNumber = self.getTotalNumber();
                var totalPage = self.getTotalPage();

                // Page number is out of bounds
                if (totalNumber > 0) {
                    if (pageNumber > totalPage) return;
                }

                // Pick data fragment in synchronous mode
                if (!self.isAsync) {
                    render(self.getDataFragment(pageNumber));
                    return;
                }

                var postData = {};
                var alias = attributes.alias || {};
                postData[alias.pageSize ? alias.pageSize : 'pageSize'] = pageSize;
                postData[alias.pageNumber ? alias.pageNumber : 'pageNumber'] = pageNumber;

                var ajaxParams = $.isFunction(attributes.ajax) ? attributes.ajax() : attributes.ajax;
                var formatAjaxParams = {
                    type: 'get',
                    cache: false,
                    data: {},
                    contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
                    dataType: 'json',
                    async: true
                };

                $.extend(true, formatAjaxParams, ajaxParams);
                $.extend(formatAjaxParams.data, postData);

                formatAjaxParams.url = attributes.dataSource;
                formatAjaxParams.success = function (response) {
                    if (self.isDynamicTotalNumber) {
                        self.findTotalNumberFromRemoteResponse(response);
                    } else {
                        self.model.totalNumber = attributes.totalNumber;
                    }

                    var finalData = self.filterDataByLocator(response);
                    render(finalData);
                };
                formatAjaxParams.error = function (jqXHR, textStatus, errorThrown) {
                    attributes.formatAjaxError && attributes.formatAjaxError(jqXHR, textStatus, errorThrown);
                    self.enable();
                };

                self.disable();

                $.ajax(formatAjaxParams);

                function render(data) {
                    // Will be invoked before paging
                    if (self.callHook('beforePaging', pageNumber) === false) return false;

                    // Pagination direction
                    model.direction = typeof model.pageNumber === 'undefined' ? 0 : (pageNumber > model.pageNumber ? 1 : -1);

                    model.pageNumber = pageNumber;

                    self.render();

                    if (self.disabled && self.isAsync) {
                        // enable pagination
                        self.enable();
                    }

                    // cache model data
                    container.data('pagination').model = model;

                    // format result data before callback invoked
                    if (attributes.formatResult) {
                        var cloneData = $.extend(true, [], data);
                        if (!Helpers.isArray(data = attributes.formatResult(cloneData))) {
                            data = cloneData;
                        }
                    }

                    container.data('pagination').currentPageData = data;

                    // invoke callback
                    self.doCallback(data, callback);

                    self.callHook('afterPaging', pageNumber);

                    // pageNumber now is the first page
                    if (pageNumber == 1) {
                        self.callHook('afterIsFirstPage');
                    }

                    // pageNumber now is the last page
                    if (pageNumber == self.getTotalPage()) {
                        self.callHook('afterIsLastPage');
                    }
                }
            },

            doCallback: function (data, customCallback) {
                var self = this;
                var model = self.model;

                if ($.isFunction(customCallback)) {
                    customCallback(data, model);
                } else if ($.isFunction(attributes.callback)) {
                    attributes.callback(data, model);
                }
            },

            destroy: function () {
                // Before destroy
                if (this.callHook('beforeDestroy') === false) return;

                this.model.el.remove();
                container.off();

                // Remove style element
                $('#paginationjs-style').remove();

                // After destroyed
                this.callHook('afterDestroy');
            },

            previous: function (callback) {
                this.go(this.model.pageNumber - 1, callback);
            },

            next: function (callback) {
                this.go(this.model.pageNumber + 1, callback);
            },

            disable: function () {
                var self = this;
                var source = self.isAsync ? 'async' : 'sync';

                // Before disabled
                if (self.callHook('beforeDisable', source) === false) return;

                self.disabled = true;
                self.model.disabled = true;

                // After disabled
                self.callHook('afterDisable', source);
            },

            enable: function () {
                var self = this;
                var source = self.isAsync ? 'async' : 'sync';

                // Before enabled
                if (self.callHook('beforeEnable', source) === false) return;

                self.disabled = false;
                self.model.disabled = false;

                // After enabled
                self.callHook('afterEnable', source);
            },

            refresh: function (callback) {
                this.go(this.model.pageNumber, callback);
            },

            show: function () {
                var self = this;

                if (self.model.el.is(':visible')) return;

                self.model.el.show();
            },

            hide: function () {
                var self = this;

                if (!self.model.el.is(':visible')) return;

                self.model.el.hide();
            },

            // Parse variables in the template
            replaceVariables: function (template, variables) {
                var formattedString;

                for (var key in variables) {
                    var value = variables[key];
                    var regexp = new RegExp('<%=\\s*' + key + '\\s*%>', 'img');

                    formattedString = (formattedString || template).replace(regexp, value);
                }

                return formattedString;
            },

            // Get data fragment
            getDataFragment: function (number) {
                var pageSize = attributes.pageSize;
                var dataSource = attributes.dataSource;
                var totalNumber = this.getTotalNumber();

                var start = pageSize * (number - 1) + 1;
                var end = Math.min(number * pageSize, totalNumber);

                return dataSource.slice(start - 1, end);
            },

            // Get total number
            getTotalNumber: function () {
                return this.model.totalNumber || attributes.totalNumber || 0;
            },

            // Get total page
            getTotalPage: function () {
                return Math.ceil(this.getTotalNumber() / attributes.pageSize);
            },

            // Get locator
            getLocator: function (locator) {
                var result;

                if (typeof locator === 'string') {
                    result = locator;
                } else if ($.isFunction(locator)) {
                    result = locator();
                } else {
                    throwError('"locator" is incorrect. (String | Function)');
                }

                return result;
            },

            // Filter data by "locator"
            filterDataByLocator: function (dataSource) {
                var locator = this.getLocator(attributes.locator);
                var filteredData;

                // Datasource is an Object, use "locator" to locate the true data
                if (Helpers.isObject(dataSource)) {
                    try {
                        $.each(locator.split('.'), function (index, item) {
                            filteredData = (filteredData ? filteredData : dataSource)[item];
                        });
                    }
                    catch (e) {
                    }

                    if (!filteredData) {
                        throwError('dataSource.' + locator + ' is undefined.');
                    } else if (!Helpers.isArray(filteredData)) {
                        throwError('dataSource.' + locator + ' must be an Array.');
                    }
                }

                return filteredData || dataSource;
            },

            // Parse dataSource
            parseDataSource: function (dataSource, callback) {
                var self = this;

                if (Helpers.isObject(dataSource)) {
                    callback(attributes.dataSource = self.filterDataByLocator(dataSource));
                } else if (Helpers.isArray(dataSource)) {
                    callback(attributes.dataSource = dataSource);
                } else if ($.isFunction(dataSource)) {
                    attributes.dataSource(function (data) {
                        if (!Helpers.isArray(data)) {
                            throwError('The parameter of "done" Function should be an Array.');
                        }
                        self.parseDataSource.call(self, data, callback);
                    });
                } else if (typeof dataSource === 'string') {
                    if (/^https?|file:/.test(dataSource)) {
                        attributes.ajaxDataType = 'jsonp';
                    }
                    callback(dataSource);
                } else {
                    throwError('Unexpected type of "dataSource".');
                }
            },

            callHook: function (hook) {
                var paginationData = container.data('pagination');
                var result;

                var args = Array.prototype.slice.apply(arguments);
                args.shift();

                if (attributes[hook] && $.isFunction(attributes[hook])) {
                    if (attributes[hook].apply(global, args) === false) {
                        result = false;
                    }
                }

                if (paginationData.hooks && paginationData.hooks[hook]) {
                    $.each(paginationData.hooks[hook], function (index, item) {
                        if (item.apply(global, args) === false) {
                            result = false;
                        }
                    });
                }

                return result !== false;
            },

            observer: function () {
                var self = this;
                var el = self.model.el;

                // Go to specified page number
                container.on(eventPrefix + 'go', function (event, pageNumber, done) {
                    pageNumber = parseInt($.trim(pageNumber));

                    if (!pageNumber) return;

                    if (!$.isNumeric(pageNumber)) {
                        throwError('"pageNumber" is incorrect. (Number)');
                    }

                    self.go(pageNumber, done);
                });

                // Page number button click
                el.delegate('.J-paginationjs-page', 'click', function (event) {
                    var current = $(event.currentTarget);
                    var pageNumber = $.trim(current.attr('data-num'));

                    if (!pageNumber || current.hasClass(attributes.disableClassName) || current.hasClass(attributes.activeClassName)) return;

                    // Before page button clicked
                    if (self.callHook('beforePageOnClick', event, pageNumber) === false) return false;

                    self.go(pageNumber);

                    // After page button clicked
                    self.callHook('afterPageOnClick', event, pageNumber);

                    if (!attributes.pageLink) return false;
                });

                // Previous button click
                el.delegate('.J-paginationjs-previous', 'click', function (event) {
                    var current = $(event.currentTarget);
                    var pageNumber = $.trim(current.attr('data-num'));

                    if (!pageNumber || current.hasClass(attributes.disableClassName)) return;

                    // Before previous clicked
                    if (self.callHook('beforePreviousOnClick', event, pageNumber) === false) return false;

                    self.go(pageNumber);

                    // After previous clicked
                    self.callHook('afterPreviousOnClick', event, pageNumber);

                    if (!attributes.pageLink) return false;
                });

                // Next button click
                el.delegate('.J-paginationjs-next', 'click', function (event) {
                    var current = $(event.currentTarget);
                    var pageNumber = $.trim(current.attr('data-num'));

                    if (!pageNumber || current.hasClass(attributes.disableClassName)) return;

                    // Before next clicked
                    if (self.callHook('beforeNextOnClick', event, pageNumber) === false) return false;

                    self.go(pageNumber);

                    // After next clicked
                    self.callHook('afterNextOnClick', event, pageNumber);

                    if (!attributes.pageLink) return false;
                });

                // Go button click
                el.delegate('.J-paginationjs-go-button', 'click', function (event) {
                    var pageNumber = $('.J-paginationjs-go-pagenumber', el).val();

                    // Before Go button clicked
                    if (self.callHook('beforeGoButtonOnClick', event, pageNumber) === false) return false;

                    container.trigger(eventPrefix + 'go', pageNumber);

                    // After Go button clicked
                    self.callHook('afterGoButtonOnClick', event, pageNumber);
                });

                // go input enter
                el.delegate('.J-paginationjs-go-pagenumber', 'keyup', function (event) {
                    if (event.which === 13) {
                        var pageNumber = $(event.currentTarget).val();

                        // Before Go input enter
                        if (self.callHook('beforeGoInputOnEnter', event, pageNumber) === false) return false;

                        container.trigger(eventPrefix + 'go', pageNumber);

                        // Regains focus
                        $('.J-paginationjs-go-pagenumber', el).focus();

                        // After Go input enter
                        self.callHook('afterGoInputOnEnter', event, pageNumber);
                    }
                });

                // Previous page
                container.on(eventPrefix + 'previous', function (event, done) {
                    self.previous(done);
                });

                // Next page
                container.on(eventPrefix + 'next', function (event, done) {
                    self.next(done);
                });

                // Disable
                container.on(eventPrefix + 'disable', function () {
                    self.disable();
                });

                // Enable
                container.on(eventPrefix + 'enable', function () {
                    self.enable();
                });

                // Refresh
                container.on(eventPrefix + 'refresh', function (event, done) {
                    self.refresh(done);
                });

                // Show
                container.on(eventPrefix + 'show', function () {
                    self.show();
                });

                // Hide
                container.on(eventPrefix + 'hide', function () {
                    self.hide();
                });

                // Destroy
                container.on(eventPrefix + 'destroy', function () {
                    self.destroy();
                });

                // Whether to load the default page
                var validTotalPage = Math.max(self.getTotalPage(), 1)
                var defaultPageNumber = attributes.pageNumber;
                // Default pageNumber should be 1 when totalNumber is dynamic
                if (self.isDynamicTotalNumber) {
                    defaultPageNumber = 1;
                }
                if (attributes.triggerPagingOnInit) {
                    container.trigger(eventPrefix + 'go', Math.min(defaultPageNumber, validTotalPage));
                }
            }
        };

        // Pagination has been initialized
        if (container.data('pagination') && container.data('pagination').initialized === true) {
            // Handle events
            if ($.isNumeric(options)) {
                // eg: container.pagination(5)
                container.trigger.call(this, eventPrefix + 'go', options, arguments[1]);
                return this;
            } else if (typeof options === 'string') {
                var args = Array.prototype.slice.apply(arguments);
                args[0] = eventPrefix + args[0];

                switch (options) {
                    case 'previous':
                    case 'next':
                    case 'go':
                    case 'disable':
                    case 'enable':
                    case 'refresh':
                    case 'show':
                    case 'hide':
                    case 'destroy':
                        container.trigger.apply(this, args);
                        break;
                    // Get selected page number
                    case 'getSelectedPageNum':
                        if (container.data('pagination').model) {
                            return container.data('pagination').model.pageNumber;
                        } else {
                            return container.data('pagination').attributes.pageNumber;
                        }
                    // Get total page
                    case 'getTotalPage':
                        return Math.ceil(container.data('pagination').model.totalNumber / container.data('pagination').model.pageSize);
                    // Get data of selected page
                    case 'getSelectedPageData':
                        return container.data('pagination').currentPageData;
                    // Whether pagination has been disabled
                    case 'isDisabled':
                        return container.data('pagination').model.disabled === true;
                    default:
                        throwError('Unknown action: ' + options);
                }
                return this;
            } else {
                // Uninstall the old instance before initializing a new one
                uninstallPlugin(container);
            }
        } else {
            if (!Helpers.isObject(options)) throwError('Illegal options');
        }

        // Check parameters
        parameterChecker(attributes);

        pagination.initialize();

        return this;
    };

    // Instance defaults
    $.fn[pluginName].defaults = {

        // Data source
        // Array | String | Function | Object
        //dataSource: '',

        // String | Function
        //locator: 'data',

        // Find totalNumber from remote response, the totalNumber will be ignored when totalNumberLocator is specified
        // Function
        //totalNumberLocator: function() {},

        // Total entries
        totalNumber: 0,

        // Default page
        pageNumber: 1,

        // entries of per page
        pageSize: 10,

        // Page range (pages on both sides of the current page)
        pageRange: 2,

        // Whether to display the 'Previous' button
        showPrevious: true,

        // Whether to display the 'Next' button
        showNext: true,

        // Whether to display the page buttons
        showPageNumbers: true,

        showNavigator: false,

        // Whether to display the 'Go' input
        showGoInput: false,

        // Whether to display the 'Go' button
        showGoButton: false,

        // Page link
        pageLink: '',

        // 'Previous' text
        prevText: '&laquo;',

        // 'Next' text
        nextText: '&raquo;',

        // Ellipsis text
        ellipsisText: '...',

        // 'Go' button text
        goButtonText: 'Go',

        // Additional className for Pagination element
        //className: '',

        classPrefix: 'paginationjs',

        // Default active class
        activeClassName: 'active',

        // Default disable class
        disableClassName: 'disabled',

        //ulClassName: '',

        // Whether to insert inline style
        inlineStyle: true,

        formatNavigator: '<%= currentPage %> / <%= totalPage %>',

        formatGoInput: '<%= input %>',

        formatGoButton: '<%= button %>',

        // Pagination element's position in the container
        position: 'bottom',

        // Auto hide previous button when current page is the first page
        autoHidePrevious: false,

        // Auto hide next button when current page is the last page
        autoHideNext: false,

        //header: '',

        //footer: '',

        // Aliases for custom pagination parameters
        //alias: {},

        // Whether to trigger pagination at initialization
        triggerPagingOnInit: true,

        // Whether to hide pagination when less than one page
        hideWhenLessThanOnePage: false,

        showFirstOnEllipsisShow: true,

        showLastOnEllipsisShow: true,

        // Pagination callback
        callback: function () { }
    };

    // Hook register
    $.fn[pluginHookMethod] = function (hook, callback) {
        if (arguments.length < 2) {
            throwError('Missing argument.');
        }

        if (!$.isFunction(callback)) {
            throwError('callback must be a function.');
        }

        var container = $(this);
        var paginationData = container.data('pagination');

        if (!paginationData) {
            container.data('pagination', {});
            paginationData = container.data('pagination');
        }

        !paginationData.hooks && (paginationData.hooks = {});

        //paginationData.hooks[hook] = callback;
        paginationData.hooks[hook] = paginationData.hooks[hook] || [];
        paginationData.hooks[hook].push(callback);

    };

    // Static method
    $[pluginName] = function (selector, options) {
        if (arguments.length < 2) {
            throwError('Requires two parameters.');
        }

        var container;

        // 'selector' is a jQuery object
        if (typeof selector !== 'string' && selector instanceof jQuery) {
            container = selector;
        } else {
            container = $(selector);
        }

        if (!container.length) return;

        container.pagination(options);

        return container;
    };

    // ============================================================
    // helpers
    // ============================================================

    var Helpers = {};

    // Throw error
    function throwError(content) {
        throw new Error('Pagination: ' + content);
    }

    // Check parameters
    function parameterChecker(args) {
        if (!args.dataSource) {
            throwError('"dataSource" is required.');
        }

        if (typeof args.dataSource === 'string') {
            if (args.totalNumberLocator === undefined) {
                if (args.totalNumber === undefined) {
                    throwError('"totalNumber" is required.');
                } else if (!$.isNumeric(args.totalNumber)) {
                    throwError('"totalNumber" is incorrect. (Number)');
                }
            } else {
                if (!$.isFunction(args.totalNumberLocator)) {
                    throwError('"totalNumberLocator" should be a Function.');
                }
            }
        } else if (Helpers.isObject(args.dataSource)) {
            if (typeof args.locator === 'undefined') {
                throwError('"dataSource" is an Object, please specify "locator".');
            } else if (typeof args.locator !== 'string' && !$.isFunction(args.locator)) {
                throwError('' + args.locator + ' is incorrect. (String | Function)');
            }
        }

        if (args.formatResult !== undefined && !$.isFunction(args.formatResult)) {
            throwError('"formatResult" should be a Function.');
        }
    }

    // uninstall plugin
    function uninstallPlugin(target) {
        var events = ['go', 'previous', 'next', 'disable', 'enable', 'refresh', 'show', 'hide', 'destroy'];

        // off events of old instance
        $.each(events, function (index, value) {
            target.off(eventPrefix + value);
        });

        // reset pagination data
        target.data('pagination', {});

        // remove old
        $('.paginationjs', target).remove();
    }

    // Object type detection
    function getObjectType(object, tmp) {
        return ((tmp = typeof (object)) == "object" ? object == null && "null" || Object.prototype.toString.call(object).slice(8, -1) : tmp).toLowerCase();
    }

    $.each(['Object', 'Array', 'String'], function (index, name) {
        Helpers['is' + name] = function (object) {
            return getObjectType(object) === name.toLowerCase();
        };
    });

    /*
     * export via AMD or CommonJS
     * */
    if (typeof define === 'function' && define.amd) {
        define(function () {
            return $;
        });
    }

})(this, window.jQuery);
