<?php
namespace nur\mapper\base;

use nur\b\ValueException;
use nur\mapper\base\impl\IdentMapToMapper;
use nur\mapper\base\impl\IdentReturnMapper;
use nur\mapper\base\impl\SofEofMapper;
use nur\t\TestCase;

class MapperTest extends TestCase {
  const NUMBERS = [1, 2, 3, 4];
  const DOUBLE = [2, 4, 6, 8];
  const FIRST = [1, 2];
  const LAST = [3, 4];
  const LAST_DOUBLE = [6, 8];

  const ASSOC = [1, "a" => "b", 2, "c" => "d"];

  const STRINGS = ["a", "bb", "ccc", "dddd"];
  const SPLIT = ["b", "b", "c", "cc", "dd", "dd"];
  const SPLIT2 = ["<a>", "<bb>", "b", "-", "b", "<ccc>", "c", "-", "cc", "<dddd>", "dd", "-", "dd"];

  function testReturn() {
    $mapper = new IdentReturnMapper(self::NUMBERS);
    self::assertSame(self::NUMBERS, iterator_to_array($mapper));

    $mapper = new IdentReturnMapper(self::ASSOC);
    self::assertSame(self::ASSOC, iterator_to_array($mapper));

    $mapper = new class(self::NUMBERS) extends Mapper {
      function mapper($item) {
        return $item * 2;
      }
    };
    self::assertSame(self::DOUBLE, iterator_to_array($mapper));
  }

  function testFilter() {
    $mapper = new class(self::NUMBERS) extends Mapper {
      function mapper($item) {
        return $item <= 2 ? $item : null;
      }
    };
    self::assertSame(self::FIRST, iterator_to_array($mapper));

    $mapper = new class(self::NUMBERS) extends Mapper {
      function mapper($item) {
        return $item > 2 ? $item : null;
      }
    };
    self::assertSame(self::LAST, iterator_to_array($mapper));
  }

  static function split($value): ?array {
    $length = strlen($value);
    if ($length < 2) return null;
    $middle = intdiv($length, 2);
    $first = substr($value, 0, $middle);
    $last = substr($value, $middle);
    return [$first, $last];
  }

  function testMapTo() {
    $mapper = new IdentMapToMapper(self::NUMBERS);
    self::assertSame(self::NUMBERS, iterator_to_array($mapper));

    $mapper = new IdentMapToMapper(self::ASSOC);
    self::assertSame(self::ASSOC, iterator_to_array($mapper));

    $mapper = new class(self::NUMBERS) extends Mapper {
      function mapper($item) {
        $this->mapToValue($item * 2);
      }
    };
    self::assertSame(self::DOUBLE, iterator_to_array($mapper));

    $mapper = new class(self::NUMBERS) extends Mapper {
      function mapper($item) {
        // possible d'utiliser return (la valeur est ignorée)
        return $item > 2 ? $this->mapToValue($item * 2) : $this->mapToNone();
      }
    };
    self::assertSame(self::LAST_DOUBLE, iterator_to_array($mapper));

    $mapper = new class(self::STRINGS) extends Mapper {
      function mapper($item) {
        return $this->mapTo(MapperTest::split($item));
      }
    };
    self::assertSame(self::SPLIT, iterator_to_array($mapper));

    $mapper = new class(self::STRINGS) extends Mapper {
      function splitemup($value) {
        $parts = MapperTest::split($value);
        if ($parts !== null) {
          yield $parts[0];
          yield "-";
          yield $parts[1];
        }
      }

      function mapper($item) {
        $this->mapToValue("<$item>");
        $this->mapTo($this->splitemup($item));
      }
    };
    self::assertSame(self::SPLIT2, iterator_to_array($mapper));
  }

  function testSofEof() {
    self::assertSame(["a", "b"], iterator_to_array(new SofEofMapper(["a", "b"])));
    self::assertSame(["<", "a", "b"], iterator_to_array(new class(["a", "b"]) extends SofEofMapper {
      const MAP_SOF = true;
    }));
    self::assertSame(["a", "b", ">"], iterator_to_array(new class(["a", "b"]) extends SofEofMapper {
      const MAP_EOF = true;
    }));
    self::assertSame(["<", "a", "b", ">"], iterator_to_array(new class(["a", "b"]) extends SofEofMapper {
      const MAP_SOF = true;
      const MAP_EOF = true;
    }));
  }

  function testClose() {
    $mapper1 = new class extends Mapper {
      public $complete = false;
      public function getIterator(): iterable {
        yield from parent::getIterator();
        $this->complete = true;
      }

      public $closed = false;
      protected function teardown(): void {
        parent::teardown();
        $this->closed = true;
      }
      public function mapper($item) {
        return $item;
      }
    };
    $mapper2 = new class extends Mapper {
      public $complete = false;
      public function getIterator(): iterable {
        yield from parent::getIterator();
        $this->complete = true;
      }

      public $closed = false;
      protected function teardown(): void {
        parent::teardown();
        $this->closed = true;
      }
      public function mapper($item) {
        return $item;
      }
    };
    $consumer = new class extends Consumer {
      public $throw = true;
      public function cook($item) {
        if ($this->throw && $item >= 5) throw new ValueException("cinq");
        return $item + 1;
      }
    };

    $mapper1->complete = $mapper1->closed = false;
    $mapper2->complete = $mapper2->closed = false;
    $consumer->throw = true;
    self::assertException(ValueException::class, [$consumer, "consume"], [1, 2, 3, 5, 6, 7], $mapper1, $mapper2);
    self::assertFalse($mapper1->complete);
    self::assertTrue($mapper1->closed);
    self::assertFalse($mapper2->complete);
    self::assertTrue($mapper2->closed);

    $mapper1->complete = $mapper1->closed = false;
    $mapper2->complete = $mapper2->closed = false;
    $consumer->throw = false;
    self::assertNotException([$consumer, "consume"], [1, 2, 3, 5, 6, 7], $mapper1, $mapper2);
    self::assertTrue($mapper1->complete);
    self::assertTrue($mapper1->closed);
    self::assertTrue($mapper2->complete);
    self::assertTrue($mapper2->closed);
  }
}
