<?php
// Copyright (c) Pickware GmbH. All rights reserved.
// This file is part of software that is released under a proprietary license.
// You must not copy, modify, distribute, make publicly available, or execute
// its contents or parts thereof without express permission by the copyright
// holder, unless otherwise permitted by law.

namespace Shopware\Plugins\ViisonCommon\Components\SubApplicationLoading;

use InvalidArgumentException;
use Shopware\Plugins\ViisonCommon\Components\SubApplicationLoading\SubApplicationCodeGenerator\EmptySubApplicationCodeGenerator;

class SubApplicationRegistry
{
    /**
     * @var SubApplication[] List of already registered SubApplications
     */
    private $subApplications = [];

    /**
     * List of names of SubApplication in self::$subApplications that can still be replaces by another SubApplication
     *
     * @var string[]
     */
    private $preRegisteredSubApplications = [];

    /**
     * Registers the $subApplication in the SubApplicationRegistry.
     *
     * @param SubApplication $subApplication
     */
    public function registerSubApplication(SubApplication $subApplication)
    {
        $this->addSubApplication($subApplication);
    }

    /**
     * Adds the $subApplication as an extension to another SubApplication.
     *
     * If the SubApplication with name $nameOfExtendedSubApplication does not exists it will be created on the fly.
     *
     * @param SubApplication $subApplication
     * @param string $nameOfExtendedSubApplication Name of the SubApplication to add it as extension
     */
    public function registerSubApplicationAsExtension(SubApplication $subApplication, $nameOfExtendedSubApplication)
    {
        $this->addSubApplication($subApplication);

        $extendedSubApplication = $this->getSubApplicationByName($nameOfExtendedSubApplication);

        if (!$extendedSubApplication) {
            $extendedSubApplication = $this->addPreRegisteredSubApplication($nameOfExtendedSubApplication);
        }

        $extendedSubApplication->addExtensionName($subApplication->getName());
    }

    /**
     * Adds the $subApplication as an dependency to another SubApplication.
     *
     * If the SubApplication with name $nameOfDependentSubApplication does not exists it will be created on the fly.
     *
     * @param SubApplication $subApplication
     * @param string $nameOfDependentSubApplication Name of the SubApplication to add it as dependency
     */
    public function registerSubApplicationAsDependency(SubApplication $subApplication, $nameOfDependentSubApplication)
    {
        $this->addSubApplication($subApplication);

        $dependentSubApplication = $this->getSubApplicationByName($nameOfDependentSubApplication);

        if (!$dependentSubApplication) {
            $dependentSubApplication = $this->addPreRegisteredSubApplication($nameOfDependentSubApplication);
        }

        $dependentSubApplication->addDependencyName($subApplication->getName());
    }

    /**
     * Finds a SubApplication by name an returns it.
     *
     * The lookup by name is not case sensitive.
     *
     * @param string $name Name of the SubApplication to find
     * @return SubApplication|null The found SubApplication or null if no SubApplication was found.
     */
    public function getSubApplicationByName($name)
    {
        return $this->subApplications[mb_strtolower($name)];
    }

    /**
     * Removes a SubApplication from this SubApplicationRegistration.
     *
     * The lookup by name is not case sensitive.
     *
     * @param string $name Name of the SubApplication to remove.
     */
    public function removeSubApplication($name)
    {
        unset($this->subApplications[mb_strtolower($name)]);
        foreach ($this->subApplications as $subApplication) {
            $subApplication->removeExtensionName($name);
            $subApplication->removeDependencyName($name);
        }
    }

    /**
     * Adds the $subApplication to the registry.
     *
     * If a SubApplication with the same name was already registered on the fly by one of the methods
     * registerSubApplicationAsDependency() or registerSubApplicationAsExtension() the SubApplications will be merged.
     *
     * @param SubApplication $subApplication
     */
    private function addSubApplication(SubApplication $subApplication)
    {
        $lowerCaseSubApplicationName = mb_strtolower($subApplication->getName());
        if (in_array($lowerCaseSubApplicationName, $this->preRegisteredSubApplications)) {
            unset($this->preRegisteredSubApplications[array_search(
                $lowerCaseSubApplicationName,
                $this->preRegisteredSubApplications
            )]);
            $preRegisteredSubApplication = $this->getSubApplicationByName($subApplication->getName());
            $subApplication->addDependencyNames($preRegisteredSubApplication->getDependencyNames());
            $subApplication->addExtensionNames($preRegisteredSubApplication->getExtensionNames());
        } elseif (isset($this->subApplications[$lowerCaseSubApplicationName])) {
            throw new InvalidArgumentException(sprintf(
                'SubApplication with name "%s" already registered.',
                $subApplication->getName()
            ));
        }

        $this->subApplications[$lowerCaseSubApplicationName] = $subApplication;
    }

    /**
     * Adds a SubApplication 'on the fly'.
     *
     * This means the SubApplication can be replaced by a real SubApplication later.
     *
     * @param string $name Name of the SubApplication
     * @return SubApplication
     */
    private function addPreRegisteredSubApplication($name)
    {
        $preRegisteredSubApplication = new SubApplication($name, new EmptySubApplicationCodeGenerator());
        $this->addSubApplication($preRegisteredSubApplication);
        $this->preRegisteredSubApplications[] = mb_strtolower($name);

        return $preRegisteredSubApplication;
    }
}
