<?php
namespace Newland\Toubiz\Map\Neos\DependencyInjection;

use Neos\Flow\Annotations as Flow;
use Neos\Flow\ObjectManagement\Exception\UnknownObjectException;
use Neos\Flow\ObjectManagement\ObjectManager;

/**
 * Simple soft boundary for dependency injection that lazily fetches the subject and
 * redirects all calls to it. This can be used to define soft dependencies in parts
 * of code that are not always executed.
 *
 * This will cause the `SoftInjectFailedException` to be thrown
 * at runtime when the object is first needed instead of during the Flow bootstrapping
 * process.
 *
 * @example
 * protected $articleUrl;
 * public function injectArticleUrlService(LazyDependencyInjectionProxy $proxy)
 * {
 *     $this->articleUrl = $proxy->initialize(
 *         'Newland\Toubiz\Poi\Neos\Service\ArticleUrlService',
 *         'The Newland.Toubiz.Poi.Neos package is required for this action'
 *     );
 * }
 *cd
 * @Flow\Scope("prototype")
 */
class SoftInjectProxy
{

    /**
     * @var ObjectManager
     * @Flow\Inject()
     */
    protected $objectManager;

    /**
     * @var string
     */
    private $type;

    /**
     * @var string
     */
    private $exceptionMessage;

    /** @var mixed */
    private $subject;

    public function initialize(string $type, string $exceptionMessage = null): SoftInjectProxy
    {
        $this->subject = null;
        $this->type = $type;
        $this->exceptionMessage = $exceptionMessage ?: 'Soft Injecting ' . $type . ' failed.';
        return $this;
    }

    /**
     * @return mixed
     */
    public function __call(string $method, array $arguments)
    {
        if (!$this->subject) {
            $this->initializeSubject();
        }
        return $this->subject->{$method}(...$arguments);
    }

    private function initializeSubject(): void
    {
        try {
            $this->subject = $this->objectManager->get($this->type);
        } catch (UnknownObjectException $previous) {
            throw new SoftInjectFailedException(
                $this->exceptionMessage,
                $previous->getCode(),
                $previous
            );
        }
    }
}
