<?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\ViisonPickwareCommon\Classes\ApiRequestCompatibility;

use \Enlight_Controller_Dispatcher_Default as Dispatcher;
use \Enlight_Controller_Front as Front;
use \Enlight_Controller_Request_Request as Request;
use \Enlight_Controller_Response_Response as Response;
use \Enlight_View_Default as View;
use Shopware\Components\DependencyInjection\Container;
use Shopware\Components\DependencyInjection\ContainerAwareInterface;
use Shopware\Plugins\ViisonCommon\Classes\Util\Util as ViisonCommonUtil;

class ControllerWrapper implements ContainerAwareInterface
{
    /**
     * @var \Shopware\Plugins\ViisonPickwareCommon\Classes\ApiRequestCompatibility\Layer $entryLayer
     */
    private $entryLayer;

    /**
     * @var \Enlight_Controller_Request_Request $request
     */
    private $request;

    /**
     * @var \Enlight_Controller_Response_Response $response
     */
    private $response;

    /**
     * @var \Enlight_View_Default $view
     */
    private $view;

    /**
     * @var \Enlight_Controller_Front $front
     */
    private $front;

    /**
     * @var \Shopware\Components\DependencyInjection\Container $container
     */
    private $container;

    /**
     * @var Shopware_Proxies_* $targetController
     */
    private $targetController;

    /**
     * @param \Shopware\Plugins\ViisonPickwareCommon\Classes\ApiRequestCompatibility\Layer $entryLayer
     * @param \Enlight_Controller_Request_Request $request
     * @param \Enlight_Controller_Response_Response $response
     * @param \Enlight_View_Default $view
     * @param \Enlight_Controller_Front $front
     */
    public function __construct(Layer $entryLayer, Request $request, Response $response, View $view, Front $front)
    {
        $this->entryLayer = $entryLayer;
        $this->request = $request;
        $this->response = $response;
        $this->view = $view;
        $this->front = $front;

        // Configure all layers with the parameters
        $this->entryLayer->configure($this->request, $this->response, $this->view, $this->front);
    }

    /**
     * @return Layer
     */
    public function getEntryLayer()
    {
        return $this->entryLayer;
    }

    /**
     * @param \Shopware\Components\DependencyInjection\Container $container
     */
    public function setContainer(Container $container = null)
    {
        $this->container = $container;
        $this->entryLayer->setContainer($container);
    }

    /**
     * Use this method to get a service from the DI container.
     *
     * @param string $name
     * @return mixed
     */
    public function get($name)
    {
        return $this->container->get($name);
    }

    /**
     * Returns an instance of the proxy of the requested controller. That is,
     * if the controller has not been created before, a new instance is created
     * using Shopware's controller dispatcher.
     *
     * @return Shopware_Proxies_*
     */
    public function getRequestedController()
    {
        if (!$this->targetController) {
            // Use the default dispatcher to load a proxy of the controller
            // Since SW 5.6 the dispatcher has its own constructor which now requires the container to be passed.
            if (ViisonCommonUtil::assertMinimumShopwareVersion('5.6.0')) {
                $dispatcher = new Dispatcher([], $this->container);
            } else {
                $dispatcher = new Dispatcher();
            }
            $class = $dispatcher->getControllerClass($this->request);
            if (mb_strpos($class, 'Pickware') !== false) {
                // Load custom classes first
                $path = $dispatcher->getControllerPath($this->request);
                $this->get('loader')->loadClass($class, $path);
            }

            $moduleName = $this->request->getModuleName();
            $controllerName = $this->request->getControllerName();
            $controllerServiceClass = mb_strtolower(sprintf('shopware_controllers_%s_%s', $moduleName, $controllerName));

            // Since SW 5.6 the controllers exist as services in the container. These are already proxied and thus
            // we can just fetch them.
            if ($this->container->has($controllerServiceClass)) {
                $this->targetController = $this->container->get($controllerServiceClass);
                $this->targetController->setRequest($this->request);
                $this->targetController->setResponse($this->response);
            } else {
                $proxy = $this->get('hooks')->getProxy($class);
                $this->targetController = new $proxy($this->request, $this->response);

                // Since SW 5.6 the `Enlight_Controller_Action` has its constructor logic moved into a `initController`
                // method, which we now need to ensure to call.
                if (method_exists($this->targetController, 'initController')) {
                    $this->targetController->initController($this->request, $this->response);
                }
            }
            $this->targetController->setView($this->view);
            $this->targetController->setFront($this->front);
            if ($this->targetController instanceof ContainerAwareInterface) {
                $this->targetController->setContainer($this->container);
            }
        }

        return $this->targetController;
    }

    /**
     * Performs both the 'pre' and 'post' actions on the entry layer, as well as the original
     * action on the original controller. That is, the 'pre' action method is called on the
     * entry layer, which causes a chained execution of the 'pre' action of all successive layers.
     * Next the requested action is called on the requested controller, before the 'post' action
     * method is called on the last/bottom layer in the compatibility chain. That again executes
     * the whole layer chain until reaching the original entry/top layer.
     *
     * @param string $actionName
     */
    public function dispatch($actionName)
    {
        // Perform the pre actions starting on the top/entry layer
        $topLayer = $this->entryLayer;
        $methodName = $actionName . 'Action';
        $topLayer->performPreAction($methodName);

        // Perform the original action on the original controller
        $this->getRequestedController()->$methodName();

        // Perform the post actions starting at the bottom/exit layer
        $bottomLayer = $topLayer;
        while ($bottomLayer->getSuccessor() !== null) {
            $bottomLayer = $bottomLayer->getSuccessor();
        }
        $bottomLayer->performPostAction($methodName);
    }
}
