<?php
namespace nur\mapper\csv;

use nur\b\params\Tparametrable;
use nur\mapper\item\Seq2AssocMapper;

/**
 * Class Csv2AssocMapper: mapper qui analyse un flux de lignes au format CSV et
 * qui produit un flux de tableaux associatifs
 *
 * NB: cette classe ne supporte pas les flux CSV multi-lignes, contrairement à
 * {@link CsvReader}
 *
 * --autogen-properties-and-methods--
 * @method int getSkipLines()
 * @method bool|null setParseHeaders(?bool $value)
 * @method array|null setHeaders(?array $value)
 * @method int setSkipLines(int $value)
 * @method string|null setMapEmpty(?string $value)
 */
class Csv2AssocMapper extends AbstractCsvMapper {
  use Tparametrable;

  function __construct(?iterable $source=null) {
    $this->seq2assoc = new Seq2AssocMapper();
    parent::__construct($source);
  }

  const PARAMETRABLE_PARAMS_SCHEMA = [
    "parse_headers" => ["?bool", null, "faut-il analyser le premier élément du flux pour calculer la liste des en-têtes en entrée?"],
    "headers" => ["?array", null, "liste et ordre des en-têtes en entrée"],
    "skip_lines" => ["int", 0, "nombre de lignes à sauter en entrée"],
    "map_empty" => ["?string", null, "valeur adoptée pour les chaines vides"],
  ];

  /** @var Seq2AssocMapper */
  private $seq2assoc;
  function pp_setParseHeaders(?bool $parseHeaders): void {
    $this->seq2assoc->setParseKeys($parseHeaders);
  }
  function pp_setHeaders(?array $headers): void {
    $this->seq2assoc->setKeys($headers);
  }

  /** @var int */
  protected $ppSkipLines;

  /** @var bool */
  protected $shouldMapEmpty = false;
  
  protected $ppMapEmpty = null;

  function pp_setMapEmpty($mapEmpty=null): void {
    $this->shouldMapEmpty = true;
    $this->ppMapEmpty = $mapEmpty;
  }

  protected function setup(): void {
    parent::setup();
    $this->seq2assoc->ensureSetup();
  }

  function _parseLine(): bool {
    if ($this->ppSkipLines > 0) {
      $this->ppSkipLines--;
      return false;
    }
    return true;
  }

  /** @var array */
  private $headers;

  function _checkHeader(array $values): bool {
    if ($this->ppMultiSchema && count($values) === 1 && $values[0] === null) {
      # ligne vide, changer de schéma
      $this->seq2assoc->setKeys(null);
      return true;
    }
    return $this->seq2assoc->_checkKeys($values);
  }

  function _mapRow(array $values): array {
    $row = $this->seq2assoc->_mapValues($values);
    foreach ($row as &$value) {
      if ($value === "" && $this->shouldMapEmpty) $value = $this->ppMapEmpty;
    }; unset($value);
    return $row;
  }

  function mapper($item) {
    if (!$this->_parseLine()) return $this->mapToNone();
    $values = str_getcsv($item, $this->separator, $this->enclosure, $this->escape);
    if ($this->_checkHeader($values)) return $this->mapToNone();
    return $this->_mapRow($values);
  }

  protected function teardown(): void {
    parent::teardown();
    $this->seq2assoc->close();
  }

  #############################################################################
  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*/[
    'getParseHeaders' => 'parse_headers',
    'getHeaders' => 'headers',
    'getSkipLines' => 'skip_lines',
    'getMapEmpty' => 'map_empty',
  ];
  const _AUTO_SETTERS = /*autogen*/[
    'setParseHeaders' => 'parse_headers',
    'setHeaders' => 'headers',
    'setSkipLines' => 'skip_lines',
    'setMapEmpty' => 'map_empty',
  ];
  #--autogen-dynamic--
}
