<?php declare(strict_types=1);

namespace Newland\Toubiz\Map\Neos\Tests\Unit\Pagination;

use Neos\Flow\Tests\UnitTestCase;
use Newland\Toubiz\Map\Neos\Provider\Pagination\InvalidPageException;
use Newland\Toubiz\Map\Neos\Provider\Pagination\Paginator;

class PaginatorTest extends UnitTestCase
{

    public function testPassesLimitAndOffsetDown(): void
    {
        $paginator = new Paginator(10);
        $paginator->addProvider(50, function($o, $l) use (&$offset, &$limit) {
            $offset = $o;
            $limit = $l;
        });

        $paginator->get(2);
        $this->assertEquals(10, $offset);
        $this->assertEquals(10, $limit);
    }

    public function testCutsLimitShortIfNumbersInProviderRunOut(): void
    {
        $paginator = new Paginator(10);
        $paginator->addProvider(14, function($o, $l) use (&$offset, &$limit) {
            $offset = $o;
            $limit = $l;
        });

        $paginator->get(2);
        $this->assertEquals(10, $offset);
        $this->assertEquals(4, $limit);
    }

    public function testSplitsOffsetAndLimitAcrossTwoProviders(): void
    {
        $paginator = new Paginator(10);
        $paginator->addProvider(14, function($o, $l) use (&$offset1, &$limit1) {
            $offset1 = $o;
            $limit1 = $l;
        });
        $paginator->addProvider(7, function($o, $l) use (&$offset2, &$limit2) {
            $offset2 = $o;
            $limit2 = $l;
        });

        // Page 2 should contain 4 from first provider and 6 from second provider
        $paginator->get(2);
        $this->assertEquals(10, $offset1);
        $this->assertEquals(4, $limit1);
        $this->assertEquals(0, $offset2);
        $this->assertEquals(6, $limit2);
    }

    public function testSplitsOffsetAndLimitAcrossMultipleProviders(): void
    {
        $paginator = new Paginator(10);
        $paginator->addProvider(12, function($o, $l) use (&$offset1, &$limit1) {
            $offset1 = $o;
            $limit1 = $l;
        });
        $paginator->addProvider(6, function($o, $l) use (&$offset2, &$limit2) {
            $offset2 = $o;
            $limit2 = $l;
        });
        $paginator->addProvider(4, function($o, $l) use (&$offset3, &$limit3) {
            $offset3 = $o;
            $limit3 = $l;
        });

        // Page 1 should contain only data from first page
        $offset1 = $limit1 = $offset2 = $limit2 = $offset3 = $limit3 = null;
        $paginator->get(1);
        $this->assertEquals(0, $offset1);
        $this->assertEquals(10, $limit1);
        $this->assertNull($offset2);
        $this->assertNull($limit2);
        $this->assertNull($offset3);
        $this->assertNull($limit3);

        // Page 2 should contain 2 from first, 6 from second, 2 from third
        $offset1 = $limit1 = $offset2 = $limit2 = $offset3 = $limit3 = null;
        $paginator->get(2);
        $this->assertEquals(10, $offset1);
        $this->assertEquals(2, $limit1);
        $this->assertEquals(0, $offset2);
        $this->assertEquals(6, $limit2);
        $this->assertEquals(0, $offset3);
        $this->assertEquals(2, $limit3);

        // Page 3 should finally contain 2 of third
        $offset1 = $limit1 = $offset2 = $limit2 = $offset3 = $limit3 = null;
        $paginator->get(3);
        $this->assertNull( $offset1);
        $this->assertNull($limit1);
        $this->assertNull($offset2);
        $this->assertNull($limit2);
        $this->assertEquals(2, $offset3);
        $this->assertEquals(2, $limit3);

    }

    /** @dataProvider provideNumberOfPages */
    public function testProvidesNumberOfPages(int $pageSize, int $numberOfItems, int $pages): void
    {
        $paginator = new Paginator($pageSize);
        $paginator->addProvider($numberOfItems, function() {});
        $this->assertEquals($pages, $paginator->numberOfPages());
    }

    public function provideNumberOfPages(): array
    {
        return [
            [ 10, 100, 10 ],
            [ 50, 100, 2 ],
            [ 50, 101, 3 ]
        ];
    }

    /** @dataProvider provideInvalidPageNumbers */
    public function testThrowsExceptionOnInvalidPageNumber(int $pageSize, int $numberOfItems, int $page): void
    {
        $this->expectException(InvalidPageException::class);

        $paginator = new Paginator($pageSize);
        $paginator->addProvider($numberOfItems, function() {});
        $paginator->get($page);
    }

    public function provideInvalidPageNumbers(): array
    {
        return [
            [ 10, 100, 11 ],
            [ 10, 100, -1 ],
            [ 10, 100, 0 ],
            [ 50, 100, 3 ],
        ];
    }

    public function testReturnsArrayOfCollectedItems(): void
    {
        $paginator = new Paginator(10);
        $paginator->addProvider(4, function(int $offset, int $limit) {
            return range(100 + $offset, 100 + $limit - 1);
        });
        $paginator->addProvider(4, function(int $offset, int $limit) {
            return range(200 + $offset, 200 + $limit - 1);
        });
        $paginator->addProvider(4, function(int $offset, int $limit) {
            return range(300 + $offset, 300 + $limit - 1);
        });

        $firstPage = $paginator->get(1);
        $this->assertCount(10, $firstPage);
        $this->assertContains(100, $firstPage);
        $this->assertContains(101, $firstPage);
        $this->assertContains(102, $firstPage);
        $this->assertContains(103, $firstPage);
        $this->assertContains(200, $firstPage);
        $this->assertContains(201, $firstPage);
        $this->assertContains(202, $firstPage);
        $this->assertContains(203, $firstPage);
        $this->assertContains(300, $firstPage);
        $this->assertContains(301, $firstPage);
        $this->assertNotContains(302, $firstPage);
        $this->assertNotContains(303, $firstPage);
    }
}
