<?php
namespace nulib\php\access;

use ArrayAccess;
use nulib\A;
use nulib\cl;

/**
 * Class KeyAccess: accès
 * - soit à une valeur d'un chemin de clé dans un tableau (si $key !== null)
 * - soit à une valeur scalaire (si $key === null)
 */
class KeyAccess extends AbstractAccess {
  const ALLOW_NULL = null;
  const ALLOW_FALSE = null;
  const PROTECT_DEST = false;

  function __construct(&$dest, $key=null, ?array $params=null) {
    parent::__construct($params);
    $this->protectDest = $params["protect_dest"] ?? static::PROTECT_DEST;
    $this->dest =& $dest;
    $this->key = $key;
    $this->allowNull = $params["allow_null"] ?? static::ALLOW_NULL;
    $this->allowFalse = $params["allow_false"] ?? static::ALLOW_FALSE;
  }

  protected bool $protectDest;

  /** @var mixed|array|ArrayAccess */
  protected $dest;

  /** @var null|int|string|array */
  protected $key;

  function reset(&$dest, $key=null): self {
    $this->dest =& $dest;
    $this->key = $key;
    return $this;
  }

  function resetKey($key=null): self {
    $this->key = $key;
    return $this;
  }

  protected ?bool $allowNull;

  protected function isAllowNull(): bool {
    $allowNull = $this->allowNull;
    if ($allowNull !== null) return $allowNull;
    return $this->key !== null;
  }

  protected ?bool $allowFalse;

  protected function isAllowFalse(): bool {
    $allowFalse = $this->allowFalse;
    if ($allowFalse !== null) return $allowFalse;
    return $this->key === null;
  }

  function exists(): bool {
    $key = $this->key;
    if ($key === null) {
      return $this->isAllowNull() || $this->dest !== null;
    } else {
      return cl::phas($this->dest, $key);
    }
  }

  function available(): bool {
    if (!$this->exists()) return false;
    $key = $this->key;
    if ($key === null) $value = $this->dest;
    else $value = cl::pget($this->dest, $key);
    if ($value === "") return $this->allowEmpty;
    if ($value === null) return $this->isAllowNull();
    if ($value === false) return $this->isAllowFalse();
    return true;
  }

  function get($default=null) {
    $key = $this->key;
    if ($key === null) $value = $this->dest;
    else $value = cl::pget($this->dest, $key, $default);
    if ($value === "" && !$this->allowEmpty) return $default;
    if ($value === null && !$this->isAllowNull()) return $default;
    if ($value === false && !$this->isAllowFalse()) return $default;
    return $value;
  }

  function set($value): void {
    $key = $this->key;
    if ($key === null) {
      if (!$this->protectDest) $this->dest = $value;
    } else {
      cl::pset($this->dest, $key, $value);
    }
  }

  function del(): void {
    $key = $this->key;
    if ($key === null) {
      if (!$this->protectDest) $this->dest = null;
    } else {
      cl::pdel($this->dest, $key);
    }
  }

  function addKey($key): self {
    if ($key === null) return $this;
    if ($this->key !== null) $key = cl::merge($this->key, $key);
    return new KeyAccess($this->dest, $key, [
      "allow_empty" => $this->allowEmpty,
      "allow_null" => $this->allowNull,
      "allow_false" => $this->allowFalse,
      "protect_dest" => $this->protectDest,
    ]);
  }

  function ensureAssoc(array $keys, ?array $params=null): void {
    A::ensure_assoc($this->dest, $keys, $params);
  }

  function ensureKeys(array $defaults, ?array $missings, ?array $params=null): void {
    A::ensure_keys($this->dest, $defaults, $missings, $params);
  }

  function deleteMissings(array $missings, ?array $params=null): void {
    A::delete_missings($this->dest, $missings, $params);
  }

  function ensureOrder(array $keys, ?array $params=null): void {
    A::ensure_order($this->dest, $keys, $params);
  }
}
