<?php

namespace IO4\Container;

use Psr\Container\ContainerInterface;
use Webfan\Webfat\App\ContainerCollection;

/**
 * A composite container that acts as a normal container, but delegates method calls to one or more internal containers
 * inspired by https://github.com/AcclimateContainer/acclimate-container
 */
class Collection extends ContainerCollection implements ContainerInterface, ContainerCollectionInterface
{
	public const CONTAINER_SPLIT = '.';
	public const THROW_ONERRROR = 0;
	public const NULL_ONERROR = 1;
	public const CALL_ID = 'call';

	protected $invoker = null;


	public function getCallId(): string
	{
		return false;
	}


	public function set(string $id, $entry)
	{
		if(isset($this->container[$id]) && (\is_callable($entry) || (is_object($entry) && $entry instanceof \Closure ))
		    && $this->factories->contains($entry)
		  ) {
			throw new \Exception(sprintf('The container entry #id: %1$s is defined allready in %2$s!', $id, __METHOD__));
			//return $this->extend($id, $entry);
		}elseif(isset($this->container[$id])){
			throw new \Exception(sprintf('The container entry #id: %1$s is defined allready in %2$s!', $id, __METHOD__));
		}

		 $this->container[$id] = $entry;
		return $this;
	}


	public function extend($id, \Closure|\callable $callable): ContainerCollectionInterface
	{
		if (!isset($this->container[$id])) {
		  throw new \frdl\NotFoundException($id);
			 // return $this->factory($id, $callable);
		}

		$factory = $this->container[$id];
		$extended = function (ContainerInterface $container, $previous = null) use ($id, $callable, $factory) {

			return \call_user_func_array($callable,[$container, $factory($container, $previous)]);
		};

		if ($this->factories->contains($this->container[$id])) {
		    $this->factories->detach($this->container[$id]);
		    $this->factories->attach($extended);
		}

		$this->container[$id] = $extended;

		  return $this;
	}


	public function factory($id, \Closure|\callable $callable): ContainerCollectionInterface
	{
		if(isset($this->container[$id]) && (\is_callable($callable) || (is_object($callable) && $callable instanceof \Closure ))
		    && $this->factories->contains($callable)
		  ) {
			return $this->extend($id, $callable);
		}elseif(isset($this->container[$id])){
			throw new \Exception(sprintf('The container entry #id: %1$s is defined allready in %2$s!', $id, __METHOD__));
		}

		$this->set($id, $callable);
		$this->factories->attach($callable);
		return $this;
	}


	public function createInvoker()
	{
		$invoker =  (new \Invoker\Invoker(null, $this));
				$invoker->getParameterResolver()->prependResolver(
					new \Invoker\ParameterResolver\Container\ParameterNameContainerResolver($this)
				);
				$invoker->getParameterResolver()->prependResolver(
					new \Invoker\ParameterResolver\Container\TypeHintContainerResolver($this)
				);
		return $invoker;
	}


	public function getInvoker()
	{
		if(null === $this->invoker){
				$this->invoker = $this->createInvoker( );
			}
		return $this->invoker;
	}


	public function get($id)
	{
		$name = $id;

		if(isset($this->resolved[$id])){
			$this->r($name);
		  return $this->resolved[$id];
		}

		if(isset($this->resolvedIds[$name])){
			$this->r($name);
			return $this->getContainer( $this->resolvedIds[$name][0] )->get( $this->resolvedIds[$name][1] );
		}


		if (isset($this->container[$id])
			&& (\is_callable($this->container[$id]) && (is_object($this->container[$id]) && $this->container[$id] instanceof \Closure ) )
			&& $this->factories->contains($this->container[$id])
		   ) {
				return \call_user_func_array($this->container[$id],[$this,isset($this->container[$id]) ? $this->container[$id] : null]);
		}


		if ( isset($this->container[$id])
			&& (\is_callable($this->container[$id]) && $this->container[$id] instanceof \Closure)
		   ) {
				$this->resolved[$id] = $this->getInvoker( )->call($this->container[$id]);
			return $this->resolved[$id];
		}

		if(isset($this->container[$id])){
			$this->r($name);
		  return $this->container[$id];
		}



		$parts = $this->_splitId($name);
		$current = '';
		while(0<count($parts) ){
			$current .= self::CONTAINER_SPLIT.array_shift($parts);
		    $current = ltrim($current, self::CONTAINER_SPLIT);
			 if($this->hasContainer($current)){
				 $id = substr($name, strlen($current), strlen($name) );
				 if ($this->getContainer($current)->has($id)) {
					  $this->resolvedIds[$name] = [$current, $id];
					 $this->r($name);
					  return $this->getContainer($current)->get($id);
				 }
			 }
		}




		foreach ($this->containers as $containerId => $container) {
		    if ($container->has($name)) {
				/*
		        $this->resolved[$name] = &$container->get($name);
				$this->container[$name] = &$this->resolved[$name];
				return $this->container[$name];
				*/
				$this->resolvedIds[$name] = [$containerId, $name];
				$this->r($name);
				return $container->get($name);
		    }
		}

		if(null !== $this->finalFallbackContainer){
		    if ($this->finalFallbackContainer->has($name)) {
				//$this->resolvedIds[$name] = [$containerId, $name];
				$this->r($name);
		       $this->resolved[$name] = $this->finalFallbackContainer->get($name);
				return $this->resolved[$name];
				 //// return $this->finalFallbackContainer->get($name);
		    }
		}



		$this->un($name, true);

		switch($this->onFalseGet){
			case self::NULL_ONERROR :
				  return null;
				break;
			case self::THROW_ONERRROR :
			default:
				 throw new \Exception(sprintf('Cannot resolve container-entry #id: %s|%s in %s', $id, $name, __METHOD__));
				break;
		}
	}


	public function &getContainer(string|int $containerId = null)
	{
		if(isset($this->containers[$containerId])){
			return $this->containers[$containerId];
		}

		switch($this->onFalseGet){
			case self::NULL_ONERROR :
				  return null;
				break;
			case self::THROW_ONERRROR :
			default:
				 throw new \Exception(sprintf('Cannot resolve container #id: %s in %s2', $containerId, __METHOD__));
				break;
		}
	}


	public function hasContainer(string|int $containerId = null): bool
	{
		return isset($this->containers[$containerId]) ? true : false;
	}


	protected function _splitId($id)
	{
		return preg_split("/(\@)/", $id);
	}


	public function has($id)
	{
		$name = $id;


		if(isset($this->resolved[$name])){
			$this->r($name);
			return true;
		}


		if(isset($this->container[$name])){
			$this->r($name);
			return true;
		}

		if(isset($this->resolvedIds[$name])){
			$this->r($name);
			return true;
		}


		foreach ($this->containers as $containerId => $container) {
		    if ($container->has($name)) {
				$this->resolvedIds[$name] = [$containerId, $name];
				$this->r($name);
		        return true;
		    }
		}

		if(null !== $this->finalFallbackContainer){
		    if ($this->finalFallbackContainer->has($name)) {
				//$this->resolvedIds[$name] = [$containerId, $name];
				$this->r($name);
		        return true;
		    }
		}


		$parts = $this->_splitId($name);
		$current = '';
		while(0<count($parts) ){
			$current .= self::CONTAINER_SPLIT.array_shift($parts);
		    $current = ltrim($current, self::CONTAINER_SPLIT);
			$id = substr($name, strlen($current), strlen($name) );
		    $id = rtrim($id, self::CONTAINER_SPLIT);
			 if($this->hasContainer($current)){
				 if ($this->getContainer($current)->has($id)) {
					 $this->resolvedIds[$name] = [$current, $id];
					 $this->r($name);
					 return true;
				 }
			 }
		}



		$this->un($name, false);
		return false;
	}


	public function unregister($id, bool $deleteDefinitions = true)
	{
		$name = $id;

		if(isset($this->container[$name]) ){
			$entry = &$this->container[$name];
			if ($this->factories->contains($entry)) {
		        $this->factories->detach($entry);
			}
		}


		if(isset($this->resolved[$name])){
			unset($this->resolved[$name]);
		}


		if(isset($this->container[$name]) && true === $deleteDefinitions){
			unset($this->container[$name]);
		}

		if(isset($this->resolvedIds[$name])){
		    unset($this->resolvedIds[$name]);
		}

		foreach ($this->containers as $containerId => $container) {
			     $class = \get_class($container);

			 try{
			     $ref = new \ReflectionClass($class);
			     $constants = $ref->getConstants();
			     $containerEntries = (isset($constants['METHOD_MAPPING'])) ? array_keys($constants['METHOD_MAPPING']) : [];
			     if(isset($containerEntries[$name])){
					 unset($containerEntries[$name]);
					 if(isset($constants['METHOD_MAPPING']) && isset($class::METHOD_MAPPING[$name]) ){
					  // ERROR : !!!  unset($class::METHOD_MAPPING[$name]);
				    }
				 }
			    }catch(\Exception $e){
				   // ... fix this!
				    if($this->has('kernel')){
						$this->get('kernel')->warning($e->getMessage());
					}
			    }


				if(\is_callable([$container, 'delete'])){
					\call_user_func_array([$container, 'delete'], [$name]);
				}elseif(\is_callable([$container, 'remove'])){
					\call_user_func_array([$container, 'remove'], [$name]);
				}elseif(\is_callable([$container, 'unregister'])){
					\call_user_func_array([$container, 'unregister'], [$name]);
				}else{
				  //.... ? no delete method?

				}

		}
	}


	public function clearContainerCaches()
	{
		foreach ($this->containers as $containerId => $container) {
			if(\is_callable([$container, 'getCompiledContainerDirectory'])){
					$dir = \call_user_func([$container, 'getCompiledContainerDirectory']);
				    $files = array_merge(glob(rtrim($dir, '\\/ ')."/*.php"),
										 glob(rtrim($dir, '\\/ ')."/*/*.php"),
										 glob(rtrim($dir, '\\/ ')."/**/*.php") );
				     foreach($files as $file){
						 if(file_exists($file)){
							 unlink($file);
						 }
					 }
			}
		}
	}


	public function getKnownEntryNames(): array
	{
		$entries = array_merge([], parent::getKnownEntryNames());

		$entries = array_merge($entries, array_keys($this->container));

		foreach ($this->containers as $containerId => $container) {
			    $ref = new \ReflectionClass(\get_class($container));
			    $constants = $ref->getConstants();
			    $containerEntries = (isset($constants['METHOD_MAPPING'])) ? array_keys($constants['METHOD_MAPPING']) : [];
				if(\is_callable([$container, 'getKnownEntryNames'])){
					$containerEntries = array_merge($containerEntries, \call_user_func([$container, 'getKnownEntryNames']));
				}elseif(\is_callable([$container, 'flatten'])){
					$containerEntries = array_merge($containerEntries, array_keys(\call_user_func([$container, 'flatten'])));
				}elseif(is_callable([$container, 'getIterator'])){
					$containerEntries = array_merge($containerEntries,
													//array_keys(\iterator_to_array(\call_user_func([$container, 'getIterator']), true))
													array_keys(\iterator_to_array($container->getIterator(), true))
												   );
				}elseif(\is_callable([$container, 'all'])){
					$containerEntries = array_merge($containerEntries, array_keys(\call_user_func([$container, 'all'])));
				}else{
				  foreach($this->resolvedIds as $name => $hash){
					  if($containerId === $hash[0]){
						  //$containerEntries[]=$hash[1];
						  $containerEntries[]=$name;
					  }
				  }
				}
			foreach($containerEntries as $itemId){
				$containerEntries[]=$containerId.static::CONTAINER_SPLIT.$itemId;
			}

			$entries = array_merge($entries, $containerEntries);
		}

		sort($entries);
		return array_unique($entries);
	}
}
__halt_compiler();----SIGNATURE:----WHD/aqBWBQVEpVQmHDA4xc36m6QHKQkaDFhxKUkXes04mxIN6JjfXcigRzTxVbtceiVg4YOWNnqIqBbUHqubAjMX+InBjn80zWuVz8VRx5cBekd3Vxt+Xw8KqmtTBy/c0pRDO9fC1sBg98yh5OMVvOO/3Xzh2JHXDhtBY8I+o72/QXDQkYHT5Co9hc+FTP/uPcu7QpUw8k0ClNreYuxZUF2rbkM481fyDlr5+hevd83yzYzJQ5FhSAenygSeO6+SqYJKZAgMFEwak5kdwfWV2ApX1TbBR73el+bk3KA1HAyqYYFKj+6RMy5Iru5/JS+7SR96euUyHeYSWs2yvHiVYKnsS+fl2Qjn6swAG0VgRjpTuFmhbOljimcFYeWizzAphH4VVhc3K2nZ3FNXkKcuD7osj8rS1seLBDP6hM6Mk9+CX/ubsmkSSz4SYaJvUzLf6S1Fh4eEQfjhJ/iLnMqkKwEwMMfRa9AhIwscz4QKn5goohhkdQRA5yN/dWDjVNvsnHBFQqRPhtLMA5WEy9TaoFc50jjtsgJ3D6y7zVREtY76r3XCx83onz68tMukobHOa3EbXZIh8i/dtJzQrpDATWQdcMg5FlgE9OztnxeqO1JzPqgxF1/RZSrUr1yJiYVqi379L1fDdgqWN/3Y3UXi9se7IMAFS6f28eew+1JXWmY=----ATTACHMENT:----ODY4OTA1NjkyOTE1NTgzNCAzNTYzMzQxNzY5ODU5ODQ1IDY2NzQ1MzkzODA4OTMwMTE=