<?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\SubApplicationCodeGenerator;

use Shopware\Components\DependencyInjection\Container;
use Shopware\Components\DependencyInjection\ContainerAwareInterface;
use Shopware\Plugins\ViisonCommon\Components\ControllerFactoryService;

abstract class AbstractSubApplicationCodeGenerator implements SubApplicationCodeGenerator
{
    /**
     * @var Container
     */
    protected $dependencyContainer;

    /**
     * @var \Enlight_Controller_Front
     */
    protected $frontController;

    /**
     * @var \Enlight_Loader
     */
    protected $classLoader;

    /**
     * @var \Enlight_Event_EventManager
     */
    protected $eventEventManager;

    /**
     * @var string
     */
    protected $path;

    /**
     * @var string
     */
    protected $controllerClass;

    /**
     * @var string
     */
    protected $name;

    /**
     * @var ControllerFactoryService
     */
    private $controllerFactoryService;

    /**
     * @param Container $dependencyContainer
     * @param \Enlight_Controller_Front $frontController
     * @param \Enlight_Loader $classLoader
     * @param \Enlight_Event_EventManager $eventEventManager
     * @param ControllerFactoryService $controllerFactoryService
     * @param string $name
     * @param string $path
     * @param string $controllerClass
     */
    public function __construct(
        Container $dependencyContainer,
        \Enlight_Controller_Front $frontController,
        \Enlight_Loader $classLoader,
        \Enlight_Event_EventManager $eventEventManager,
        ControllerFactoryService $controllerFactoryService,
        $name,
        $path,
        $controllerClass
    ) {
        $this->dependencyContainer = $dependencyContainer;
        $this->path = $path;
        $this->controllerClass = $controllerClass;
        $this->name = $name;
        $this->frontController = $frontController;
        $this->classLoader = $classLoader;
        $this->eventEventManager = $eventEventManager;
        $this->controllerFactoryService = $controllerFactoryService;
    }

    /**
     * @return array
     */
    protected function getViewParameters()
    {
        $controller = $this->loadBackendController();
        if (!$controller || !method_exists($controller, 'getViewParams')) {
            return [];
        }

        return $controller->getViewParams();
    }

    /**
     * @return \Enlight_Class|null
     */
    protected function loadBackendController()
    {
        $controllerClass = $this->controllerClass;
        // Try to load the controller class if necessary
        if (!class_exists($controllerClass)) {
            try {
                // Create a new request for the controller path event, to set the correct module (always
                // 'backend') and controller name, that is subAppName
                $request = new \Enlight_Controller_Request_RequestHttp();
                $request->setModuleName('backend');
                $request->setControllerName($this->name);

                // Try to find the controller path
                $event = $this->eventEventManager->notifyUntil(
                    'Enlight_Controller_Dispatcher_ControllerPath_Backend_' . $this->name,
                    [
                        'subject' => $this,
                        'request' => $request,
                    ]
                );
                if (!$event) {
                    return null;
                }

                // Try to load the controller class
                $this->classLoader->loadClass($controllerClass, $event->getReturn());
            } catch (\Exception $e) {
                // Controller can not be loaded
                return null;
            }
        }

        return $this->controllerFactoryService->getController(
            $controllerClass,
            $this->frontController->Request(),
            $this->frontController->Response()
        );
    }

    /**
     * @param $value
     * @return string
     */
    protected static function convertPascalCaseToSnakeCase($value)
    {
        $matches = [];
        preg_match_all('/[A-Z][a-z]+|[A-Z]+(?![a-z])/', $value, $matches);

        return mb_strtolower(implode('_', $matches[0]));
    }

    /**
     * Returns all .js-files belonging to the given sub application.
     *
     * @return string[]
     */
    protected function getSubAppFiles()
    {
        $directory = $this->getViewPath();

        if (!is_dir($directory)) {
            throw new \RuntimeException(
                sprintf(
                    'ViisonCommonJSLoader: Sub application path \'%s\' for sub application \'%s\' does not exist',
                    $directory,
                    $this->name
                )
            );
        }

        $files = $this->getFilesRecursive($directory);

        // Remove all non-JavaScript files
        $files = array_filter($files, function ($file) {
            return preg_match('/.*\\.js$/', $file);
        });

        return $files;
    }

    /**
     * Returns all file names in the given directory and its subdirectories, files starting with '.' are excluded.
     *
     * @param string $dir
     * @return string[]
     */
    private function getFilesRecursive($dir)
    {
        $result = [];
        $files = scandir($dir);
        foreach ($files as $key => $value) {
            // Exclude all files and directories, whose names start with '.' (e.g. '.', '..', '.DS_Store' etc.)
            if (mb_substr($value, 0, 1) === '.') {
                continue;
            }

            $path = realpath($dir . '/' . $value);
            if (!is_dir($path)) {
                $result[] = $path;
            } else {
                $result = array_merge($result, $this->getFilesRecursive($path));
            }
        }

        // Move app.js to end. This is necessary so that dependencies of the sub application are defined before the sub
        // application itself. This way we make sure that Shopware does not fire an AJAX call e.g. to load the Main
        // controller of the subapp.
        for ($i = 0; $i < count($result); $i++) {
            if (preg_match('/app.js/', $result[$i])) {
                $result[] = $result[$i];
                unset($result[$i]);
                break;
            }
        }

        return $result;
    }

    /**
     * @return string
     */
    protected function getViewPath()
    {
        return realpath($this->path) . '/Views/backend/' . self::convertPascalCaseToSnakeCase($this->name);
    }
}
