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

use ArrayAccess;
use Countable;
use Iterator;
use nur\sery\cl;
use nur\sery\IArrayWrapper;

/**
 * Class BaseArray: implémentation de base d'un objet array-like, qui peut aussi
 * servir comme front-end pour un array
 */
class BaseArray implements ArrayAccess, Countable, Iterator, IArrayWrapper {
  function __construct(?array &$data=null) {
    $this->reset($data);
  }

  /** @var array */
  protected $data;

  function &wrappedArray(): ?array { return $this->data; }

  function __toString(): string { return var_export($this->data, true); }
  #function __debugInfo() { return $this->data; }
  function reset(?array &$data): void { $this->data =& $data; }
  function count(): int { return $this->data !== null? count($this->data): 0; }
  function keys(): array { return $this->data !== null? array_keys($this->data): []; }

  #############################################################################
  # base

  function has($key): bool {
    return $this->data !== null && array_key_exists($key, $this->data);
  }
  function &get($key, $default=null) {
    if ($this->data !== null && array_key_exists($key, $this->data)) {
      return $this->data[$key];
    } else return $default;
  }
  function set($key, $value): void {
    if ($key === null) $this->data[] = $value;
    else $this->data[$key] = $value;
  }
  function del($key): void {
    unset($this->data[$key]);
  }

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

  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); }

  #############################################################################
  # iterator

  /** @var bool */
  private $valid = false;

  function rewind() {
    if ($this->data !== null) {
      $first = reset($this->data);
      $this->valid = $first !== false || key($this->data) !== null;
    } else {
      $this->valid = false;
    }
  }
  function valid(): bool { return $this->valid; }
  function key() { return key($this->data); }
  function current() { return current($this->data); }
  function next() {
    $next = next($this->data);
    $this->valid = $next !== false || key($this->data) !== null;
  }

  #############################################################################
  # divers

  function phas($pkey): bool { return cl::phas($this->data, $pkey); }
  function pget($pkey, $default=null): bool { return cl::pget($this->data, $pkey, $default); }
  function pset($pkey, $value): void { cl::pset($this->data, $pkey, $value); }
  function pdel($pkey): void { cl::pdel($this->data, $pkey); }

  function contains($value, bool $strict=false): bool {
    if ($value === null || $this->data === null) return false;
    return in_array($value, $this->data, $strict);
  }

  function add($value, bool $unique=true, bool $strict=false): bool {
    if ($unique && $this->contains($value, $strict)) return false;
    $this->set(null, $value);
    return true;
  }

  function addAll(?array $values, bool $unique=true, bool $strict=false): void {
    if ($values === null) return;
    $index = 0;
    foreach ($values as $key => $value) {
      if ($key === $index) {
        $this->add($value, $unique, $strict);
        $index++;
      } else {
        $this->set($key, $value);
      }
    }
  }

  function resetAll(?array $values): void {
    $this->data = null;
    $this->addAll($values);
  }
}
