<?php
namespace nur\mapper\item;

use nur\A;
use nur\b\IllegalAccessException;
use nur\b\params\Tparametrable;
use nur\b\ValueException;
use nur\data\types\Metadata;
use nur\mapper\base\Mapper;

/**
 * Class SchemaMapper
 *
 * --autogen-properties-and-methods--
 * @method bool isUseKey()
 * @method bool isEnsureKnownTypes()
 * @method bool isRecursive()
 * @method bool isEnsureTypes()
 * @method bool isThrow()
 * @method bool isKeepResults()
 * @method bool isCheckRequired()
 * @method array|null setSchema(?array $value)
 * @method bool setUseKey(bool $value)
 * @method bool setEnsureKnownTypes(bool $value)
 * @method bool setRecursive(bool $value)
 * @method bool setEnsureTypes(bool $value)
 * @method bool setThrow(bool $value)
 * @method bool setKeepResults(bool $value)
 * @method bool setCheckRequired(bool $value)
 */
class SchemaMapper extends Mapper {
  use Tparametrable;

  protected function SCHEMA(): ?array {
    return self::SCHEMA;
  } const SCHEMA = null;

  function __construct(?array $schema=null, ?iterable $source=null) {
    parent::__construct($source);
    $this->pp_setSchema($schema);
  }

  const MULTIPLEX_RESULTS_COLS = "cols";
  const MULTIPLEX_RESULTS_ROWS = "rows";

  const PARAMETRABLE_PARAMS_SCHEMA = [
    "schema" => ["?array", null, "schéma des données"],
    "use_key" => ["bool", false, "faut-il tenir compte des clés fournies par la source"],
    "ensure_known_types" => ["bool", false, "faut-il vérifier et convertir les types connus?"],
    "recursive" => ["bool", true, "faut-il appliquer les schémas de façon récursive"],
    "ensure_types" => ["bool", true, "faut-il vérifier et convertir les types connus?"],
    "throw" => ["bool", false, "faut-il lancer une exception si certains champs sont invalides?"],
    "keep_results" => ["bool", false, "faut-il garder le résultat des analyses et l'insérer dans le flux?"],
    "multiplex_results" => [["string"], null, "faut-il multiplexer champs et résultats d'analyse",
      "allowed_values" => [self::MULTIPLEX_RESULTS_COLS, self::MULTIPLEX_RESULTS_ROWS],
    ],
    "check_required" => ["bool", false, "faut-il vérifier que les champs requis soient présents"],
  ];

  /** @var Metadata */
  protected $md;

  function pp_setSchema(?array $schema): self {
    if ($schema === null) $schema = $this->SCHEMA();
    if ($schema !== null) $this->md = new Metadata($schema);
    return $this;
  }

  /** @var bool */
  protected $ppUseKey;

  /** @var bool */
  protected $ppEnsureKnownTypes;

  /** @var bool */
  protected $ppRecursive;

  /** @var bool */
  protected $ppEnsureTypes;

  /** @var bool */
  protected $ppThrow;

  /** @var bool */
  protected $ppKeepResults;

  /** @var string|null */
  protected $ppMultiplexResults;

  function pp_setMultiplexResults(string $multiplexResults): self {
    switch ($multiplexResults) {
    case "cols":
    case "col":
    case "c":
      $multiplexResults = self::MULTIPLEX_RESULTS_COLS;
      break;
    case "rows":
    case "row":
    case "r":
      $multiplexResults = self::MULTIPLEX_RESULTS_ROWS;
      break;
    default:
      throw ValueException::unexpected_value($multiplexResults, [
        self::MULTIPLEX_RESULTS_COLS,
        self::MULTIPLEX_RESULTS_ROWS,
      ]);
    }
    $this->ppMultiplexResults = $multiplexResults;
    return $this;
  }

  function setMultiplexResults(string $multiplexResults=self::MULTIPLEX_RESULTS_COLS): void {
    $this->pp_setMultiplexResults($multiplexResults);
  }

  /** @var bool */
  protected $ppCheckRequired;

  const OKEY_NB_ERRORS_TOTAL = "nb_errors_total";

  function getNbErrorsTotal(): int {
    return $this->getOvalue(self::OKEY_NB_ERRORS_TOTAL);
  }

  protected function setup(): void {
    $this->setOvalue(self::OKEY_NB_ERRORS_TOTAL, 0);
  }

  function mapper($item, $key=null) {
    if (!$this->ppUseKey) $key = null;
    $md = $this->md;
    $md->ensureSchema($item, $key, ["recursive" => $this->ppRecursive]);
    $itemResults = null;
    if ($this->ppEnsureTypes) {
      $md->verifix($item, $results, $this->ppThrow, $this->ppRecursive);
      $nbErrors = 0;
      foreach ($results as $result) {
        if (!$result["valid"]) {
          $nbErrors++;
          $this->incOvalue(self::OKEY_NB_ERRORS_TOTAL);
        }
      }
      if ($this->ppKeepResults) {
        $item = [
          "item" => $item,
          "results" => $results,
          "nb_errors" => $nbErrors,
        ];
      } elseif ($this->ppMultiplexResults !== null) {
        switch ($this->ppMultiplexResults) {
        case "cols":
          $mitem = ["[nb_errors]" => $nbErrors];
          foreach ($item as $key => $value) {
            $mitem[$key] = $value;
            $result = A::get($results, $key);
            if ($result !== null) {
              $result = $result["valid"]? null: $result["error"];
            }
            $mitem["[${key}_error]"] = $result;
          }
          foreach ($md->getCikeys() as $key) {
            $result = A::get($results, $key);
            if ($result !== null) {
              $result = $result["valid"]? null: $result["error"];
            }
            $mitem["[${key}_error]"] = $result;
          }
          $item = $mitem;
          break;
        case "rows":
          # construire la ligne avec les résultats
          $mitem = ["[nb_errors]" => $nbErrors];
          foreach ($item as $key => $value) {
            $result = A::get($results, $key);
            if ($result !== null) {
              $result = $result["valid"]? null: $result["error"];
            }
            $mitem[$key] = $result;
          }
          foreach ($md->getCikeys() as $key) {
            $result = A::get($results, $key);
            if ($result !== null) {
              $result = $result["valid"]? null: $result["error"];
            }
            $mitem[$key] = $result;
          }
          $itemResults = $mitem;
          # puis ajouter la colonne manquante dans l'objet original
          $mitem = ["[nb_errors]" => $nbErrors];
          foreach ($item as $key => $value) {
            $mitem[$key] = $value;
          }
          foreach ($md->getCikeys() as $key) {
            $mitem[$key] = null;
          }
          $item = $mitem;
          break;
        default:
          throw IllegalAccessException::unexpected_state();
        }
      }
    } elseif ($this->ppCheckRequired) {
      $md->checkRequired($item);
    }
    if ($itemResults === null) return $item;
    else return $this->mapTo([$item, $itemResults]);
  }

  #############################################################################
  const _AUTOGEN_CONSTS = [
    "" => [self::class, "_autogen_consts"],
  ];
  const _AUTOGEN_LITERALS = /*autogen*/[
    [
      \nur\b\params\parametrable_utils::class,
      '\\nur\\b\\params\\parametrable_utils::class',
    ],
    [
      self::PARAMETRABLE_PARAMS_SCHEMA,
      'self::PARAMETRABLE_PARAMS_SCHEMA',
    ],
  ];
  const _AUTOGEN_METHODS = /*autogen*/[
    [
      \nur\b\params\parametrable_utils::class,
      '_autogen_methods_getters',
      self::PARAMETRABLE_PARAMS_SCHEMA,
      null,
    ],
    [
      \nur\b\params\parametrable_utils::class,
      '_autogen_methods_setters',
      self::PARAMETRABLE_PARAMS_SCHEMA,
      null,
    ],
  ];
  const _AUTO_GETTERS = /*autogen*/[
    'getSchema' => 'schema',
    'isUseKey' => 'use_key',
    'isEnsureKnownTypes' => 'ensure_known_types',
    'isRecursive' => 'recursive',
    'isEnsureTypes' => 'ensure_types',
    'isThrow' => 'throw',
    'isKeepResults' => 'keep_results',
    'getMultiplexResults' => 'multiplex_results',
    'isCheckRequired' => 'check_required',
  ];
  const _AUTO_SETTERS = /*autogen*/[
    'setSchema' => 'schema',
    'setUseKey' => 'use_key',
    'setEnsureKnownTypes' => 'ensure_known_types',
    'setRecursive' => 'recursive',
    'setEnsureTypes' => 'ensure_types',
    'setThrow' => 'throw',
    'setKeepResults' => 'keep_results',
    'setMultiplexResults' => 'multiplex_results',
    'setCheckRequired' => 'check_required',
  ];
  #--autogen-dynamic--
}