<?php
/**
 * Pagination class
 * @author Jérôme Brilland
 * @version 3.0
 * @copyright (c) 2015-2021, Jérôme Brilland
 * @license http://www.gnu.org/licenses/gpl.txt GNU General Public License
 */
class Paginator {

    /**
     * @var int Number of items per page
     * @access private
     */
    private $itemsPerPage;

    /**
     * @var int Total number of items
     * @access private
     */
    private $totalItems;

    /**
     * @var int Current page
     * @access private
     */
    private $currentPage;

    /**
     * @var int Number of pages
     * @access private
     */
    private $pageCount;

    /**
     * @var int Offset value
     * @access private
     */
    private $offset;

    /**
     * @var string URL pattern
     * @access private
     */
    private $urlPattern;

    /**
     * @var string
     * @access private
     */
    private $ulClass;

    /**
     * @access private
     */
    private $liDisabledClass;

    /**
     * @access private
     */
    private $liActiveClass;

    /**
     * Class constructor
     * @param integer $itemsPerPage
     * @param integer $totalItems
     * @param integer $currentPage
     * @param string $urlPattern
     */
    public function __construct( int $itemsPerPage, int $totalItems, int $currentPage, string $urlPattern = null ) {
        $this->itemsPerPage = $itemsPerPage;
        $this->totalItems = $totalItems;
        $this->setCurrentPage( $currentPage );
        $this->urlPattern = $urlPattern;
    }

    /**
     * Calculates the number of pages
     * @return int The number of pages
     * @access public
     */
    public function getPageCount() {
        if ( empty( $this->pageCount ) ) {
            if ( $this->itemsPerPage > 0 ) {
                return ceil( $this->totalItems / $this->itemsPerPage );
            } else {
                return 1;
            }
        }
        return $this->pageCount;
    }

    /**
     * Set the current page number
     * @param integer $currentPage
     */
    public function setCurrentPage( int $currentPage ) {
        // Ensures that the requested page number is within range. Note that the default is to start at page 1.
        $pageCount = $this->getPageCount();
        if ( empty( $currentPage ) || $currentPage <= 1 ) {
            $this->currentPage = 1;
        } elseif ( $currentPage > $pageCount ) { 
            $this->currentPage = $pageCount;
        } else {
            $this->currentPage = $currentPage;
        }
    }

    /**
     * Calculates the offset value
     * @access public
     */
    public function getOffset() {
        if ( $this->itemsPerPage > 0 ) {
            $this->offset = ( $this->currentPage - 1 ) * $this->itemsPerPage;
        } else {
            $this->offset = null;
        }
        return $this->offset;
    }

    /**
     * 
     * @return integer
     * @access public
     */
    public function getLimit() {
        return $this->itemsPerPage;
    }

    /**
     * Returns the current page number
     * @return integer
     * @access public
     */
    public function getCurrentPage() {
        return $this->currentPage;
    }

    /**
     * Return the total number of items
     * @return integer
     * @access public
     */
    public function getTotalItems() {
        return $this->totalItems;
    }

    /**
     * @param string $urlPattern
     * @access public
     */
    public function setUrlPattern( string $urlPattern ) {
        $this->urlPattern = $urlPattern;
        return $this;
    }
    
    /**
     * 
     * @param integer $page
     * @return boolean
     * @access public
     */
    public function isValidPage( int $page ) {
        if ( !Validator::validateInteger( $page) ) {
            return false;
        }
        return $page > 0 && $page <= $this->getPageCount();
    }

    /**
     * Is the current page the first one?
     * @return boolean
     * @access public
     */
    public function isFirst() {
        return 1 == $this->currentPage;
    }

    /**
     * Is the current page the last one?
     * @return boolean
     * @access public
     */
    public function isLast() {
        return $this->currentPage == $this->pageCount;
    }

    /**
     * @return Paginator
     * @access public
     */
    public function setUlClass( string $ulClass ) {
        $this->ulClass = $ulClass;
        return $this;
    }

    /**
     * @return Paginator
     * @access public
     */
    public function setLiDisabledClass( string $liDisabledClass ) {
        $this->liDisabledClass = $liDisabledClass;
        return $this;
    }

    /**
     * @return Paginator
     * @access public
     */
    public function setLiActiveClass( string $liActiveClass ) {
        $this->liActiveClass = $liActiveClass;
        return $this;
    }

    /**
     * @param integer adjacents How many adjacent pages should be shown on each side?
     * @return string Pagination links
     * @access public
     */
    public function makeMenu( int $adjacents = 3 ) {
        $i18n = IoC::resolve( 'i18n' );
        $html = '<ul class="' . $this->ulClass . '">' . "\n";
        $lastpage = $this->getPageCount();
        if ( $lastpage > 1 ) {
            if ( 1 == $this->currentPage ) {
                $html .= '<li class="' . $this->liDisabledClass . '"><a href="#">' . $i18n->__( 'previous' ) . '</a></li>' . "\n";
            } else {
                $html .= '<li><a href="' . sprintf( $this->urlPattern, $this->currentPage - 1 ) . '">' . $i18n->__( 'previous' ) . '</a></li>' . "\n";
            }

            //not enough pages to bother breaking it up
            if ( $lastpage < 7 + ( $adjacents * 2 ) ) {
                for ( $i = 1; $i <= $lastpage; $i++ ) {
                    $class = ( $i == $this->currentPage ) ? ' class="' . $this->liActiveClass . '"' : '';
                    $html .= '<li' . $class . '><a href="' . sprintf( $this->urlPattern, $i ) . '">' . $i . '</a></li>' . "\n";
                }
            } elseif ( $lastpage >= 7 + ( $adjacents * 2 ) ) {

                //close to beginning; only hide later pages
                if ( $this->currentPage < 1 + ( $adjacents * 3 ) ) {
                    for ( $i = 1; $i <= 4 + ( $adjacents * 2 ); $i++ ) {
                        $class = ( $i == $this->currentPage ) ? ' class="' . $this->liActiveClass . '"' : '';
                        $html .= '<li' . $class . '><a href="' . sprintf( $this->urlPattern, $i ) . '">' . $i . '</a></li>' . "\n";
                    }
                    $html .= '<li style="background-color: transparent;">...</li>' . "\n";
                    $html .= '<li><a href="' . sprintf( $this->urlPattern, $lastpage - 1 ) . '">' . ( $lastpage - 1 ) . '</a></li>' . "\n";
                    $html .= '<li><a href="' . sprintf( $this->urlPattern, $lastpage ) . '">' . $lastpage . '</a></li>' . "\n";

                    //in middle; hide some front and some back    
                } elseif ( $lastpage - ( $adjacents * 2 ) > $this->currentPage && $this->currentPage > ( $adjacents * 2 ) ) {
                    $html .= '<li><a href="' . sprintf( $this->urlPattern, 1 ) . '">1</a></li>' . "\n";
                    $html .= '<li><a href="' . sprintf( $this->urlPattern, 2 ) . '">2</a></li>' . "\n";
                    $html .= '<li style="background-color: transparent;">...</li>' . "\n";
                    for ( $i = $this->currentPage - $adjacents; $i <= $this->currentPage + $adjacents; $i++ ) {
                        $class = ( $i == $this->currentPage ) ? ' class="' . $this->liActiveClass . '"' : '';
                        $html .= '<li' . $class . '><a href="' . sprintf( $this->urlPattern, $i ) . '">' . $i . '</a></li>' . "\n";
                    }
                    $html .= '<li style="background-color: transparent;">...</li>' . "\n";
                    $html .= '<li><a href="' . sprintf( $this->urlPattern, $lastpage - 1 ) . '">' . ( $lastpage - 1 ) . '</a></li>' . "\n";
                    $html .= '<li><a href="' . sprintf( $this->urlPattern, $lastpage ) . '">' . $lastpage . '</a></li>' . "\n";

                    //close to end; only hide early pages    
                } else {
                    $html .= '<li><a href="' . sprintf( $this->urlPattern, 1 ) . '">1</a></li>' . "\n";
                    $html .= '<li><a href="' . sprintf( $this->urlPattern, 2 ) . '">2</a></li>' . "\n";
                    $html .= '<li style="background-color: transparent;">...</li>' . "\n";
                    for ( $i = $lastpage - ( 1 + ( $adjacents * 3 ) ); $i <= $lastpage; $i++ ) {
                        $class = ( $i == $this->currentPage ) ? ' class="' . $this->liActiveClass . '"' : '';
                        $html .= '<li' . $class . '><a href="' . sprintf( $this->urlPattern, $i ) . '">' . $i . '</a></li>' . "\n";
                    }
                }
            }
            if ( $this->currentPage == $lastpage ) {
                $html .= '<li class="' . $this->liDisabledClass . '"><a href="#">' . $i18n->__( 'next' ) . '</a></li>' . "\n";
            } else {
                $html .= '<li><a href="' . sprintf( $this->urlPattern, $this->currentPage + 1 ) . '">' . $i18n->__( 'next' ) . '</a></li>' . "\n";
            }
        }
        $html .= '</ul>' . "\n";
        return $html;
    }

    public function __toString() {
        return $this->makeMenu();
    }

}

