<?php

/**
 * @see       https://github.com/laminas/laminas-router for the canonical source repository
 * @copyright https://github.com/laminas/laminas-router/blob/master/COPYRIGHT.md
 * @license   https://github.com/laminas/laminas-router/blob/master/LICENSE.md New BSD License
 */

namespace Laminas\Router;

use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\AbstractFactoryInterface;
use Laminas\ServiceManager\Exception\ServiceNotCreatedException;
use Laminas\ServiceManager\FactoryInterface;
use Laminas\ServiceManager\ServiceLocatorInterface;

/**
 * Specialized invokable/abstract factory for use with RoutePluginManager.
 *
 * Can be mapped directly to specific route plugin names, or used as an
 * abstract factory to map FQCN services to invokables.
 */
class RouteInvokableFactory implements
    AbstractFactoryInterface,
    FactoryInterface
{
    /**
     * Options used to create instance (used with laminas-servicemanager v2)
     *
     * @var array
     */
    protected $creationOptions = [];

    /**
     * Can we create a route instance with the given name? (v3)
     *
     * Only works for FQCN $routeName values, for classes that implement RouteInterface.
     *
     * @param ContainerInterface $container
     * @param string $routeName
     * @return bool
     */
    public function canCreate(ContainerInterface $container, $routeName)
    {
        if (! class_exists($routeName)) {
            return false;
        }

        if (! is_subclass_of($routeName, RouteInterface::class)) {
            return false;
        }

        return true;
    }

    /**
     * Can we create a route instance with the given name? (v2)
     *
     * Proxies to canCreate().
     *
     * @param ServiceLocatorInterface $container
     * @param string $normalizedName
     * @param string $routeName
     * @return bool
     */
    public function canCreateServiceWithName(ServiceLocatorInterface $container, $normalizedName, $routeName)
    {
        return $this->canCreate($container, $routeName);
    }

    /**
     * Create and return a RouteInterface instance.
     *
     * If the specified $routeName class does not exist or does not implement
     * RouteInterface, this method will raise an exception.
     *
     * Otherwise, it uses the class' `factory()` method with the provided
     * $options to produce an instance.
     *
     * @param ContainerInterface $container
     * @param string $routeName
     * @param null|array $options
     * @return RouteInterface
     */
    public function __invoke(ContainerInterface $container, $routeName, array $options = null)
    {
        $options = $options ?: [];

        if (! class_exists($routeName)) {
            throw new ServiceNotCreatedException(sprintf(
                '%s: failed retrieving invokable class "%s"; class does not exist',
                __CLASS__,
                $routeName
            ));
        }

        if (! is_subclass_of($routeName, RouteInterface::class)) {
            throw new ServiceNotCreatedException(sprintf(
                '%s: failed retrieving invokable class "%s"; class does not implement %s',
                __CLASS__,
                $routeName,
                RouteInterface::class
            ));
        }

        return $routeName::factory($options);
    }

    /**
     * Create a route instance with the given name. (v2)
     *
     * Proxies to __invoke().
     *
     * @param ServiceLocatorInterface $container
     * @param string $normalizedName
     * @param string $routeName
     * @return RouteInterface
     */
    public function createServiceWithName(ServiceLocatorInterface $container, $normalizedName, $routeName)
    {
        return $this($container, $routeName, $this->creationOptions);
    }

    /**
     * Create and return RouteInterface instance
     *
     * For use with laminas-servicemanager v2; proxies to __invoke().
     *
     * @param ServiceLocatorInterface $container
     * @return RouteInterface
     */
    public function createService(ServiceLocatorInterface $container, $normalizedName = null, $routeName = null)
    {
        $routeName = $routeName ?: RouteInterface::class;
        return $this($container, $routeName, $this->creationOptions);
    }

    /**
     * Set options to use when creating a service (v2)
     *
     * @param array $creationOptions
     */
    public function setCreationOptions(array $creationOptions)
    {
        $this->creationOptions = $creationOptions;
    }
}
