import React, {Component, Fragment} from 'react';

const LEFT_PAGE = 'LEFT';
const RIGHT_PAGE = 'RIGHT';

/**
 * Helper method for creating a range of numbers
 * range(1, 5) => [1, 2, 3, 4, 5]
 */
const range = (from, to, step = 1) => {
    let i = from;
    const range = [];

    while (i <= to) {
        range.push(i);
        i += step;
    }

    return range;
}

export default class Paginate extends Component {
    constructor(props) {
        super(props);

        this.loadProps = this.loadProps.bind(this);
        this.gotoPage = this.gotoPage.bind(this);
        this.handleClick = this.handleClick.bind(this);
        this.handleMoveLeft = this.handleMoveLeft.bind(this);
        this.handleMoveRight = this.handleMoveRight.bind(this);
        this.makePageLink = this.makePageLink.bind(this);
        this.fetchPageNumbers = this.fetchPageNumbers.bind(this);

        this.loadProps();
        this.state = {currentPage: this.currentPage};
    }

    loadProps() {
        const {totalRecords = 0, pageLimit = 30, pageNeighbours = 0, currentPage = 1} = this.props;

        this.pageLimit = typeof pageLimit === 'number' ? pageLimit : 30;
        this.totalRecords = typeof totalRecords === 'number' ? totalRecords : 0;
        this.pageNeighbours = typeof pageNeighbours === 'number' ? Math.max(0, Math.min(pageNeighbours, 2)) : 0;
        this.totalPages = Math.ceil(this.totalRecords / this.pageLimit);
        this.currentPage = typeof currentPage === 'number' ? currentPage : 1
    }

    gotoPage(page) {
        const {onPageChanged = f => f} = this.props;
        const currentPage = Math.max(0, Math.min(page, this.totalPages));
        this.setState({currentPage: currentPage}, () => onPageChanged(page));
    }

    handleClick(page) {
        return (evt) => {
            evt.preventDefault();
            this.gotoPage(page);
        }
    }

    handleMoveLeft(evt) {
        evt.preventDefault();
        this.gotoPage(this.state.currentPage - 1);
    }

    handleMoveRight(evt) {
        evt.preventDefault();
        this.gotoPage(this.state.currentPage + 1);
    }

    makePageLink(page) {
        const {makeLink = f => f} = this.props;
        return makeLink(page);
    }

    /**
     * Let's say we have 10 pages and we set pageNeighbours to 2
     * Given that the current page is 6
     * The pagination control will look like the following:
     *
     * (1) < {4 5} [6] {7 8} > (10)
     *
     * (x) => terminal pages: first and last page(always visible)
     * [x] => represents current page
     * {...x} => represents page neighbours
     */
    fetchPageNumbers() {
        const totalPages = this.totalPages;
        const currentPage = this.state.currentPage;
        const pageNeighbours = this.pageNeighbours;

        /**
         * totalNumbers: the total page numbers to show on the control
         * totalBlocks: totalNumbers + 2 to cover for the left(<) and right(>) controls
         */
        const totalNumbers = (this.pageNeighbours * 2) + 3;
        const totalBlocks = totalNumbers + 2;

        if (totalPages > totalBlocks) {
            const startPage = Math.max(2, currentPage - pageNeighbours);
            const endPage = Math.min(totalPages - 1, currentPage + pageNeighbours);

            let pages = range(startPage, endPage);

            /**
             * hasLeftSpill: has hidden pages to the left
             * hasRightSpill: has hidden pages to the right
             * spillOffset: number of hidden pages either to the left or to the right
             */
            const hasLeftSpill = startPage > 2;
            const hasRightSpill = (totalPages - endPage) > 1;
            const spillOffset = totalNumbers - (pages.length + 1);

            // handle: (1) < {5 6} [7] {8 9} (10)
            if (hasLeftSpill && !hasRightSpill) {
                const extraPages = range(startPage - spillOffset, startPage - 1);
                pages = [LEFT_PAGE, ...extraPages, ...pages];
                // handle: (1) {2 3} [4] {5 6} > (10)
            } else if (!hasLeftSpill && hasRightSpill) {
                const extraPages = range(endPage + 1, endPage + spillOffset);
                pages = [...pages, ...extraPages, RIGHT_PAGE];
                // handle: (1) < {4 5} [6] {7 8} > (10)
            } else if (hasLeftSpill && hasRightSpill) {
                pages = [LEFT_PAGE, ...pages, RIGHT_PAGE];
            }

            return [1, ...pages, totalPages];

        }

        return range(1, totalPages);

    }

    render() {
        this.loadProps();

        if (!this.totalRecords || this.totalPages === 1) return null;

        const currentPage = this.state.currentPage;
        const pages = this.fetchPageNumbers();
        const blocks = pages.map((page, index) => {
            if (page === LEFT_PAGE) return (
                <li key={page} className="page-item">
                    <a className="page-link" href={this.makePageLink(this.state.currentPage - 1)} onClick={this.handleMoveLeft}>
                        <span>&laquo;</span>
                    </a>
                </li>
            );
            if (page === RIGHT_PAGE) return (
                <li key={page} className="page-item">
                    <a className="page-link" href={this.makePageLink(this.state.currentPage + 1)} onClick={this.handleMoveRight}>
                        <span>&raquo;</span>
                    </a>
                </li>
            );
            return (
                <li key={page} className={`page-item ${currentPage === page ? 'active' : ''}`}>
                    <a className="page-link" href={this.makePageLink(page)} onClick={this.handleClick(page)}>{page}</a>
                </li>
            );
        })

        return (
            <Fragment>
                <ul className="pagination">
                    {blocks}
                </ul>
            </Fragment>
        );

    }
}

