<?php
namespace nur\mapper\base;

use Generator;
use IteratorAggregate;
use nur\b\ICloseable;
use nur\b\params\IParametrable;
use nur\b\params\Parametrable;
use nur\mapper\base\oobd\IOobdManager;
use nur\mapper\base\oobd\TOobdManager;

/**
 * Class Producer: un générateur qu'il est possible de piloter
 * - notamment, il est possible de le fermer avant d'arriver à la fin de la
 * génération avec la méthode {@link close()}
 *
 * La méthode {@link close()} doit être appelée à la fin de la génération, ce
 * qui est automatique si on utilise des outils qui supportent {@link ICloseable}.
 * le cas échéant, il faut le faire manuellement:
 * ~~~
 * $producer = new MyProducer();
 * try {
 *   foreach ($producer as $value) {
 *     ...
 *   }
 * } finally {
 *   $producer->close();
 * }
 * ~~~
 */
abstract class Producer extends Parametrable implements IteratorAggregate, ICloseable, IParametrable, IOobdManager {
  use TOobdManager;

  /**
   * initialiser le générateur. cette méthode est appelée avant de lancer
   * la génération
   */
  protected function setup(): void {
  }

  /**
   * terminer le générateur. cette méthode est appelée à la fin de la génération
   */
  protected function teardown(): void {
  }

  /**
   * retourner un générateur qui produit les données. ce générateur doit être
   * préparé à prendre une exception {@link StopException} à chaque occurrence
   * de yield
   */
  abstract function producer();

  /** @var Generator */
  private $generator;

  function getIterator() {
    $this->setup();
    $this->return = null;
    return $this->generator = $this->producer();
  }

  function close(): void {
    if ($this->generator !== null) {
      if ($this->generator instanceof Generator) {
        try {
          $this->generator->throw(new StopException());
          $hasReturned = true;
        } catch (StopException $e) {
          $hasReturned = false;
        }
        $this->return = $hasReturned? $this->generator->getReturn(): null;
      }
      $this->generator = null;
    }
    $this->teardown();
  }

  protected $return;

  /**
   * retourner la valeur de retour du générateur. il faut d'abord appeler la
   * méthode {@link close()}
   */
  function getReturn() {
    return $this->return;
  }
}
