<?php
namespace nur\sery\php\iter;

use Exception;
use Iterator;
use nur\sery\NoMoreDataException;
use nur\sery\php\ICloseable;

/**
 * Class AbstractIterator: implémentation de base d'un itérateur
 */
abstract class AbstractIterator implements Iterator, ICloseable {
  /**
   * initialiser les ressources nécessaires à l'itération.
   *
   * les exceptions lancées par cette méthode sont ignorées.
   */
  protected function iter_setup(): void {}

  /**
   * lancer un traitement avant de commencer l'itération.
   *
   * cette méthode est appelée après {@link iter_setup()} et l'objet est garanti
   * d'être dans un état valide.
   *
   * cette méthode est prévue pour être surchargée par l'utilisateur, mais il
   * doit gérer lui-même les exceptions éventuelles.
   */
  protected function iter_beforeStart() {}

  /**
   * retourner le prochain élément.
   * lancer l'exception {@link NoMoreDataException} pour indiquer que plus aucun
   * élément n'est disponible
   *
   * le cas échéant, initialiser $key
   *
   * @throws NoMoreDataException
   */
  abstract protected function iter_next(&$key);

  /**
   * modifier l'élement retourné par {@link iter_next()} avant de le retourner.
   *
   *
   * cette méthode est prévue pour être surchargée par l'utilisateur, mais il
   * doit gérer lui-même les exceptions éventuelles.
   */
  protected function iter_cook(&$item): void {}

  /**
   * lancer un traitement avant de terminer l'itération et de libérer les
   * resources
   *
   * cette méthode est appelée avant {@link iter_teardown()} et l'objet est
   * garanti d'être dans un état valide.
   *
   * cette méthode est prévue pour être surchargée par l'utilisateur, mais il
   * doit gérer lui-même les exceptions éventuelles.
   */
  protected function iter_beforeClose(): void {}

  /**
   * libérer les ressources allouées.
   *
   * les exceptions lancées par cette méthode sont ignorées.
   */
  protected function iter_teardown(): void {}

  function close(): void {
    $this->rewind();
  }

  #############################################################################
  # Implémentation par défaut

  private $setup = false;

  protected function _hasIteratorBeenSetup(): bool {
    return $this->setup;
  }

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

  private $index = 0;
  protected $key;
  protected $item = null;

  function key() {
    return $this->key;
  }

  function current() {
    return $this->item;
  }

  function next(): void {
    if ($this->toredown) return;
    $this->valid = false;
    try {
      $item = $this->iter_next($key);
    } catch (NoMoreDataException $e) {
      $this->iter_beforeClose();
      try {
        $this->iter_teardown();
      } catch (Exception $e) {
      }
      $this->toredown = true;
      return;
    }
    $this->iter_cook($item);
    $this->item = $item;
    if ($key !== null) {
      $this->key = $key;
    } else {
      $this->index++;
      $this->key = $this->index;
    }
    $this->valid = true;
  }

  function rewind(): void {
    if ($this->setup) {
      if (!$this->toredown) {
        $this->iter_beforeClose();
        try {
          $this->iter_teardown();
        } catch (Exception $e) {
        }
      }
      $this->setup = false;
      $this->valid = false;
      $this->toredown = true;
      $this->index = 0;
      $this->key = null;
      $this->item = null;
    }
  }

  function valid(): bool {
    if (!$this->setup) {
      try {
        $this->iter_setup();
      } catch (Exception $e) {
      }
      $this->setup = true;
      $this->toredown = false;
      $this->iter_beforeStart();
      $this->next();
    }
    return $this->valid;
  }
}
