<?php

/**
 * Part of the Joomla Framework Event 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\Event;

use Psr\Container\ContainerInterface;

/**
 * Decorator for an event listener to be pulled from the service container.
 *
 * @since  2.0.0
 */
final class LazyServiceEventListener
{
    /**
     * The service container to load the service from
     *
     * @var    ContainerInterface
     * @since  2.0.0
     */
    private $container;

    /**
     * The ID of the service from the container to be used
     *
     * @var    string
     * @since  2.0.0
     */
    private $serviceId;

    /**
     * The method from the service to be called
     *
     * @var    string
     * @since  2.0.0
     */
    private $method;

    /**
     * Constructor.
     *
     * @param   ContainerInterface  $container  The service container to load the service from when it shall be executed
     * @param   string              $serviceId  The ID of the service from the container to be used
     * @param   string              $method     The method from the service to be called if necessary. If left empty, the service must be a callable;
     *                                          (i.e. have an `__invoke()` method on a class)
     *
     * @since   2.0.0
     * @throws  \InvalidArgumentException if the service ID is empty
     */
    public function __construct(ContainerInterface $container, string $serviceId, string $method = '')
    {
        if (empty($serviceId)) {
            throw new \InvalidArgumentException(
                sprintf(
                    'The $serviceId parameter cannot be empty in %s',
                    self::class
                )
            );
        }

        $this->container = $container;
        $this->serviceId = $serviceId;
        $this->method    = $method;
    }

    /**
     * Load a service from the container to listen to an event.
     *
     * @param   EventInterface  $event  The event to process
     *
     * @return  void
     *
     * @since   2.0.0
     * @throws  \InvalidArgumentException if the constructor's $method parameter is empty when not executing a callable service
     * @throws  \RuntimeException if the service cannot be executed
     */
    public function __invoke(EventInterface $event): void
    {
        if (!$this->container->has($this->serviceId)) {
            throw new \RuntimeException(
                sprintf(
                    'The "%s" service has not been registered to the service container',
                    $this->serviceId
                )
            );
        }

        $service = $this->container->get($this->serviceId);

        // If the service is callable on its own, just execute it
        if (\is_callable($service)) {
            \call_user_func($service, $event);

            return;
        }

        if (empty($this->method)) {
            throw new \InvalidArgumentException(
                sprintf(
                    'The $method argument is required when creating a "%s" to call a method from the "%s" service.',
                    self::class,
                    $this->serviceId
                )
            );
        }

        if (!method_exists($service, $this->method)) {
            throw new \RuntimeException(
                sprintf(
                    'The "%s" method does not exist on "%s" (from service "%s")',
                    $this->method,
                    \get_class($service),
                    $this->serviceId
                )
            );
        }

        \call_user_func([$service, $this->method], $event);
    }
}
