<?php
/**
 * Part of the Joomla Framework DI Package
 *
 * @copyright  Copyright (C) 2013 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\DI;

/**
 * Defines the representation of a resource.
 *
 * @since  2.0.0
 * @internal
 */
final class ContainerResource
{
	/**
	 * Defines the resource as non-shared
	 *
	 * @const  integer
	 * @since  2.0.0
	 */
	public const NO_SHARE = 0;

	/**
	 * Defines the resource as shared
	 *
	 * @const  integer
	 * @since  2.0.0
	 */
	public const SHARE = 1;

	/**
	 * Defines the resource as non-protected
	 *
	 * @const  integer
	 * @since  2.0.0
	 */
	public const NO_PROTECT = 0;

	/**
	 * Defines the resource as protected
	 *
	 * @const  integer
	 * @since  2.0.0
	 */
	public const PROTECT = 2;

	/**
	 * The container the resource is assigned to
	 *
	 * @var    Container
	 * @since  2.0.0
	 */
	private $container;

	/**
	 * The object instance for a shared object
	 *
	 * @var    mixed
	 * @since  2.0.0
	 */
	private $instance;

	/**
	 * The factory object
	 *
	 * @var    callable
	 * @since  2.0.0
	 */
	private $factory;

	/**
	 * Flag if the resource is shared
	 *
	 * @var    boolean
	 * @since  2.0.0
	 */
	private $shared = false;

	/**
	 * Flag if the resource is protected
	 *
	 * @var    boolean
	 * @since  2.0.0
	 */
	private $protected = false;

	/**
	 * Create a resource representation
	 *
	 * @param   Container  $container  The container
	 * @param   mixed      $value      The resource or its factory closure
	 * @param   integer    $mode       Resource mode, defaults to Resource::NO_SHARE | Resource::NO_PROTECT
	 *
	 * @since   2.0.0
	 */
	public function __construct(Container $container, $value, int $mode = 0)
	{
		$this->container = $container;
		$this->shared    = ($mode & self::SHARE) === self::SHARE;
		$this->protected = ($mode & self::PROTECT) === self::PROTECT;

		if (\is_callable($value))
		{
			$this->factory = $value;
		}
		else
		{
			if ($this->shared)
			{
				$this->instance = $value;
			}

			if (\is_object($value))
			{
				$this->factory = function () use ($value)
				{
					return clone $value;
				};
			}
			else
			{
				$this->factory = function () use ($value)
				{
					return $value;
				};
			}
		}
	}

	/**
	 * Check whether the resource is shared
	 *
	 * @return  boolean
	 *
	 * @since   2.0.0
	 */
	public function isShared(): bool
	{
		return $this->shared;
	}

	/**
	 * Check whether the resource is protected
	 *
	 * @return  boolean
	 *
	 * @since   2.0.0
	 */
	public function isProtected(): bool
	{
		return $this->protected;
	}

	/**
	 * Get an instance of the resource
	 *
	 * If a factory was provided, the resource is created and - if it is a shared resource - cached internally.
	 * If the resource was provided directly, that resource is returned.
	 *
	 * @return  mixed
	 *
	 * @since   2.0.0
	 */
	public function getInstance()
	{
		$callable = $this->factory;

		if ($this->isShared())
		{
			if ($this->instance === null)
			{
				$this->instance = $callable($this->container);
			}

			return $this->instance;
		}

		return $callable($this->container);
	}

	/**
	 * Get the factory
	 *
	 * @return  callable
	 *
	 * @since   2.0.0
	 */
	public function getFactory(): callable
	{
		return $this->factory;
	}

	/**
	 * Reset the resource
	 *
	 * The instance cache is cleared, so that the next call to get() returns a new instance.
	 * This has an effect on shared, non-protected resources only.
	 *
	 * @return  boolean  True if the resource was reset, false otherwise
	 *
	 * @since   2.0.0
	 */
	public function reset(): bool
	{
		if ($this->isShared() && !$this->isProtected())
		{
			$this->instance = null;

			return true;
		}

		return false;
	}
}
