<?php
namespace nur\mapper\csv;

use nur\A;
use nur\b\params\Tparametrable;
use nur\str;

/**
 * Class Assoc2CsvMapper: mapper qui convertir un flux de tableaux associatifs
 * en flux de lignes au format CSV
 *
 * --autogen-properties-and-methods--
 * @method array getHeaders()
 * @method bool isOutputHeaders()
 * @method array setHeaders(array $value)
 * @method bool setOutputHeaders(bool $value)
 */
class Assoc2CsvMapper extends AbstractCsvMapper {
  use Tparametrable;

  const MAP_EOF = true;

  const PARAMETRABLE_PARAMS_SCHEMA = [
    "headers" => ["array", null, "liste et ordre des champs en sortie"],
    "output_headers" => ["bool", true, "faut-il afficher les en-têtes en sortie?"],
  ];

  /**
   * @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 $ppHeaders;

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

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

  private function computeHeaders(array $row) {
    return array_keys($row);
  }

  function _checkHeaders(?array $row): array {
    $skipLine = false;
    if ($row === null) {
      $outputHeaders = $this->ppOutputHeaders && $this->ppHeaders !== null;
      return [$skipLine, $outputHeaders, $this->ppHeaders];
    }
    $prevHeaders = $this->ppHeaders;
    if ($this->ppMultiSchema) {
      # vérifier si le schéma a changé
      $headers = $this->computeHeaders($row);
      if ($prevHeaders === null) $prevHeaders = $headers;
      if (self::is_different($prevHeaders, $headers)) {
        $skipLine = true;
        $this->ppOutputHeaders = true;
      } else {
        $headers = $prevHeaders;
      }
    } else {
      $headers = $prevHeaders;
      if ($headers === null) $headers = $this->computeHeaders($row);
    }
    return [$skipLine, $this->ppOutputHeaders, $headers];
  }

  function _setOutputHeaders(array $headers): void {
    $this->ppHeaders = $headers;
    $this->ppOutputHeaders = false;
  }

  function _cookHeaders(array $headers): array {
    return $headers;
  }

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

  function getLine(array $values, bool $stripNl=true): string {
    $tmpf = fopen("php://memory", "w+b");
    fputcsv($tmpf, $values, $this->separator, $this->enclosure, $this->escape);
    rewind($tmpf);
    $line = stream_get_contents($tmpf);
    if ($stripNl) $line = str::strip_nl($line);
    fclose($tmpf);
    return $line;
  }

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

    [$skipLine, $outputHeaders, $headers] = $this->_checkHeaders($row);
    if ($skipLine) $lines[] = "";
    if ($outputHeaders) {
      $lines[] = $this->getLine($this->_cookHeaders($headers));
      $this->_setOutputHeaders($headers);
    }
    if ($row !== null) {
      $lines[] = $this->getLine($this->_cookValues($headers, $row));
    }

    $this->mapTo($lines);
  }

  #############################################################################
  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*/[
    'getHeaders' => 'headers',
    'isOutputHeaders' => 'output_headers',
  ];
  const _AUTO_SETTERS = /*autogen*/[
    'setHeaders' => 'headers',
    'setOutputHeaders' => 'output_headers',
  ];
  #--autogen-dynamic--
}