<?php

/**
 * LICENSE: This Software is the property of Lifestyle Webconsulting GmbH (Aschaffenburg, Germany)
 * and is protected by copyright law - it is NOT Freeware.
 *
 * Any unauthorized use of this software without a valid license
 * is a violation of the license agreement and will be prosecuted by
 * civil and criminal law.
 *
 * @copyright  2018 Lifestyle Webconsulting GmbH
 * @version    $Id:$
 * @link       http://www.life-style.de
 */

namespace LifeStyle\Tools\WebserviceBundle\EventListener;

use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\HeaderBag;
use LifeStyle\Tools\WebserviceBundle\Error\Storage as ErrorStorage;
use LifeStyle\Tools\WebserviceBundle\Error\Type\External as ExternalError;
use LifeStyle\Tools\WebserviceBundle\Exception\InvalidHeaderException;
use LifeStyle\Tools\WebserviceBundle\Exception\InvalidContentException;
use LifeStyle\Tools\WebserviceBundle\Response\Builder as ResponseBuilder;

/**
 * Class WsControllerListener
 * @package LifeStyle\Tools\WebserviceBundle\EventListener
 */
class WsControllerListener implements EventSubscriberInterface
{
    /**
     * @var ErrorStorage
     */
    protected $errors;

    /**
     * @var ResponseBuilder
     */
    protected $responseBuilder;

    /**
     * Constructor
     *
     * @param ErrorStorage $errorStorage
     * @param ResponseBuilder $responseBuilder
     */
    public function __construct(ErrorStorage $errorStorage, ResponseBuilder $responseBuilder)
    {
        $this->errors = $errorStorage;
        $this->responseBuilder = $responseBuilder;
    }

    /**
     * Received controller event
     *
     * Verify content and load xml
     *
     * @param FilterControllerEvent $event
     * @throws InvalidContentException
     */
    public function onKernelController(FilterControllerEvent $event)
    {
        $request = $event->getRequest();
        if (!($configuration = $request->attributes->get('_wsRoute'))) {
            return;
        }

        // Set controller and action for xml response
        $controller = $request->headers->get('sso-api-controller');
        $action = $request->headers->get('sso-api-action');
        $this->responseBuilder->setController($controller);
        $this->responseBuilder->setAction($action);

        if ('xml' !== $request->getContentType()) {
            throw new InvalidContentException('Invalid Content-Type. Expected xml.');
        }

        if (false === ($xml = $this->loadXml($request->getContent()))) {
            throw new InvalidContentException('Invalid Content. Could not load xml.');
        }

        // Make xml accessible by request attribute
        $request->attributes->set('XmlRequest', $xml);

        $this->verifyControllerAction($controller, $action, $xml);
    }

    /**
     * Validate and load xml
     *
     * @param string $xml
     * @return \SimpleXMLElement|false
     */
    private function loadXml($xml)
    {
        libxml_clear_errors();
        libxml_disable_entity_loader(true);
        libxml_use_internal_errors(true);

        $simpleXML = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_COMPACT);

        if (!$simpleXML) {
            $errors = libxml_get_errors();
            foreach ($errors as $error) {
                $this->errors->addError(new ExternalError('xh10', 'XmlInvalid', $error->message));
            }
            libxml_clear_errors();
            libxml_disable_entity_loader(false);
        } else {
            libxml_clear_errors();
            libxml_disable_entity_loader(false);
        }

        if ($this->errors->hasErrors()) {
            throw new InvalidContentException('Invalid Content. Expected well formed xml.');
        }

        return $simpleXML;
    }

    /**
     * Verify controller and action against xml
     *
     * @param \SimpleXMLElement $xml
     * @return bool
     */
    private function verifyControllerAction($controller, $action, \SimpleXMLElement $xml)
    {
        if (!isset($xml->$controller)) {
            $this->errors->addError(new ExternalError('xh20', 'ControllerInvalid',
                'Controller in header does not match controller in xml'));
        } elseif (!isset($xml->$controller->$action)) {
            $this->errors->addError(new ExternalError('xh22', 'ActionInvalid',
                'Action in header does not match action in xml'));
        }

        // Controller or action missing
        if ($this->errors->hasErrors()) {
            throw new InvalidHeaderException();
        }
    }

    /**
     * Returns list of subscribed events
     *
     * @return array
     */
    public static function getSubscribedEvents()
    {
        return array(KernelEvents::CONTROLLER => 'onKernelController');
    }
}
