<?php

/*
 * This file is part of the Alice package.
 *
 * (c) Nelmio <hello@nelm.io>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Nelmio\Alice\Loader;

use Faker\Factory as FakerGeneratorFactory;
use Faker\Generator as FakerGenerator;
use Nelmio\Alice\DataLoaderInterface;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\Chainable\CollectionDenormalizerWithTemporaryFixture;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\Chainable\NullListNameDenormalizer;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\Chainable\NullRangeNameDenormalizer;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\Chainable\SimpleCollectionDenormalizer;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\FixtureMethodCallReferenceResolver;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\FunctionCallArgumentResolver;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\PhpFunctionCallValueResolver;
use Nelmio\Alice\Throwable\Exception\BadMethodCallExceptionFactory;
use Nelmio\Alice\Faker\Provider\AliceProvider;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\Value\SimpleValueDenormalizer;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Lexer\EmptyValueLexer;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Lexer\FunctionLexer;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Lexer\GlobalPatternsLexer;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Lexer\ReferenceEscaperLexer;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Lexer\ReferenceLexer;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Lexer\SubPatternsLexer;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\LexerInterface;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\FunctionFixtureReferenceParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\StringMergerParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\Chainable\DynamicArrayTokenParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\Chainable\EscapedValueTokenParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\Chainable\FixtureListReferenceTokenParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\Chainable\FixtureMethodReferenceTokenParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\Chainable\FixtureRangeReferenceTokenParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\Chainable\FunctionTokenParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\Chainable\IdentityTokenParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\Chainable\MethodReferenceTokenParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\Chainable\OptionalTokenParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\Chainable\ParameterTokenParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\Chainable\PropertyReferenceTokenParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\Chainable\SimpleReferenceTokenParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\Chainable\StringArrayTokenParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\Chainable\StringTokenParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\Chainable\TolerantFunctionTokenParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\Chainable\VariableTokenParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\SimpleParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\Chainable\WildcardReferenceTokenParser;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParser\TokenParserRegistry;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\ParserInterface as ExpressionLanguageParserInterface;
use Nelmio\Alice\FixtureBuilder\ExpressionLanguage\Parser\TokenParserInterface;
use Nelmio\Alice\FileLocator\DefaultFileLocator;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\FixtureDenormalizerInterface;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\FixtureDenormalizerRegistry;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SimpleFixtureBagDenormalizer;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\Arguments\SimpleArgumentsDenormalizer;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\ArgumentsDenormalizerInterface;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\Calls\OptionalCallsDenormalizer;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\CallsDenormalizerInterface;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\Constructor\ConstructorWithCallerDenormalizer;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\Constructor\SimpleConstructorDenormalizer;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\ConstructorDenormalizerInterface;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\Property\SimplePropertyDenormalizer;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\PropertyDenormalizerInterface;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\SimpleSpecificationsDenormalizer;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\Value\UniqueValueDenormalizer;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationBagDenormalizer\ValueDenormalizerInterface;
use Nelmio\Alice\FixtureBuilder\Denormalizer\FixtureBagDenormalizerInterface;
use Nelmio\Alice\FixtureBuilder\Denormalizer\FlagParser\Chainable\ExtendFlagParser;
use Nelmio\Alice\FixtureBuilder\Denormalizer\FlagParser\Chainable\OptionalFlagParser;
use Nelmio\Alice\FixtureBuilder\Denormalizer\FlagParser\Chainable\TemplateFlagParser;
use Nelmio\Alice\FixtureBuilder\Denormalizer\FlagParser\Chainable\UniqueFlagParser;
use Nelmio\Alice\FixtureBuilder\Denormalizer\FlagParser\ElementFlagParser;
use Nelmio\Alice\FixtureBuilder\Denormalizer\FlagParser\FlagParserRegistry;
use Nelmio\Alice\FixtureBuilder\Denormalizer\FlagParserInterface;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Parameter\SimpleParameterBagDenormalizer;
use Nelmio\Alice\FixtureBuilder\Denormalizer\SimpleDenormalizer;
use Nelmio\Alice\FixtureBuilder\DenormalizerInterface;
use Nelmio\Alice\FixtureBuilder\SimpleBuilder;
use Nelmio\Alice\Generator\Caller\SimpleCaller;
use Nelmio\Alice\Generator\CallerInterface;
use Nelmio\Alice\Generator\Hydrator\Property\SymfonyPropertyAccessorHydrator;
use Nelmio\Alice\Generator\Hydrator\PropertyHydratorInterface;
use Nelmio\Alice\Generator\Instantiator\Chainable\NoCallerMethodCallInstantiator;
use Nelmio\Alice\Generator\Instantiator\Chainable\NoMethodCallInstantiator;
use Nelmio\Alice\Generator\Instantiator\Chainable\NullConstructorInstantiator;
use Nelmio\Alice\Generator\Instantiator\Chainable\StaticFactoryInstantiator;
use Nelmio\Alice\Generator\Instantiator\ExistingInstanceInstantiator;
use Nelmio\Alice\Generator\Instantiator\InstantiatorRegistry;
use Nelmio\Alice\Generator\Instantiator\InstantiatorResolver;
use Nelmio\Alice\Generator\InstantiatorInterface;
use Nelmio\Alice\Generator\ObjectGenerator\CompleteObjectGenerator;
use Nelmio\Alice\Generator\ObjectGenerator\SimpleObjectGenerator;
use Nelmio\Alice\Generator\Hydrator\SimpleHydrator;
use Nelmio\Alice\Generator\HydratorInterface;
use Nelmio\Alice\Generator\Resolver\Fixture\TemplateFixtureBagResolver;
use Nelmio\Alice\Generator\Resolver\FixtureSet\RemoveConflictingObjectsResolver;
use Nelmio\Alice\Generator\Resolver\Parameter\RemoveConflictingParametersParameterBagResolver;
use Nelmio\Alice\Generator\Resolver\FixtureSet\SimpleFixtureSetResolver;
use Nelmio\Alice\Generator\Resolver\UniqueValuesPool;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\ArrayValueResolver;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\DynamicArrayValueResolver;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\EvaluatedValueResolver;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\FakerFunctionCallValueResolver;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\FixturePropertyReferenceResolver;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\FixtureReferenceResolver;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\FixtureWildcardReferenceResolver;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\ListValueResolver;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\OptionalValueResolver;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\ParameterValueResolver;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\SelfFixtureReferenceResolver;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\UniqueValueResolver;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\UnresolvedFixtureReferenceIdResolver;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\ValueForCurrentValueResolver;
use Nelmio\Alice\Generator\Resolver\Value\Chainable\VariableValueResolver;
use Nelmio\Alice\Generator\Resolver\Value\ValueResolverRegistry;
use Nelmio\Alice\Generator\ValueResolverInterface;
use Nelmio\Alice\Parser\Chainable\PhpParser;
use Nelmio\Alice\Parser\Chainable\YamlParser;
use Nelmio\Alice\Parser\IncludeProcessor\DefaultIncludeProcessor;
use Nelmio\Alice\Parser\ParserRegistry;
use Nelmio\Alice\Parser\RuntimeCacheParser;
use Nelmio\Alice\ParserInterface;
use Nelmio\Alice\FixtureBuilderInterface;
use Nelmio\Alice\Generator\ObjectGeneratorInterface;
use Nelmio\Alice\Generator\Resolver\Parameter\Chainable\ArrayParameterResolver;
use Nelmio\Alice\Generator\Resolver\Parameter\SimpleParameterBagResolver;
use Nelmio\Alice\Generator\Resolver\Parameter\ParameterResolverRegistry;
use Nelmio\Alice\Generator\Resolver\Parameter\Chainable\RecursiveParameterResolver;
use Nelmio\Alice\Generator\Resolver\Parameter\Chainable\StaticParameterResolver;
use Nelmio\Alice\Generator\Resolver\Parameter\Chainable\StringParameterResolver;
use Nelmio\Alice\Generator\FixtureSetResolverInterface;
use Nelmio\Alice\Generator\DoublePassGenerator;
use Nelmio\Alice\GeneratorInterface;
use Nelmio\Alice\FileLoaderInterface;
use Nelmio\Alice\IsAServiceTrait;
use Nelmio\Alice\ObjectSet;
use Nelmio\Alice\Generator\Resolver\ParameterBagResolverInterface;
use Nelmio\Alice\PropertyAccess\StdPropertyAccessor;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\Yaml\Parser as SymfonyYamlParser;

/**
 * Loader implementation made to be usable without any dependency injection for quick and easy usage. For more advanced
 * usages, use {@see \Nelmio\Alice\Loader\SimpleFileLoader} instead or implement your own loader.
 *
 * @method DataLoaderInterface getDataLoader()
 * @method FileLoaderInterface getFileLoader()
 *
 * @method FixtureBuilderInterface getFixtureBuilder()
 * @method GeneratorInterface getGenerator()
 * @method ParserInterface getParser()
 *
 * @method DenormalizerInterface getDenormalizer()
 * @method FixtureBagDenormalizerInterface getFixtureBagDenormalizer
 * @method FixtureDenormalizerInterface getFixtureDenormalizer()
 * @method FlagParserInterface getFlagParser()
 * @method ConstructorDenormalizerInterface getConstructorDenormalizer()
 * @method PropertyDenormalizerInterface getPropertyDenormalizer()
 * @method CallsDenormalizerInterface getCallsDenormalizer()
 * @method ArgumentsDenormalizerInterface getArgumentsDenormalizer()
 * @method ValueDenormalizerInterface getValueDenormalizer()
 *
 * @method ExpressionLanguageParserInterface getExpressionLanguageParser()
 * @method LexerInterface getLexer()
 * @method TokenParserInterface getExpressionLanguageTokenParser()
 *
 * @method ObjectGeneratorInterface getObjectGenerator()
 *
 * @method FixtureSetResolverInterface getFixtureSetResolver()
 * @method ParameterBagResolverInterface getParameterResolver()
 * @method ValueResolverInterface getValueResolver()
 * @method FakerGenerator getFakerGenerator()
 *
 * @method InstantiatorInterface getInstantiator()
 * @method HydratorInterface getHydrator()
 * @method PropertyHydratorInterface getPropertyHydrator()
 * @method PropertyAccessorInterface getPropertyAccessor()
 * @method CallerInterface getCaller()
 */
class NativeLoader implements FileLoaderInterface, DataLoaderInterface
{
    use IsAServiceTrait;

    private $previous = '';

    /**
     * @var array
     */
    private $cache = [];

    /**
     * @var FakerGenerator
     */
    private $fakerGenerator;

    /**
     * @var FileLoaderInterface
     */
    private $fileLoader;

    /**
     * @var DataLoaderInterface
     */
    private $dataLoader;

    public function __construct(FakerGenerator $fakerGenerator = null)
    {
        $this->fakerGenerator = (null === $fakerGenerator) ? $this->getFakerGenerator() : $fakerGenerator;
        $this->dataLoader = $this->getDataLoader();
        $this->fileLoader = $this->getFileLoader();
    }

    /**
     * @inheritdoc
     */
    public function loadFile(string $file, array $parameters = [], array $objects = []): ObjectSet
    {
        return $this->fileLoader->loadFile($file, $parameters, $objects);
    }

    /**
     * @inheritdoc
     */
    public function loadData(array $data, array $parameters = [], array $objects = []): ObjectSet
    {
        return $this->dataLoader->loadData($data, $parameters, $objects);
    }

    protected function createDataLoader(): DataLoaderInterface
    {
        return new SimpleDataLoader(
            $this->getFixtureBuilder(),
            $this->getGenerator()
        );
    }

    protected function createFileLoader(): FileLoaderInterface
    {
        return new SimpleFileLoader(
            $this->getParser(),
            $this->dataLoader
        );
    }

    protected function createFixtureBuilder(): FixtureBuilderInterface
    {
        return new SimpleBuilder(
            $this->getDenormalizer()
        );
    }

    protected function createGenerator(): GeneratorInterface
    {
        return new DoublePassGenerator(
            $this->getFixtureSetResolver(),
            $this->getObjectGenerator()
        );
    }

    protected function createParser(): ParserInterface
    {
        $registry = new ParserRegistry([
            new YamlParser(new SymfonyYamlParser()),
            new PhpParser(),
        ]);

        return new RuntimeCacheParser(
            $registry,
            new DefaultFileLocator(),
            new DefaultIncludeProcessor(
                new DefaultFileLocator()
            )
        );
    }

    protected function createDenormalizer(): DenormalizerInterface
    {
        return new SimpleDenormalizer(
            new SimpleParameterBagDenormalizer(),
            $this->getFixtureBagDenormalizer()
        );
    }

    protected function createFixtureBagDenormalizer(): FixtureBagDenormalizerInterface
    {
        return new SimpleFixtureBagDenormalizer(
            $this->getFixtureDenormalizer(),
            $this->getFlagParser()
        );
    }

    protected function createFixtureDenormalizer(): FixtureDenormalizerInterface
    {
        return new FixtureDenormalizerRegistry(
            $this->getFlagParser(),
            [
                new \Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\Chainable\SimpleDenormalizer(
                    new SimpleSpecificationsDenormalizer(
                        $this->getConstructorDenormalizer(),
                        $this->getPropertyDenormalizer(),
                        $this->getCallsDenormalizer()
                    )
                ),
                new SimpleCollectionDenormalizer(
                    new CollectionDenormalizerWithTemporaryFixture(
                        new NullListNameDenormalizer()
                    )
                ),
                new SimpleCollectionDenormalizer(
                    new CollectionDenormalizerWithTemporaryFixture(
                        new NullRangeNameDenormalizer()
                    )
                ),
            ]
        );
    }

    protected function createFlagParser(): FlagParserInterface
    {
        $registry = new FlagParserRegistry([
            new ExtendFlagParser(),
            new OptionalFlagParser(),
            new TemplateFlagParser(),
            new UniqueFlagParser(),
        ]);

        return new ElementFlagParser($registry);
    }

    protected function createConstructorDenormalizer(): ConstructorDenormalizerInterface
    {
        return new ConstructorWithCallerDenormalizer(
            new SimpleConstructorDenormalizer(
                $this->getArgumentsDenormalizer()
            )
        );
    }

    protected function createPropertyDenormalizer(): PropertyDenormalizerInterface
    {
        return new SimplePropertyDenormalizer(
            $this->getValueDenormalizer()
        );
    }

    protected function createCallsDenormalizer(): CallsDenormalizerInterface
    {
        return new OptionalCallsDenormalizer(
            $this->getArgumentsDenormalizer()
        );
    }

    protected function createArgumentsDenormalizer(): ArgumentsDenormalizerInterface
    {
        return new SimpleArgumentsDenormalizer(
            $this->getValueDenormalizer()
        );
    }

    protected function createValueDenormalizer(): ValueDenormalizerInterface
    {
        return new UniqueValueDenormalizer(
            new SimpleValueDenormalizer(
                $this->getExpressionLanguageParser()
            )
        );
    }

    protected function createExpressionLanguageParser(): ExpressionLanguageParserInterface
    {
        return new FunctionFixtureReferenceParser(
            new StringMergerParser(
                new SimpleParser(
                    $this->getLexer(),
                    $this->getExpressionLanguageTokenParser()
                )
            )
        );
    }

    protected function createLexer(): LexerInterface
    {
        return new EmptyValueLexer(
            new ReferenceEscaperLexer(
                new GlobalPatternsLexer(
                    new FunctionLexer(
                        new SubPatternsLexer(
                            new ReferenceLexer()
                        )
                    )
                )
            )
        );
    }

    protected function createExpressionLanguageTokenParser(): TokenParserInterface
    {
        return new TokenParserRegistry([
            new DynamicArrayTokenParser(),
            new EscapedValueTokenParser(),
            new FixtureListReferenceTokenParser(),
            new FixtureMethodReferenceTokenParser(),
            new FixtureRangeReferenceTokenParser(),
            new IdentityTokenParser(
                new FunctionTokenParser()
            ),
            new MethodReferenceTokenParser(),
            new OptionalTokenParser(),
            new ParameterTokenParser(),
            new PropertyReferenceTokenParser(),
            new SimpleReferenceTokenParser(),
            new StringArrayTokenParser(),
            new StringTokenParser(),
            new TolerantFunctionTokenParser(
                new IdentityTokenParser(
                    new FunctionTokenParser()
                )
            ),
            new VariableTokenParser(),
            new WildcardReferenceTokenParser(),
        ]);
    }

    protected function createObjectGenerator(): ObjectGeneratorInterface
    {
        return new CompleteObjectGenerator(
            new SimpleObjectGenerator(
                $this->getValueResolver(),
                $this->getInstantiator(),
                $this->getHydrator(),
                $this->getCaller()
            )
        );
    }

    protected function createFixtureSetResolver(): FixtureSetResolverInterface
    {
        return new RemoveConflictingObjectsResolver(
            new SimpleFixtureSetResolver(
                $this->getParameterResolver(),
                new TemplateFixtureBagResolver()
            )
        );
    }

    protected function createParameterResolver(): ParameterBagResolverInterface
    {
        $registry = new ParameterResolverRegistry([
            new StaticParameterResolver(),
            new ArrayParameterResolver(),
            new RecursiveParameterResolver(new StringParameterResolver()),
        ]);

        return new RemoveConflictingParametersParameterBagResolver(
            new SimpleParameterBagResolver($registry)
        );
    }

    protected function createValueResolver(): ValueResolverInterface
    {
        return new ValueResolverRegistry([
            new ArrayValueResolver(),
            new DynamicArrayValueResolver(),
            new EvaluatedValueResolver(),
            new FunctionCallArgumentResolver(
                new PhpFunctionCallValueResolver(
                    $this->getBlacklistedFunctions(),
                    new FakerFunctionCallValueResolver($this->fakerGenerator)
                )
            ),
            new FixturePropertyReferenceResolver(
                $this->getPropertyAccessor()
            ),
            new FixtureMethodCallReferenceResolver(),
            new UnresolvedFixtureReferenceIdResolver(
                new SelfFixtureReferenceResolver(
                    new FixtureReferenceResolver()
                )
            ),
            new FixtureWildcardReferenceResolver(),
            new ListValueResolver(),
            new OptionalValueResolver(),
            new ParameterValueResolver(),
            new UniqueValueResolver(
                new UniqueValuesPool()
            ),
            new ValueForCurrentValueResolver(),
            new VariableValueResolver(),
        ]);
    }

    /**
     * @return string[]
     */
    protected function getBlacklistedFunctions(): array
    {
        return [
            'current',
        ];
    }

    protected function createFakerGenerator(): FakerGenerator
    {
        $generator = FakerGeneratorFactory::create();
        $generator->addProvider(new AliceProvider());

        return $generator;
    }

    protected function createInstantiator(): InstantiatorInterface
    {
        return new ExistingInstanceInstantiator(
            new InstantiatorResolver(
                new InstantiatorRegistry([
                    new NoCallerMethodCallInstantiator(),
                    new NullConstructorInstantiator(),
                    new NoMethodCallInstantiator(),
                    new StaticFactoryInstantiator(),
                ])
            )
        );
    }

    protected function createHydrator(): HydratorInterface
    {
        return new SimpleHydrator(
            $this->getPropertyHydrator()
        );
    }

    protected function createPropertyHydrator(): PropertyHydratorInterface
    {
        return new SymfonyPropertyAccessorHydrator(
            $this->getPropertyAccessor()
        );
    }

    protected function createPropertyAccessor(): PropertyAccessorInterface
    {
        return new StdPropertyAccessor(
            PropertyAccess::createPropertyAccessorBuilder()
                ->enableMagicCall()
                ->getPropertyAccessor()
        );
    }

    protected function createCaller(): CallerInterface
    {
        return new SimpleCaller($this->getValueResolver());
    }

    public function __call(string $method, array $arguments)
    {
        if (array_key_exists($method, $this->cache)) {
            return $this->cache[$method];
        }

        if (false === preg_match('/^get.*/', $method)) {
            throw BadMethodCallExceptionFactory::createForUnknownMethod($method);
        }

        $realMethod = str_replace('get', 'create', $method);
        if ($realMethod === $this->previous) {
            throw BadMethodCallExceptionFactory::createForUnknownMethod($method);
        }
        $this->previous = $realMethod;

        $service = $this->$realMethod(...$arguments);
        $this->cache[$method] = $service;

        return $service;
    }
}
