<?php
namespace nur\mapper\item;

use nur\A;
use nur\b\params\Tparametrable;
use nur\mapper\base\Mapper;

/**
 * Class Assoc2SeqMapper: transformer un flux de tableaux associatifs en
 * tableaux séquentiels
 *
 * --autogen-properties-and-methods--
 * @method bool isMultiSchema()
 * @method array getKeys()
 * @method bool isOutputKeys()
 * @method bool setMultiSchema(bool $value)
 * @method array setKeys(array $value)
 * @method bool setOutputKeys(bool $value)
 */
class Assoc2SeqMapper extends Mapper {
  use Tparametrable;

  const MAP_EOF = true;

  const PARAMETRABLE_PARAMS_SCHEMA = [
    "multi_schema" => ["bool", false, "les flux multi-schémas sont-ils supportés?"],
    "keys" => ["array", null, "liste et ordre des champs en sortie"],
    "output_keys" => ["bool", true, "faut-il afficher les en-têtes en sortie?"],
  ];

  /** @var bool les flux avec plusieurs schémas sont-ils supportés? */
  protected $ppMultiSchema;

  /**
   * @var array liste et ordre des champs en sortie. si cette valeur n'est pas
   * spécifiée, elle est calculée à partir du premier élément du flux.
   */
  protected $ppKeys;

  /** @var bool faut-il afficher les en-têtes en sortie? */
  protected $ppOutputKeys;

  private static final function is_different(array $h1, array $h2): bool {
    sort($h1);
    sort($h2);
    return $h1 != $h2;
  }

  private function computeKeys(array $item) {
    return array_keys($item);
  }

  function _checkKeys(?array $item): array {
    $skipLine = false;
    if ($item === null) {
      $outputKeys = $this->ppOutputKeys && $this->ppKeys !== null;
      return [$skipLine, $outputKeys, $this->ppKeys];
    }
    $prevKeys = $this->ppKeys;
    if ($this->ppMultiSchema) {
      # vérifier si le schéma a changé
      $keys = $this->computeKeys($item);
      if ($prevKeys === null) $prevKeys = $keys;
      if (self::is_different($prevKeys, $keys)) {
        $skipLine = true;
        $this->ppOutputKeys = true;
      } else {
        $keys = $prevKeys;
      }
    } else {
      $keys = $prevKeys;
      if ($keys === null) $keys = $this->computeKeys($item);
    }
    return [$skipLine, $this->ppOutputKeys, $keys];
  }

  function _setOutputKeys(array $keys): void {
    $this->ppKeys = $keys;
    $this->ppOutputKeys = false;
  }

  function _cookValues(array $keys, array $item): array {
    $values = [];
    foreach ($keys as $header) {
      $values[] = A::get($item, $header, false);
    }
    return $values;
  }

  function mapper($item) {
    if ($this->eof) $item = null;
    else $item = A::with($item);
    $arrays = [];

    [$skipLine, $outputKeys, $keys] = $this->_checkKeys($item);
    if ($skipLine) $arrays[] = [];
    if ($outputKeys) {
      $arrays[] = $keys;
      $this->_setOutputKeys($keys);
    }
    if ($item !== null) {
      $arrays[] = $this->_cookValues($keys, $item);
    }

    return $this->mapTo($arrays);
  }

  #############################################################################
  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*/[
    'isMultiSchema' => 'multi_schema',
    'getKeys' => 'keys',
    'isOutputKeys' => 'output_keys',
  ];
  const _AUTO_SETTERS = /*autogen*/[
    'setMultiSchema' => 'multi_schema',
    'setKeys' => 'keys',
    'setOutputKeys' => 'output_keys',
  ];
  #--autogen-dynamic--
}