<?php
namespace nur\b\coll;

use nur\A;
use nur\b\IllegalAccessException;

/**
 * Class ArrayStack: une vue vers un ensemble d'instances de {@link IArray}
 * organisées en pile. Seule l'instance sur la pile du haut est modifiable
 */
class ArrayStack implements IArray {
  use TArrayMdBasic, TAutomethods;

  const _AUTO_GETTERS = null;
  const _AUTO_SETTERS = null;
  const _AUTO_DELETERS = null;
  const _AUTO_CI_GETTERS = null;
  const _AUTO_CI_SETTERS = null;
  const _AUTOGEN_LITERALS = null;
  const _AUTOGEN_METHODS = null;

  /** @var array schéma des données de cette vue */
  const SCHEMA = null;

  function __construct(?IArray $array=null) {
    if ($array !== null) $this->push($array);
  }

  /** @var IArray[] */
  protected $stack = [];

  function stackDepth(): int {
    return count($this->stack);
  }

  function &root(): array {
    return $this->stack;
  }

  function push(IArray $array): self {
    array_unshift($this->stack, null);
    $this->stack[0] = $array;
    return $this;
  }

  function pop(): IArray {
    $array = $this->stack[0];
    array_shift($this->stack);
    return $array;
  }

  function peek(int $index=1): IArray {
    $count = count($this->stack);
    if ($count > 0) {
      while ($index < 0) $index += $count;
    }
    return $this->stack[$index];
  }

  function __toString(): string { return $this->stack[0]->__toString(); }
  function &array(): ?array { return $this->stack[0]->array(); }
  function keys(): array {
    $keys = [];
    foreach ($this->stack as $array) {
      A::merge2($keys, array_fill_keys($array->keys(), true));
    }
    return array_keys($keys);
  }
  function count(): int { return count($this->keys()); }
  function has($key): bool {
    foreach ($this->stack as $array) {
      if ($array->has($key)) return true;
    }
    return false;
  }
  function &get($key, $default=null) {
    foreach ($this->stack as $array) {
      if ($array->has($key)) return $array->get($key);
    }
    return $default;
  }
  function set($key, $value): IArray { $this->stack[0]->set($key, $value); return $this; }
  function add($value): IArray { $this->stack[0]->add($value); return $this; }
  function del($key): IArray { $this->stack[0]->del($key); return $this; }

  function addAll(?array $values): IArray { $this->stack[0]->addAll($values); return $this; }
  function resetAll(?array $values): IArray { $this->stack[0]->resetAll($values); return $this; }

  function offsetExists($key) { return $this->has($key); }
  function &offsetGet($key) { return $this->get($key); }
  function offsetSet($key, $value) { $this->set($key, $value); }
  function offsetUnset($key) { $this->del($key); }

  function __isset($name) { return $this->has($name); }
  function &__get($name) { return $this->get($name); }
  function __set($name, $value) { $this->set($name, $value); }
  function __unset($name) { $this->del($name); }

  function __call(string $name, array $args) {
    foreach ($this->stack as $array) {
      if ($array->_haveMethod($name)) {
        return call_user_func_array([$array, $name], $args);
      }
    }
    throw IllegalAccessException::not_implemented($name);
  }

  # Rajouter ceci dans les classes dérivées:
  #use TArrayMd, TAutoconstsStatic; // ou TArrayMdDynamic, TAutoconstsDynamic
  #const _AUTOGEN_CONSTS = ["" => [self::class, "_AUTOGEN_CONSTS"]];
  ##--autogen-dynamic--
}
