<?php
declare(strict_types=1);

namespace PhpList\Core\Core;

use Symfony\Bundle\WebServerBundle\WebServerBundle;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Yaml\Yaml;

/**
 * This class takes care of processing HTTP requests using Symfony.
 *
 * @author Oliver Klee <oliver@phplist.com>
 */
class ApplicationKernel extends Kernel
{
    /**
     * @var ApplicationStructure
     */
    private $applicationStructure = null;

    /**
     * @return BundleInterface[]
     */
    public function registerBundles(): array
    {
        $bundles = $this->bundlesFromConfiguration();
        if ($this->shouldHaveDevelopmentBundles()) {
            $bundles[] = new WebServerBundle();
        }

        return $bundles;
    }

    /**
     * Returns the directory of the project/application.
     *
     * @return string absolute path without the trailing slash
     */
    public function getProjectDir()
    {
        return $this->getAndCreateApplicationStructure()->getCorePackageRoot();
    }

    /**
     * @return string
     */
    public function getRootDir()
    {
        return $this->getProjectDir();
    }

    /**
     * Returns the absolute path to the application root.
     *
     * When core is installed as a dependency (library) of an application, this method will return
     * the application's package path.
     *
     * When phpList4-core is installed stand-alone (i.e., as an application - usually only for testing),
     * this method will be the phpList4-core package path.
     *
     * @return string the absolute path without the trailing slash
     */
    private function getApplicationDir(): string
    {
        return $this->getAndCreateApplicationStructure()->getApplicationRoot();
    }

    /**
     * @return string
     */
    public function getCacheDir()
    {
        return $this->getApplicationDir() . '/var/cache/' . $this->getEnvironment();
    }

    /**
     * @return string
     */
    public function getLogDir()
    {
        return $this->getApplicationDir() . '/var/logs';
    }

    /**
     * @return ApplicationStructure
     */
    private function getAndCreateApplicationStructure(): ApplicationStructure
    {
        if ($this->applicationStructure === null) {
            $this->applicationStructure = new ApplicationStructure();
        }

        return $this->applicationStructure;
    }

    /**
     * Adds the "kernel.application_dir" parameter to the container while it is being built.
     *
     * @param ContainerBuilder $container
     *
     * @return void
     */
    protected function build(ContainerBuilder $container)
    {
        $container->setParameter('kernel.application_dir', $this->getApplicationDir());
    }

    /**
     * Loads the container configuration.
     *
     * @param LoaderInterface $loader
     *
     * @return void
     *
     * @throws \Exception
     */
    public function registerContainerConfiguration(LoaderInterface $loader)
    {
        $loader->load($this->getApplicationDir() . '/config/parameters.php');
        $loader->load($this->getRootDir() . '/config/config_' . $this->getEnvironment() . '.yml');
        $loader->load($this->getApplicationDir() . '/config/config_modules.yml');
    }

    /**
     * @return bool
     */
    private function shouldHaveDevelopmentBundles(): bool
    {
        return $this->environment !== Environment::PRODUCTION;
    }

    /**
     * Reads the bundles from the bundle configuration file and instantiates them.
     *
     * @return Bundle[]
     */
    private function bundlesFromConfiguration(): array
    {
        $bundles = [];

        /** @var string[] $packageBundles */
        foreach ($this->readBundleConfiguration() as $packageBundles) {
            foreach ($packageBundles as $bundleClassName) {
                if (class_exists($bundleClassName)) {
                    $bundles[] = new $bundleClassName();
                }
            }
        }

        return $bundles;
    }

    /**
     * Reads the bundle configuration file and returns two-dimensional array:
     *
     * 'package name' => [0 => 'bundle class name']
     *
     * @return string[][]
     *
     * @throws \RuntimeException if the configuration file cannot be read
     */
    private function readBundleConfiguration(): array
    {
        $configurationFilePath = $this->getApplicationDir() . '/config/bundles.yml';
        if (!is_readable($configurationFilePath)) {
            throw new \RuntimeException('The file "' . $configurationFilePath . '" could not be read.', 1504272377);
        }

        return Yaml::parse(file_get_contents($configurationFilePath));
    }
}
