<?php 
 
namespace Webkul\UVDesk\ExtensionFrameworkBundle\DependencyInjection; 
 
use Symfony\Component\Yaml\Yaml; 
use Symfony\Component\Config\FileLocator; 
use Symfony\Component\DependencyInjection\Reference; 
use Symfony\Component\DependencyInjection\Definition; 
use Symfony\Component\DependencyInjection\ContainerBuilder; 
use Symfony\Component\HttpKernel\DependencyInjection\Extension; 
use Symfony\Component\EventDispatcher\EventSubscriberInterface; 
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; 
use Webkul\UVDesk\ExtensionFrameworkBundle\Definition\MappingResource; 
use Webkul\UVDesk\ExtensionFrameworkBundle\Configurators\AppConfigurator; 
use Webkul\UVDesk\ExtensionFrameworkBundle\Configurators\PackageConfigurator; 
use Webkul\UVDesk\ExtensionFrameworkBundle\Definition\Package\PackageInterface; 
use Webkul\UVDesk\ExtensionFrameworkBundle\Definition\Routing\RoutingResourceInterface; 
use Webkul\UVDesk\ExtensionFrameworkBundle\Definition\Application\ApplicationInterface; 
use Webkul\UVDesk\ExtensionFrameworkBundle\Definition\Package\ConfigurablePackageInterface; 
use Webkul\UVDesk\ExtensionFrameworkBundle\Definition\Package\ContainerBuilderAwarePackageInterface; 
 
 
class ContainerExtension extends Extension 
{ 
    public function getAlias() 
    { 
        return 'uvdesk_extensions'; 
    } 
 
    public function getConfiguration(array $configs, ContainerBuilder $container) 
    { 
        return new Configuration(); 
    } 
 
    public function load(array $configs, ContainerBuilder $container) 
    { 
        // Define parameters 
        foreach ($this->processConfiguration($this->getConfiguration($configs, $container), $configs) as $param => $value) { 
            switch ($param) { 
                case 'dir': 
                    $container->setParameter("uvdesk_extensions.dir", $value); 
                    break; 
                default: 
                    break; 
            } 
        } 
 
        // Define services 
        $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); 
        $loader->load('services.yaml'); 
         
        // Compile modules 
        $env = $container->getParameter('kernel.environment'); 
        $path = $container->getParameter("kernel.project_dir") . "/uvdesk.lock"; 
        $mappingResource = $container->findDefinition(MappingResource::class); 
        $availableConfigurations = $this->parsePackageConfigurations($container->getParameter("kernel.project_dir") . "/config/extensions"); 
         
        foreach ($this->getCachedPackages($path) as $attributes) { 
            $reference = current(array_keys($attributes['package'])); 
            $supportedEnvironments = $attributes['package'][$reference]; 
 
            // Check if package is supported in the current environment 
            if (in_array('all', $supportedEnvironments) || in_array($env, $supportedEnvironments)) { 
                $class = new \ReflectionClass($reference); 
                 
                if (!$class->implementsInterface(PackageInterface::class)) { 
                    throw new \Exception("Class $reference could not be registered as a package. Please check that it implements the " . PackageInterface::class . " interface."); 
                } 
 
                if ($class->implementsInterface(ConfigurablePackageInterface::class)) { 
                    $schema = $class->newInstanceWithoutConstructor()->getConfiguration(); 
 
                    if (!empty($schema)) { 
                        $qualifiedName = str_replace(['/', '-'], '_', $attributes['name']); 
     
                        if (empty($availableConfigurations[$qualifiedName])) { 
                            throw new \Exception("No available configurations found for package '" . $attributes['name'] . "'"); 
                        } 
 
                        // Validate package configuration params 
                        $packageConfigurations = $this->processConfiguration($schema, $availableConfigurations[$qualifiedName]); 
 
                        // Unset and cache package params for later re-use 
                        unset($availableConfigurations[$qualifiedName]); 
                        $mappingResource->addMethodCall('setPackageConfigurations', array($reference, $packageConfigurations)); 
                    } 
                } 
 
                // Prepare package for configuration 
                $this->loadPackageServices($class->getFileName(), $loader); 
 
                if ($container->hasDefinition($reference)) { 
                    $mappingResource->addMethodCall('setPackageMetadata', array($reference, $attributes)); 
                } 
            } 
        } 
 
        if (!empty($availableConfigurations)) { 
            // @TODO: Raise exception about invalid configurations 
            dump('Invalid configurations found'); 
            dump($availableConfigurations); 
            die; 
        } 
 
        // Configure services 
        $container->registerForAutoconfiguration(PackageInterface::class)->addTag(PackageInterface::class)->setLazy(true)->setPublic(true); 
        $container->registerForAutoconfiguration(ApplicationInterface::class)->addTag(ApplicationInterface::class)->setLazy(true)->setPublic(true); 
        $container->registerForAutoconfiguration(ContainerBuilderAwarePackageInterface::class)->addTag(ContainerBuilderAwarePackageInterface::class); 
        $container->registerForAutoconfiguration(RoutingResourceInterface::class)->addTag(RoutingResourceInterface::class); 
    } 
 
    private function getCachedPackages($path) : array 
    { 
        try { 
            if (file_exists($path)) { 
                return json_decode(file_get_contents($path), true)['packages'] ?? []; 
            } 
        } catch (\Exception $e) { 
            // Skip module compilation ... 
        } 
 
        return []; 
    } 
 
    private function loadPackageServices($classPath, YamlFileLoader $loader) 
    { 
        $path = dirname($classPath) . "/Resources/config/services.yaml"; 
 
        if (file_exists($path)) { 
            $loader->load($path); 
        } 
    } 
 
    private function parsePackageConfigurations($prefix) : array 
    { 
        $configs = []; 
 
        if (file_exists($prefix) && is_dir($prefix)) { 
            foreach (array_diff(scandir($prefix), ['.', '..']) as $extensionConfig) { 
                $path = "$prefix/$extensionConfig"; 
     
                if (!is_dir($path) && 'yaml' === pathinfo($path, PATHINFO_EXTENSION)) { 
                    $configs[pathinfo($path, PATHINFO_FILENAME)] = Yaml::parseFile($path); 
                } 
            } 
        } 
 
        return $configs; 
    } 
}