<?php
namespace nulib\schema\_assoc;

use nulib\schema\_scalar\ScalarSchemaTest;
use nulib\schema\Schema;
use nulib\tests\TestCase;
use nulib\ValueException;

class AssocSchemaTest extends TestCase {
  const NULL_SCHEMA = [
    "" => [
      "assoc",
      "compute_func" => null,
      "validate_func" => null,
      "ensure_array" => null,
      "ensure_assoc" => null,
      "ensure_keys" => null,
      "ensure_order" => null,
    ],
    "schema" => null,
    "type" => [null],
    "default" => null,
    "title" => null,
    "required" => false,
    "nullable" => true,
    "desc" => null,
    "analyzer_func" => null,
    "extractor_func" => null,
    "parser_func" => null,
    "normalizer_func" => null,
    "messages" => null,
    "formatter_func" => null,
    "format" => null,
    "size" => null,
    "precision" => null,
    "name" => null,
    "pkey" => null,
    "header" => null,
    "computed" => null,
  ];

  static function schema(array $definition, array $keyDefinitions): array {
    $definition = array_merge(self::NULL_SCHEMA, $definition, ["schema" => []]);
    foreach ($keyDefinitions as $key => $keydef) {
      $definition["schema"][$key] = array_merge(ScalarSchemaTest::NULL_SCHEMA, $keydef);
    }; unset($subdef);
    return $definition;
  }

  function testNormalize() {
    self::assertSame(self::schema([
      "type" => ["array"], "nullable" => true,
    ], [
      "s" => [
        "type" => ["string"], "nullable" => false,
        "name" => "s", "pkey" => "s", "header" => "s",
      ],
    ]), AssocSchema::normalize_definition(["s" => "string"]));

    self::assertSame(self::schema([
      "type" => ["array"], "nullable" => true,
    ], [
      "s" => [
        "type" => ["string"], "nullable" => false,
        "name" => "s", "pkey" => "s", "header" => "s",
      ],
      "i" => [
        "type" => ["int"], "nullable" => false,
        "name" => "i", "pkey" => "i", "header" => "i",
      ],
      "b" => [
        "type" => ["bool"], "nullable" => false,
        "name" => "b", "pkey" => "b", "header" => "b",
      ],
    ]), AssocSchema::normalize_definition([
      "s" => "string",
      "i" => "int",
      "b" => "bool",
    ]));
  }

  function testConstructor() {
    $schema = new AssocSchema([
      "s" => "string",
      "i" => "int",
      "b" => "bool",
    ]);
    self::assertSame(self::schema([
      "type" => ["array"], "nullable" => true,
    ], [
      "s" => [
        "type" => ["string"], "nullable" => false,
        "name" => "s", "pkey" => "s", "header" => "s",
      ],
      "i" => [
        "type" => ["int"], "nullable" => false,
        "name" => "i", "pkey" => "i", "header" => "i",
      ],
      "b" => [
        "type" => ["bool"], "nullable" => false,
        "name" => "b", "pkey" => "b", "header" => "b",
      ],
    ]), $schema->getDefinition());
    //yaml::dump($schema->getDefinition());
  }

  function testWrapper() {
    $schema = new AssocSchema([
      "s" => "?string",
      "i" => "?int",
      "b" => "?bool",
    ]);
    $array = ["s" => "  string  ", "i" => "  42 ", "b" => false];
    $schema->getWrapper($array);
    self::assertSame([
      "s" => "string",
      "i" => 42,
      "b" => false,
    ], $array);

    ###########################################################################
    $schema = new AssocSchema([
      "s" => "string",
      "i" => "int",
      "b" => "bool",
    ]);

    $array = ["s" => "  string  "];
    $schema->getWrapper($array);
    self::assertSame([
      "s" => "string",
      "i" => 0,
      "b" => false,
    ], $array);

    $array = ["b" => false, "s" => "  string  "];
    $schema->getWrapper($array);
    self::assertSame([
      "s" => "string",
      "i" => 0,
      "b" => false,
    ], $array);

    $array = ["s" => "  string  "];
    $schema->getWrapper($array, null, ["ensure_order" => false]);
    self::assertSame([
      "s" => "string",
      "i" => 0,
      "b" => false,
    ], $array);

    $array = ["b" => false, "s" => "  string  "];
    $schema->getWrapper($array, null, ["ensure_order" => false]);
    self::assertSame([
      "b" => false,
      "s" => "string",
      "i" => 0,
    ], $array);

    $array = ["s" => "  string  "];
    $schema->getWrapper($array, null, ["ensure_keys" => false]);
    self::assertSame([
      "s" => "string",
    ], $array);

    $array = ["b" => false, "s" => "  string  "];
    $schema->getWrapper($array, null, ["ensure_keys" => false]);
    self::assertSame([
      "s" => "string",
      "b" => false,
    ], $array);

    // false équivaut à absent, sauf pour "b" qui est de type bool
    $array = ["s" => false, "i" => false, "b" => false];
    $schema->getWrapper($array, null, ["ensure_keys" => true]);
    self::assertSame([
      "s" => "",
      "i" => 0,
      "b" => false,
    ], $array);

    $array = ["s" => false, "i" => false, "b" => false];
    $schema->getWrapper($array, null, ["ensure_keys" => false]);
    self::assertSame([
      "b" => false,
    ], $array);
  }

  const STRING_SCHEMA = [
    "s" => "string",
    "f" => "string",
    "m" => "string",
  ];

  const NSTRING_SCHEMA = [
    "s" => "?string",
    "f" => "?string",
    "m" => "?string",
  ];

  const RSTRING_SCHEMA = [
    "s" => ["string", "required" => true],
    "f" => ["string", "required" => true],
    "m" => ["string", "required" => true],
  ];

  const RNSTRING_SCHEMA = [
    "s" => ["?string", "required" => true],
    "f" => ["?string", "required" => true],
    "m" => ["?string", "required" => true],
  ];

  const STRINGS = ["s" => "string", "f" => false];
  const NSTRINGS = ["s" => null, "f" => null];

  function testString() {
    /** @var AssocSchema $schema */
    $schema = Schema::ns(self::STRING_SCHEMA);

    $array = self::STRINGS;
    $wrapper = $schema->getWrapper($array, null, ["throw" => false]);
    self::assertSame(["s" => "string", "f" => "", "m" => ""], $array);
    $result = $wrapper->getResult("s");
    self::assertTrue($result->normalized);
    $result = $wrapper->getResult("f");
    self::assertTrue($result->present);
    self::assertFalse($result->available);
    $result = $wrapper->getResult("m");
    self::assertFalse($result->present);

    self::assertNotException(function() use ($schema) {
      $array = self::STRINGS;
      $schema->getWrapper($array);
    });

    $array = self::NSTRINGS;
    $wrapper = $schema->getWrapper($array, null, ["throw" => false]);
    self::assertSame(["s" => null, "f" => null, "m" => ""], $array);
    $result = $wrapper->getResult("s");
    self::assertFalse($result->valid);
    self::assertSame("null", $result->messageKey);
    $result = $wrapper->getResult("f");
    self::assertFalse($result->valid);
    self::assertSame("null", $result->messageKey);
    $result = $wrapper->getResult("m");
    self::assertFalse($result->present);

    self::assertException(ValueException::class, function() use ($schema) {
      $array = self::NSTRINGS;
      $schema->getWrapper($array);
    });
  }

  function testNstring() {
    /** @var AssocSchema $schema */
    $schema = Schema::ns(self::NSTRING_SCHEMA);

    $array = self::STRINGS;
    $wrapper = $schema->getWrapper($array, null, ["throw" => false]);
    self::assertSame(["s" => "string", "f" => null, "m" => null], $array);
    $result = $wrapper->getResult("s");
    self::assertTrue($result->normalized);
    $result = $wrapper->getResult("f");
    self::assertTrue($result->present);
    self::assertFalse($result->available);
    $result = $wrapper->getResult("m");
    self::assertFalse($result->present);

    self::assertNotException(function() use ($schema) {
      $array = self::STRINGS;
      $schema->getWrapper($array);
    });

    $array = self::NSTRINGS;
    $wrapper = $schema->getWrapper($array, null, ["throw" => false]);
    self::assertSame(["s" => null, "f" => null, "m" => null], $array);
    $result = $wrapper->getResult("s");
    self::assertTrue($result->normalized);
    $result = $wrapper->getResult("f");
    self::assertTrue($result->normalized);
    $result = $wrapper->getResult("m");
    self::assertFalse($result->present);

    self::assertNotException(function() use ($schema) {
      $array = self::NSTRINGS;
      $schema->getWrapper($array);
    });
  }

  function testRstring() {
    /** @var AssocSchema $schema */
    $schema = Schema::ns(self::RSTRING_SCHEMA);

    $array = self::STRINGS;
    $wrapper = $schema->getWrapper($array, null, ["throw" => false]);
    self::assertSame(["s" => "string", "f" => "", "m" => ""], $array);
    $result = $wrapper->getResult("s");
    self::assertTrue($result->normalized);
    $result = $wrapper->getResult("f");
    self::assertTrue($result->present);
    self::assertFalse($result->available);
    self::assertSame("unavailable", $result->messageKey);
    $result = $wrapper->getResult("m");
    self::assertFalse($result->present);
    self::assertSame("missing", $result->messageKey);

    self::assertException(ValueException::class, function() use ($schema) {
      $array = self::STRINGS;
      $schema->getWrapper($array);
    });

    $array = self::NSTRINGS;
    $wrapper = $schema->getWrapper($array, null, ["throw" => false]);
    self::assertSame(["s" => null, "f" => null, "m" => ""], $array);
    $result = $wrapper->getResult("s");
    self::assertFalse($result->valid);
    self::assertSame("null", $result->messageKey);
    $result = $wrapper->getResult("f");
    self::assertFalse($result->valid);
    self::assertSame("null", $result->messageKey);
    $result = $wrapper->getResult("m");
    self::assertFalse($result->present);
    self::assertSame("missing", $result->messageKey);

    self::assertException(ValueException::class, function() use ($schema) {
      $array = self::NSTRINGS;
      $schema->getWrapper($array);
    });
  }

  function testRnstring() {
    /** @var AssocSchema $schema */
    $schema = Schema::ns(self::RNSTRING_SCHEMA);

    $array = self::STRINGS;
    $wrapper = $schema->getWrapper($array, null, ["throw" => false]);
    self::assertSame(["s" => "string", "f" => null, "m" => null], $array);
    $result = $wrapper->getResult("s");
    self::assertTrue($result->normalized);
    $result = $wrapper->getResult("f");
    self::assertTrue($result->present);
    self::assertFalse($result->available);
    self::assertSame("unavailable", $result->messageKey);
    $result = $wrapper->getResult("m");
    self::assertFalse($result->present);
    self::assertSame("missing", $result->messageKey);

    self::assertException(ValueException::class, function() use ($schema) {
      $array = self::STRINGS;
      $schema->getWrapper($array);
    });

    $array = self::NSTRINGS;
    $wrapper = $schema->getWrapper($array, null, ["throw" => false]);
    self::assertSame(["s" => null, "f" => null, "m" => null], $array);
    $result = $wrapper->getResult("s");
    self::assertTrue($result->normalized);
    $result = $wrapper->getResult("f");
    self::assertTrue($result->normalized);
    $result = $wrapper->getResult("m");
    self::assertFalse($result->present);
    self::assertSame("missing", $result->messageKey);

    self::assertException(ValueException::class, function() use ($schema) {
      $array = self::NSTRINGS;
      $schema->getWrapper($array);
    });
  }

  function testMessage() {
    $schema = new AssocSchema([
      "rs" => ["string", "required" => true],
      "i" => ["int"],
    ]);

    $value = [];
    $result = $schema->getWrapper($value, null, ["throw" => false])->getResult(null);
    $expectedMessage = <<<EOT
rs: vous devez spécifier cette valeur
EOT;
    self::assertSame($expectedMessage, $result->message);

    $value = [
      "rs" => null,
      "i" => "abc",
    ];
    $result = $schema->getWrapper($value, null, ["throw" => false])->getResult(null);
    $expectedMessage = <<<EOT
rs: cette valeur ne doit pas être nulle
i: une valeur numérique entière est attendue
EOT;
    self::assertSame($expectedMessage, $result->message);
  }
}
