<?php

/**
 * Kontajner na data, který se chová jako pole i jako objekt.
 *
 * @category Dalten
 * @package  Data
 */
class Dalten_Data_ArrayObject implements ArrayAccess, IteratorAggregate
{
	/**
	 * Data objetku.
	 *
	 * @var array
	 */
	private array $_data;

	/**
	 * Konstruktor.
	 *
	 * @param mixed $data      Data objektu.
	 * @param bool  $recursive Mají se data převést rekurzivně?
	 */
	public function __construct($data = array(), $recursive = true)
	{
		$this->_data = (array) $data;

		if ($recursive) {
			foreach ($this->_data as &$value) {
				if (is_array($value) || is_object($value)) {
					$value = $this->_toArrayObject($value);
				}
			}
		}
	}

	/**
	 * Převede rekurzivně pole nebo objekt na Dalten_Data_ArrayObject.
	 *
	 * @param mixed $data Data.
	 *
	 * @return Dalten_Data_ArrayObject Výsledný objekt.
	 */
	protected function _toArrayObject($data): Dalten_Data_ArrayObject
    {
		$object = new self();
		foreach ((array) $data as $key => $value) {
			if (is_array($value) || is_object($value)) {
				$value = $this->_toArrayObject($value);
			}

			$object[$key] = $value;
		}

		return $object;
	}

	/**
	 * Zjistí, jestli daný klíč v objektu existuje.
	 *
	 * @param string $key Klíč.
	 *
	 * @return bool Existuje daný klíč?
	 */
	public function __isset($key)
	{
		return array_key_exists((string) $key, $this->_data);
	}

	/**
	 * Vrátí hodnotu uloženou pod daným klíčem.
	 *
	 * @param string $key Klíč.
	 *
	 * @return mixed Hodnota uložená pod daným klíčem.
	 */
	public function __get($key)
	{
		return $this->_data[$key];
	}

	/**
	 * Nastaví hodnotu pod daný klíč.
	 *
	 * @param string $key   Klíč.
	 * @param mixed  $value Hodnota.
	 */
	public function __set($key, $value)
	{
		$this->_data[(string) $key] = $value;
	}

	/**
	 * Odstraní hodnotu pod daným klíčem.
	 *
	 * @param string $key Klíč.
	 */
	public function __unset($key)
	{
		unset($this->_data[(string) $key]);
	}

	/**
	 * Vrátí iterátor nad daty objektu.
	 *
	 * @return Traversable Iterátor nad daty objektu.
	 */
	public function getIterator(): Traversable
	{
		return new ArrayIterator($this->_data);
	}

	/**
	 * Zjistí, jestli daný klíč v objektu existuje.
	 *
	 * @param string $offset Klíč.
	 *
	 * @return bool Existuje daný klíč?
	 */
	public function offsetExists(mixed $offset): bool
	{
		return array_key_exists((string) $offset, $this->_data);
	}

	/**
	 * Vrátí hodnotu uloženou pod daným klíčem.
	 *
	 * @param string $offset Klíč.
	 *
	 * @return mixed Hodnota uložená pod daným klíčem.
	 */
	public function offsetGet(mixed $offset): mixed
	{
		return $this->_data[(string) $offset];
	}

	/**
	 * Nastaví hodnotu pod daný klíč.
	 *
	 * @param string $offset Klíč.
	 * @param mixed  $value  Hodnota.
	 */
	public function offsetSet(mixed $offset, mixed $value): void
	{
		$this->_data[(string) $offset] = $value;
	}

	/**
	 * Odstraní hodnotu pod daným klíčem.
	 *
	 * @param string $offset Klíč.
	 */
	public function offsetUnset(mixed $offset): void
	{
		unset($this->_data[(string) $offset]);
	}

	/**
	 * Převede objekt na vanilkové pole.
	 *
	 * @param bool $recursive Má se objekt převést rekurzivně?
	 *
	 * @return array Data objektu jako vanilkové pole.
	 */
	public function toArray(bool $recursive = true): array
    {
		$array = $this->_data;

		if ($recursive) {
			foreach ($array as &$value) {
				if (is_object($value)) {
					if (method_exists($value, 'toArray')) {
						$value = $value->toArray();
					}

					$value = (array) $value;
				}
			}
		}

		return $array;
	}
}
