<?php
namespace nur\b\values;

use nur\A;

/**
 * Class Breaker: aide pour construire des listes multi-niveaux.
 *
 * Dans une liste, certaines valeurs sont des clés. L'idée est de pouvoir
 * visuellement représenter les valeurs associées à ces clés. La méthode
 * should_break_on() indique quand la clé change, ce qui indique qu'il faut
 * passer à la section suivante.
 *
 * Par exemple, avec la liste suivante:
 *   clé valeur
 *   A   1.1
 *   A   1.2
 *   A   1.3
 *   B   2.1
 *   B   2.2
 *   C   3.1
 * should_break_on($row["clé"]) retourne true pour les valeurs 1.1, 2.1 et 3.1
 * et donc on peut facilement construire cette liste pour affichage:
 *   clé valeur
 *   A   1.1
 *       1.2
 *       1.3
 *   B   2.1
 *       2.2
 *   C   3.1
 */
class Breaker {
  function __construct() {
    $this->reset();
  }

  private $values;

  private $changedKeys;

  private function computeShouldBreak(array $values): bool {
    $should_break = false;
    foreach ($values as $key => $value) {
      if (!$should_break) {
        if ($value === A::get($this->values, $key)) {
          $this->changedKeys[$key] = false;
          continue;
        } else {
          $should_break = true;
        }
      }
      $this->changedKeys[$key] = $should_break;
    }
    $this->values = $values;
    return $should_break;
  }

  function reset() {
    $this->values = [];
    $this->changedKeys = [];
  }

  /**
   * Retourner true si $values est différent par rapport au précédent appel
   *
   * $value peut contenir des instances de Breaker à condition que leur
   * méthodes {@link shouldBreakOn()} aient déjà été appelées.
   */
  function shouldBreakOn(...$values): bool {
    # résoudre les éventuelles instances de Breaker
    $actual_values = [];
    foreach ($values as $value) {
      if ($value instanceof Breaker) {
        $actual_values = array_merge($actual_values, $value->values);
      } else {
        $actual_values[] = $value;
      }
    }
    return $this->computeShouldBreak($actual_values);
  }

  /**
   * Retourner true si les valeurs de $array correspondant aux clés $keys sont
   * différentes par rapport au précédent appel
   *
   * $keys peut contenir des instances de Breaker à condition que leur méthodes
   * {@link shouldBreakOnKeys()} aient déjà été appelées.
   */
  function shouldBreakOnKeys($array, ...$keys): bool {
    # résoudre les éventuelles instances de Breaker
    $values = [];
    foreach ($keys as $key) {
      if ($key instanceof Breaker) {
        $values[] = $key;
      } else {
        $values[$key] = A::get($array, $key);
      }
    }
    $actual_values = [];
    foreach ($values as $key => $value) {
      if ($value instanceof Breaker) {
        $actual_values = array_merge($actual_values, $value->values);
      } else {
        $actual_values[$key] = $value;
      }
    }
    return $this->computeShouldBreak($actual_values);
  }

  /**
   * Tester si la clé spécifiée a été changée.
   *
   * Il faut d'abord avoir appelé l'une des méthodes {@link shouldBreakOn()}
   * ou {@link shouldBreakOnKeys()}
   */
  function isChanged($key=null): bool {
    if ($key !== null) return $this->changedKeys[$key];
    foreach ($this->changedKeys as $changed) {
      if ($changed) return true;
    }
    return false;
  }

  /**
   * Appeler {@link shouldBreakOn()}(...$values) puis retourner un tableau avec
   * les valeurs non modifiées à false.
   */
  function getChanged(...$values): array {
    $this->shouldBreakOn(...$values);
    $values = [];
    foreach ($this->values as $key => $value) {
      $values[$key] = $this->changedKeys[$key]? $value: false;
    }
    return $values;
  }

  /**
   * Appeler {@link shouldBreakOnKeys()}($array, ...$keys) puis retourner un
   * tableau avec les valeurs non modifiées à false.
   */
  function getChangedKeys($array, ...$keys): array {
    $this->shouldBreakOnKeys($array, ...$keys);
    $values = [];
    foreach ($this->values as $key => $value) {
      $values[$key] = $this->changedKeys[$key]? $value: false;
    }
    return $values;
  }
}