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

namespace Joomla\Application\Controller;

use Joomla\Controller\ControllerInterface;
use Joomla\Router\ResolvedRoute;

/**
 * Resolves a controller for the given route.
 *
 * @since  2.0.0
 */
class ControllerResolver implements ControllerResolverInterface
{
	/**
	 * Resolve the controller for a route
	 *
	 * @param   ResolvedRoute  $route  The route to resolve the controller for
	 *
	 * @return  callable
	 *
	 * @since   2.0.0
	 * @throws  \InvalidArgumentException
	 */
	public function resolve(ResolvedRoute $route): callable
	{
		$controller = $route->getController();

		// Try to resolve a callable defined as an array
		if (\is_array($controller))
		{
			if (isset($controller[0]) && \is_string($controller[0]) && isset($controller[1]))
			{
				if (!class_exists($controller[0]))
				{
					throw new \InvalidArgumentException(sprintf('Cannot resolve controller for URI `%s`', $route->getUri()));
				}

				try
				{
					$controller[0] = $this->instantiateController($controller[0]);
				}
				catch (\ArgumentCountError $error)
				{
					throw new \InvalidArgumentException(
						sprintf(
							'Controller `%s` has required constructor arguments, cannot instantiate the class', $controller[0]
						),
						0,
						$error
					);
				}
			}

			if (!\is_callable($controller))
			{
				throw new \InvalidArgumentException(sprintf('Cannot resolve controller for URI `%s`', $route->getUri()));
			}

			return $controller;
		}

		// Try to resolve an invokable object
		if (\is_object($controller))
		{
			if (!\is_callable($controller))
			{
				throw new \InvalidArgumentException(sprintf('Cannot resolve controller for URI `%s`', $route->getUri()));
			}

			return $controller;
		}

		// Try to resolve a known function
		if (\function_exists($controller))
		{
			return $controller;
		}

		// Try to resolve a class name if it implements our ControllerInterface
		if (\is_string($controller) && interface_exists(ControllerInterface::class))
		{
			if (!class_exists($controller))
			{
				throw new \InvalidArgumentException(sprintf('Cannot resolve controller for URI `%s`', $route->getUri()));
			}

			try
			{
				return [$this->instantiateController($controller), 'execute'];
			}
			catch (\ArgumentCountError $error)
			{
				throw new \InvalidArgumentException(
					sprintf(
						'Controller `%s` has required constructor arguments, cannot instantiate the class', $controller
					),
					0,
					$error
				);
			}
		}

		// Unsupported resolution
		throw new \InvalidArgumentException(sprintf('Cannot resolve controller for URI `%s`', $route->getUri()));
	}

	/**
	 * Instantiate a controller class
	 *
	 * @param   string  $class  The class to instantiate
	 *
	 * @return  object  Controller class instance
	 *
	 * @since   2.0.0
	 */
	protected function instantiateController(string $class): object
	{
		return new $class;
	}
}
