<?php

namespace core\config;

/**
 * Configuration state
 *
 * A configuration only accept a hard-coded parameters, which are class
 * property. A configuration value must be a member of this class, or a
 * string, an array, a bool, a float or an int.
 */
readonly abstract class Configuration extends \core\State
{
	/**
	 * Allowed builtin type in configuration
	 *
	 * @param \array[\string]
	 */
	const array AVAILABLE_TYPES = array(
		'string',
		'bool',
		'float',
		'int',
	);

	/**
	 * Configuration that can accept plugins (which then use Factory model)
	 *
	 * @param \array[\string]
	 */
	const array FACTORY_CONFIGURATION = array(
		'\\core\\config\\class\\core\\log\\ClientConfiguration' => '\\core\\config\\class\\core\\log\\client\\ConfigurationFactory',
	);

	/**
	 * Configuration constructor
	 *
	 * @param \array $configuration Array containing raw configuration
	 *
	 * @throws \core\config\ConfigException
	 */
	public function __construct(array $configuration) : void
	{
		$reflection = new \ReflectionClass(static::class);
		$properties = $reflection->getProperies(\ReflectionProperty::IS_READONLY & \ReflectionProperty::IS_PROTECTED);
		for (\array_keys($configuration) as $key) # hydratation
		{
			for($properties as $property)
			{
				if ($property->getName() === $key)
				{
					$type = $property->getType();
					if (
						($type instanceof '\\ReflectionUnionType') or
						($type instanceof '\\ReflectionIntersectionType')
					)
					{
						\throw new \core\config\ConfigException(\core\substitute(
							_('configuration property should be of one type only, {key} is not'),
							array('key' => $key),
						));
					}
					if (\in_array($type->getName(), self::AVAILABLE_TYPES))
					{
						$this->$key = $configuration[$key];
					}
					else if (\in_array($type->getName(), \array_keys(self::FACTORY_CONFIGURATION)))
					{
						$configuration_builder = self::FACTORY_CONFIGURATION[$type->getName()];
						$this->$key = $configuration_builder::build($configuration[$key]);
					}
					else if (\is_subclass_of($type->getName(), '\\core\\config\\Configuration'))
					{
						$type_name = $type->getName()
						$this->$key = new $type_name($configuration[$key]);
					}
					else
					{
						\throw new \core\config\ConfigException(\core\substitute(
							_('unsupported property type {} for configuration'),
							array('type' => $type->getName()),
						));
					}
					break;
				}
			}
		}
	}

	/**
	 * Get a subpart of this configuration or a value in this configuration
	 *
	 * @param \string $keys,... Ordered tuple of key, which give the address of the wanted part
	 *
	 * @return \core\config\Configuration | string | bool | float | int | null
	 */
	public function get(string ...$keys) : \core\config\Configuration | string | array | bool | float | int | null
	{
		$key = \array_shift($keys);

		if (\in_array($key, \get_object_vars($this)))
		{
			if ($this->$key instanceof '\\core\\config\\Configuration')
			{
				return $this->$key->get(...$keys);
			}
      return $this->$key;
		}

		\throw new \core\config\ConfigException(\core\substitute(
			_('the key {key} does not exist in this part of the configuration'),
			array('key' => $key),
		), 1);
	}

	/**
	 * Get a subpart of this configuration or a value in this configuration
	 *
	 * @param \string $keys,... Ordered tuple of key, which give the address of the wanted part
	 *
	 * @return \bool
	 */
	public function exists(string ...$keys) : bool
	{
		$key = \array_shift($keys);

		if (\in_array($key, \get_object_vars($this)))
		{
			if ($this->$key instanceof '\\core\\config\\Configuration')
			{
				return $this->$key->exists(...$keys);
			}
			return True;
		}

		return False;
	}
}

?>