<?php
namespace nur\m\base;

use nur\b\coll\BaseArray;
use nur\iter;
use nur\m\IRowIterator;

abstract class AbstractRowIterator extends BaseArray implements IRowIterator {
  /**
   * @var bool faut-il réinitialiser l'itérateur après l'appel des méthodes
   * {@link first()} et {@link one()}
   */
  const AUTO_REWIND = true;

  private $setup = false;
  private $valid = false;
  private $toredown = true;

  private $index = -1;
  private $key = null;
  private $row = null;

  function __construct() {
    # ne pas appeler le constructeur parent, $data sera initialisé dans les
    # classes dérivées
    #parent::__construct(null);
  }

  /** initialiser cet objet et les resources dont il a besoin */
  protected function _setup(): void {}

  /**
   * retourner l'élément suivant, ou false s'il n'y a plus d'éléments
   * disponibles
   */
  abstract protected function _next();

  /**
   * corriger le cas échéant un élément avant de le présenter à l'utilisateur.
   * si $key est initialisé, alors cette valeur sera retournée en tant que clé
   * à l'utilisateur
   */
  protected function _cook(&$row, &$key): void {}

  /** retourner la clé associée à l'élément courant */
  function _key() {
    $key = $this->key;
    return $key !== null? $key: $this->index;
  }

  /** retourner l'élément courant */
  function _current() { return $this->row; }

  /** fermer toutes les resources utilisées par cet objet */
  protected function _teardown(): void {}

  /**
   * méthode appelée avant l'initialisation de l'objet par {@link _setup()}.
   *
   * Cette méthode est prévue pour être surchargée par l'utilisateur
   */
  protected function beforeSetup(): void {}

  /**
   * méthode appelée avant le début de l'itération: après l'initialisation de
   * l'objet par {@link _setup()} mais avant que le premier élément soit
   * retourné par {@link _next()}.
   *
   * Cette méthode est prévue pour être surchargée par l'utilisateur
   */
  protected function beforeIter(): void {}

  /**
   * méthode appelée pour corriger le cas échéant les lignes retournées.
   *
   * Cette méthode est prévue pour être surchargée par l'utilisateur
   */
  protected function cook(&$row, &$key): void {}

  function rewind() {
    if ($this->setup) {
      if (!$this->toredown) $this->_teardown();
      $this->setup = false;
      $this->valid = false;
      $this->toredown = true;
      $this->index = -1;
      $this->key = null;
      $this->row = null;
    }
  }

  function next() {
    if ($this->toredown) return;
    $key = null;
    $row = $this->_next();
    if ($row !== false) {
      $this->key = null;
      $this->_cook($row, $key);
      $this->cook($row, $key);
      $this->index++;
      $this->key = $key;
      $this->row = $row;
      $this->valid = true;
    } else {
      $this->_teardown();
      $this->valid = false;
      $this->toredown = true;
    }
  }

  function valid() {
    if (!$this->setup) {
      $this->beforeSetup();
      $this->_setup();
      $this->setup = true;
      $this->toredown = false;
      $this->beforeIter();
      $this->next();
    }
    return $this->valid;
  }

  function isClosed(): bool {
    return $this->toredown;
  }

  function all(): array {
    return iterator_to_array($this, true);
  }

  function allVals(?string $name=null): array {
    return rows::vals($this->all(), $name);
  }

  function first($default=null) {
    return iter::first($this, $default);
  }

  function firstVal(?string $name=null, $default=null) {
    return rows::val($this->first(), $name, $default);
  }

  function numRows(): int {
    return $this->first()["num_rows"];
  }

  function insertId(): int {
    return $this->first()["insert_id"];
  }

  function one($default=null, ?bool $rewind=null): array {
    if ($rewind === null) $rewind = static::AUTO_REWIND;
    return iter::one($this, $default, $rewind);
  }

  function peek($default=null, ?bool $rewind=false): array {
    if ($rewind === null) $rewind = static::AUTO_REWIND;
    return iter::peek($this, $default, $rewind);
  }

  # BaseArray
  function has($key): bool { return $this->_has($key); }
  function &get($key, $default=null) { return $this->_get($key, $default); }
  function set($key, $value): self { return $this->_set($key, $value); }
  function add($value): self { return $this->_set(null, $value); }
  function del($key): self { return $this->_del($key); }

  # Modèle d'implémentation dans une classe dérivée:
  ## Iterator
  #function key(): KeyType { return $this->_key(); }
  #function current(): ?ValueType { return $this->_current(); }

  # Par exemple, pour un iterateur générique, on aurait:
  ## Iterator
  #function key() { return $this->_key(); }
  #function current() { return $this->_current(); }
}