<?php
namespace nur;

use nur\b\ValueException;
use nur\t\TestCase;

class mdTest extends TestCase {
  const SCHEMA_DATA1 = [
    # sequentiel
    [null,       ["a" => null, "b" => null]],
    [[],         ["a" => null, "b" => null]],
    [[null],     ["a" => null, "b" => null]],
    ["a",        ["a" => "a", "b" => null]],
    [["a"],      ["a" => "a", "b" => null]],
    [["a", "b"], ["a" => "a", "b" => "b"]],
    # associatif
    [["a" => "a"],             ["a" => "a", "b" => null]],
    [["b" => "b"],             ["b" => "b", "a" => null]],
    [["a" => "a", "b" => "b"], ["a" => "a", "b" => "b"]],
    [["b" => "b", "a" => "a"], ["b" => "b", "a" => "a"]],
    # mix séquentiel / associatif
    [["a" => "a", "b"], ["a" => "a", "b" => "b"]],
    [["a", "b" => "b"], ["b" => "b", "a" => "a"]],
    # avec extra
    [["a", "b", "x"],                           ["a" => "a", "b" => "b", "x"]],
    [["a", "b", "y" => "y"],                    ["y" => "y", "a" => "a", "b" => "b"]],
    [["a" => "a", "b" => "b", "x"],             ["a" => "a", "b" => "b", "x"]],
    [["a" => "a", "b" => "b", "y" => "y"],      ["a" => "a", "b" => "b", "y" => "y"]],
    [["a" => "a", "b" => "b", "x", "y" => "y"], ["a" => "a", "b" => "b", "y" => "y", "x"]],
  ];
  const SCHEMA_DATA2 = [
    # sequentiel
    ["k", null,       ["a" => "k", "b" => null]],
    ["k", [],         ["a" => "k", "b" => null]],
    ["k", [null],     ["a" => "k", "b" => null]],
    ["k", "a",        ["a" => "k", "b" => "a"]],
    ["k", ["a"],      ["a" => "k", "b" => "a"]],
    ["k", ["a", "b"], ["a" => "k", "b" => "a", "b"]],
    #  associatif
    ["k", ["a" => "a"],             ["a" => "a", "b" => null]],
    ["k", ["b" => "b"],             ["b" => "b", "a" => "k"]],
    ["k", ["a" => "a", "b" => "b"], ["a" => "a", "b" => "b"]],
    ["k", ["b" => "b", "a" => "a"], ["b" => "b", "a" => "a"]],
    # mix séquentiel / associatif
    ["k", ["a" => "a", "b"], ["a" => "a", "b" => "b"]],
    ["k", ["a", "b" => "b"], ["b" => "b", "a" => "k", "a"]],
    #  avec extra
    ["k", ["a", "b", "x"],                           ["a" => "k", "b" => "a", "b", "x"]],
    ["k", ["a", "b", "y" => "y"],                    ["y" => "y", "a" => "k", "b" => "a", "b"]],
    ["k", ["a" => "a", "b" => "b", "x"],             ["a" => "a", "b" => "b", "x"]],
    ["k", ["a" => "a", "b" => "b", "y" => "y"],      ["a" => "a", "b" => "b", "y" => "y"]],
    ["k", ["a" => "a", "b" => "b", "x", "y" => "y"], ["a" => "a", "b" => "b", "y" => "y", "x"]],
  ];

  function testSchema() {
    $schema = [
      "a" => null,
      "b" => null,
    ];

    $index = 0;
    foreach (self::SCHEMA_DATA1 as [$data, $result]) {
      md::ensure_schema($data, $schema);
      self::assertSame($result, $data, "at SCHEMA_DATA1[$index]");
      $index++;
    }
    $index = 0;
    foreach (self::SCHEMA_DATA2 as [$key, $data, $result]) {
      md::ensure_schema($data, $schema, $key);
      self::assertSame($result, $data, "at SCHEMA_DATA2[$index]");
      $index++;
    }
  }

  #############################################################################

  function testRecursiveSchema() {
    $item_schema = [
      "id" => null,
      "name" => null,
    ];
    $schema = [
      "item" => ["array", "default", "schema" => $item_schema],
      "items" => ["array[]", "default", "schema" => $item_schema],
      "nitem" => ["?array", "default", "schema" => $item_schema],
      "nitems" => ["?array[]", "default", "schema" => $item_schema],
    ];

    $data = null;
    md::ensure_schema($data, $schema);
    self::assertSame([
      "item" => ["id" => "default", "name" => null],
      "items" => [["id" => "default", "name" => null]],
      "nitem" => ["id" => "default", "name" => null],
      "nitems" => [["id" => "default", "name" => null]],
    ], $data);

    $data = [false, false, false, false];
    md::ensure_schema($data, $schema);
    self::assertSame([
      "item" => ["id" => "default", "name" => null],
      "items" => [["id" => "default", "name" => null]],
      "nitem" => ["id" => "default", "name" => null],
      "nitems" => [["id" => "default", "name" => null]],
    ], $data);

    $data = [null, null, null, null];
    md::ensure_schema($data, $schema);
    self::assertSame([
      "item" => ["id" => "default", "name" => null],
      "items" => [["id" => "default", "name" => null]],
      "nitem" => null,
      "nitems" => null,
    ], $data);

    $data = ["id", "id", "id", "id"];
    md::ensure_schema($data, $schema);
    self::assertSame([
      "item" => ["id" => "id", "name" => null],
      "items" => [["id" => "id", "name" => null]],
      "nitem" => ["id" => "id", "name" => null],
      "nitems" => [["id" => "id", "name" => null]],
    ], $data);
  }

  #############################################################################

  function testTypes() {
    $schema = [
      "regular" => [null, "default"],
      "boolt" => ["bool", true],
      "boolf" => ["bool", false],
      "booln" => ["bool", null],
      "nboolt" => ["?bool", true],
      "nboolf" => ["?bool", false],
      "nbooln" => ["?bool", null],
      "mixed" => ["mixed", "default"],
    ];
    
    $data = [];
    md::ensure_schema($data, $schema);
    self::assertSame([
      "regular" => "default",
      "boolt" => true, "boolf" => false, "booln" => false,
      "nboolt" => true, "nboolf" => false, "nbooln" => null,
      "mixed" => "default",
    ], $data);

    $data = [
      false,
      false, false, false,
      false, false, false,
      false,
    ];
    md::ensure_schema($data, $schema);
    self::assertSame([
      "regular" => "default",
      "boolt" => false, "boolf" => false, "booln" => false,
      "nboolt" => false, "nboolf" => false, "nbooln" => false,
      "mixed" => false,
    ], $data);

    $data = [
      null,
      null, null, null,
      null, null, null,
      null,
    ];
    md::ensure_schema($data, $schema);
    self::assertSame([
      "regular" => null,
      "boolt" => true, "boolf" => false, "booln" => false,
      "nboolt" => null, "nboolf" => null, "nbooln" => null,
      "mixed" => null,
    ], $data);

    $data = [
      "x",
      "x", "x", "x",
      "x", "x", "x",
      "x",
    ];
    md::ensure_schema($data, $schema);
    self::assertSame([
      "regular" => "x",
      "boolt" => true, "boolf" => true, "booln" => true,
      "nboolt" => true, "nboolf" => true, "nbooln" => true,
      "mixed" => "x",
    ], $data);
  }

  function testArrayToString() {
    $schema = ["value" => "string"];

    $data = ["value" => "one"];
    md::ensure_schema($data, $schema);
    self::assertSame(["value" => "one"], $data);

    $data = ["value" => ["one", "two"]];
    md::ensure_schema($data, $schema);
    self::assertSame(["value" => "one two"], $data);

    $data = ["value" => ["one", "two" => false, "three" => true]];
    md::ensure_schema($data, $schema);
    self::assertSame(["value" => "one three"], $data);
  }

  #############################################################################

  function testRequired() {
    $schema = [
      "required" => [null, "required" => true],
      "nullable" => [null, 1],
    ];

    $data = null;
    self::assertException(ValueException::class, function() use (&$data, $schema) {
      md::ensure_schema($data, $schema, null, true);
    });
    self::assertSame(["required" => null, "nullable" => 1], $data);

    $data = [];
    self::assertException(ValueException::class, function() use (&$data, $schema) {
      md::ensure_schema($data, $schema, null, true);
    });
    self::assertSame(["required" => null, "nullable" => 1], $data);

    $data = [null];
    self::assertException(ValueException::class, function() use (&$data, $schema) {
      md::ensure_schema($data, $schema, null, true);
    });
    self::assertSame(["required" => null, "nullable" => 1], $data);

    $data = [false];
    self::assertException(ValueException::class, function() use (&$data, $schema) {
      md::ensure_schema($data, $schema, null, true);
    });
    self::assertSame(["required" => null, "nullable" => 1], $data);

    $data = ["x"];
    self::assertNotException(function() use (&$data, $schema) {
      md::ensure_schema($data, $schema, null, true);
    });
    self::assertSame(["required" => "x", "nullable" => 1], $data);

    $data = ["x", "y", "z"];
    self::assertNotException(function() use (&$data, $schema) {
      md::ensure_schema($data, $schema, null, true);
    });
    self::assertSame(["required" => "x", "nullable" => "y", "z"], $data);

    $data = ["x", false, "z"];
    self::assertNotException(function() use (&$data, $schema) {
      md::ensure_schema($data, $schema, null, true);
    });
    self::assertSame(["required" => "x", "nullable" => 1, "z"], $data);
  }

  #############################################################################

  const GET_DATA = [
    [null, [
      "a" => null,
      "b" => "b",
    ]],
    [false, [
      "a" => "a",
      "b" => "b",
    ]],
    [1, [
      "a" => 1,
      "b" => "b",
    ]],
    [[], [
      "a" => "a",
      "b" => "b",
    ]],
    [[null], [
      "a" => null,
      "b" => "b",
    ]],
    [[false], [
      "a" => "a",
      "b" => "b",
    ]],
    [[1], [
      "a" => 1,
      "b" => "b",
    ]],
    [[1, 2], [
      "a" => 1,
      "b" => 2,
    ]],
    [["a" => 1], [
      "a" => 1,
      "b" => "b",
    ]],
    [["b" => 2], [
      "a" => "a",
      "b" => 2,
    ]],
    [["a" => 1, "b" => 2], [
      "a" => 1,
      "b" => 2,
    ]],
    [["b" => 2, "a" => 1], [
      "a" => 1,
      "b" => 2,
    ]],
  ];

  function testGet() {
    $schema = [
      "a" => [null, "a"],
      "b" => [null, "b"],
    ];

    $i = 0;
    foreach (self::GET_DATA as [$data, $results]) {
      $j = 0;
      foreach ($results as $key => $expected) {
        $actual = md::get($data, $key, $schema);
        self::assertSame($expected, $actual, "at GET_DATA[$i][$j]");
        $j++;
      }
      $i++;
    }

    # test default
    $data = [];
    self::assertSame("a", md::get($data, "a", $schema));
    self::assertSame("A", md::get($data, "a", $schema, "A"));
    $data = ["a" => "x"];
    self::assertSame("x", md::get($data, "a", $schema));
    self::assertSame("x", md::get($data, "a", $schema, "A"));
  }

  #############################################################################

  const GET_VALUES_DATA = [
    [null, [], []],
    [["a", "b"], [], ["a", "b"]],
    [["a" => "a"], ["a" => "a"], []],
    [["b" => "b"], ["b" => "b"], []],
    [["a" => "a", "b" => "b"], ["a" => "a", "b" => "b"], []],
    [["b" => "b", "a" => "a"], ["a" => "a", "b" => "b"], []],
  ];

  function testGetValues() {
    $schema = [
      "a" => null,
      "b" => null,
    ];

    $i = 0;
    foreach (self::GET_VALUES_DATA as [$data, $xvalues, $xothers]) {
      $values = md::get_values($data, $schema);
      self::assertSame($xvalues, $values, "values at GET_VALUES_DATA[$i]");
      $others = md::get_others($data, $schema);
      self::assertSame($xothers, $others, "others at GET_VALUES_DATA[$i]");
      $i++;
    }
  }
}