<?php

/**
 * This file is part of the FreeDSx LDAP package.
 *
 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FreeDSx\Ldap\Server\Paging;

use DateTime;
use DateTimeInterface;
use FreeDSx\Ldap\Control\ControlBag;
use FreeDSx\Ldap\Control\PagingControl;
use FreeDSx\Ldap\Operation\Request\SearchRequest;

/**
 * Encapsulates the paging request from a client and provides some helpful methods.
 *
 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
 */
final class PagingRequest
{
    /**
     * @var PagingControl
     */
    private $control;

    /**
     * @var SearchRequest
     */
    private $request;

    /**
     * @var ControlBag
     */
    private $controls;

    /**
     * @var int
     */
    private $iteration = 1;

    /**
     * @var string
     */
    private $nextCookie;

    /**
     * @var DateTimeInterface|null
     */
    private $lastProcessed = null;

    /**
     * @var DateTimeInterface
     */
    private $created;

    /**
     * @var string
     */
    private $uniqueId;

    /**
     * @var bool
     */
    private $hasProcessed = false;

    public function __construct(
        PagingControl $control,
        SearchRequest $request,
        ControlBag $controls,
        string $nextCookie,
        ?DateTimeInterface $created = null,
        ?string $uniqueId = null
    ) {
        $this->control = $control;
        $this->request = $request;
        $this->controls = $controls;
        $this->nextCookie = $nextCookie;
        $this->created = $created ?? new DateTime();
        $this->uniqueId = $uniqueId ?? random_bytes(16);
    }

    /**
     * An opaque value that uniquely identifies this paging request across its lifecycle.
     *
     * @return string
     */
    public function getUniqueId(): string
    {
        return $this->uniqueId;
    }

    /**
     * Whether the paging control is considered critical or not.
     *
     * @return bool
     */
    public function isCritical(): bool
    {
        return $this->control->getCriticality();
    }

    /**
     * When the paging request was originally created.
     *
     * @return DateTimeInterface
     */
    public function createdAt(): DateTimeInterface
    {
        return $this->created;
    }

    /**
     * When the request was last processed. May be null if not processed yet.
     *
     * @return DateTimeInterface|null
     */
    public function lastProcessedAt(): ?DateTimeInterface
    {
        return $this->lastProcessed;
    }

    /**
     * @return SearchRequest
     */
    public function getSearchRequest(): SearchRequest
    {
        return $this->request;
    }

    /**
     * @return ControlBag
     */
    public function controls(): ControlBag
    {
        return $this->controls;
    }

    /**
     * @return string
     */
    public function getCookie(): string
    {
        return $this->control->getCookie();
    }

    /**
     * The current iteration of paging that this request represents. Incremented by one for each request.
     *
     * @return int
     */
    public function getIteration(): int
    {
        return $this->iteration;
    }

    /**
     * The size of the result set to return, as requested by the client.
     *
     * @return int
     */
    public function getSize(): int
    {
        return $this->control->getSize();
    }

    /**
     * Whether this represents a request to abandon the paging request.
     *
     * @return bool
     */
    public function isAbandonRequest(): bool
    {
        return $this->control->getSize() === 0
            && $this->control->getCookie() !== '';
    }

    /**
     * Whether this represents the start of an unprocessed paging request.
     *
     * @return bool
     */
    public function isPagingStart(): bool
    {
        if ($this->hasProcessed) {
            return false;
        }

        return $this->control->getCookie() === ''
            && $this->control->getSize() >= 0;
    }

    /**
     * @return string
     * @internal
     */
    public function getNextCookie(): string
    {
        return $this->nextCookie;
    }

    /**
     * @param PagingControl $pagingControl
     * @internal
     */
    public function updatePagingControl(PagingControl $pagingControl): void
    {
        $this->control = $pagingControl;
    }

    /**
     * @param string $cookie
     * @internal
     */
    public function updateNextCookie(string $cookie): void
    {
        $this->nextCookie = $cookie;
    }

    /**
     * @internal
     */
    public function markProcessed(): void
    {
        $this->lastProcessed = new DateTime();
        $this->iteration++;
        $this->hasProcessed = true;
    }
}