<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\MainBundle\Pagination;

use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

/**
 * Standard paginator class.
 *
 * Represent a set of paginated pages;
 */
class Paginator implements PaginatorInterface
{
    /**
     * @param string[] $routeParameters
     */
    public function __construct(
        /**
         * The number of total items.
         */
        protected int $totalItems,
        /**
         * the number of items on a single page.
         */
        protected int $itemPerPage,
        /**
         * The number of the current page.
         */
        protected int $currentPageNumber,
        /**
         * the route of the pages.
         */
        protected string $route,
        /**
         * the parameters of the route.
         */
        protected array $routeParameters,
        /**
         * the generator for url.
         */
        protected UrlGeneratorInterface $urlGenerator,
        /**
         * the key in the GET parameter to indicate the page number in
         * generated routes.
         */
        protected string $pageKey,
        /**
         * the key in the GET parameter to indicate the number of item per page.
         */
        protected string $itemPerPageKey,
    ) {}

    public function count(): int
    {
        return $this->countPages();
    }

    public function countPages(): int
    {
        if (0 === $this->itemPerPage) {
            return 1;
        }

        if (0 === $this->totalItems) {
            return 1;
        }

        $nb = floor($this->totalItems / $this->itemPerPage);

        if ($this->totalItems % $this->itemPerPage > 0) {
            ++$nb;
        }

        return 0 === $nb ? 1 : (int) $nb;
    }

    public function getCurrentPage(): Page
    {
        return $this->getPage($this->currentPageNumber);
    }

    public function getCurrentPageFirstItemNumber(): int
    {
        return $this->getCurrentPage()->getFirstItemNumber();
    }

    public function getItemsPerPage(): int
    {
        return $this->itemPerPage;
    }

    /**
     * @throws \RuntimeException if the next page does not exists
     */
    public function getNextPage(): Page
    {
        if (!$this->hasNextPage()) {
            throw new \RuntimeException('this page has no next page');
        }

        return $this->getPage($this->currentPageNumber + 1);
    }

    public function getPage(int $number): Page
    {
        if (!$this->hasPage($number)) {
            throw new \RuntimeException("The page with number {$number} does not ".'exists');
        }

        return new Page(
            $number,
            $this->itemPerPage,
            $this->urlGenerator,
            $this->route,
            array_merge($this->routeParameters, [
                $this->pageKey => $number,
                $this->itemPerPageKey => $this->itemPerPage,
            ]),
            $this->totalItems
        );
    }

    public function getPagesGenerator(): iterable
    {
        for ($i = 1; $this->countPages() >= $i; ++$i) {
            yield $this->getPage($i);
        }
    }

    /**
     * @return Page
     *
     * @throws \RuntimeException if the next page does not exists
     */
    public function getPreviousPage(): PageInterface
    {
        if (!$this->hasPreviousPage()) {
            throw new \RuntimeException('this page has no previous page');
        }

        return $this->getPage($this->currentPageNumber - 1);
    }

    public function getTotalItems(): int
    {
        return $this->totalItems;
    }

    public function hasNextPage(): bool
    {
        return $this->hasPage($this->currentPageNumber + 1);
    }

    public function hasPage($number): bool
    {
        if (0 === $this->totalItems) {
            return 1 === $number;
        }

        return 0 < $number
              && $this->countPages() >= $number;
    }

    public function hasPreviousPage(): bool
    {
        return $this->hasPage($this->currentPageNumber - 1);
    }

    public function isCurrentPage(PageInterface $page): bool
    {
        return $page->getNumber() === $this->currentPageNumber;
    }

    public function setItemsPerPage(int $itemsPerPage)
    {
        $this->itemPerPage = $itemsPerPage;
    }
}
