<?php

/*
 * This file is part of the FOSRestBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\RestBundle\EventListener;

use FOS\RestBundle\Controller\Annotations\View as ViewAnnotation;
use FOS\RestBundle\FOSRestBundle;
use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandlerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Templating\TemplateReferenceInterface;

/**
 * The ViewResponseListener class handles the View core event as well as the "@extra:Template" annotation.
 *
 * @author Lukas Kahwe Smith <smith@pooteeweet.org>
 *
 * @internal
 */
class ViewResponseListener implements EventSubscriberInterface
{
    private $viewHandler;
    private $forceView;

    /**
     * Constructor.
     *
     * @param ViewHandlerInterface $viewHandler
     * @param bool                 $forceView
     */
    public function __construct(ViewHandlerInterface $viewHandler, $forceView)
    {
        $this->viewHandler = $viewHandler;
        $this->forceView = $forceView;
    }

    /**
     * Renders the parameters and template and initializes a new response object with the
     * rendered content.
     *
     * @param GetResponseForControllerResultEvent $event
     */
    public function onKernelView(GetResponseForControllerResultEvent $event)
    {
        $request = $event->getRequest();

        if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) {
            return false;
        }

        $configuration = $request->attributes->get('_template');

        $view = $event->getControllerResult();
        if (!$view instanceof View) {
            if (!$configuration instanceof ViewAnnotation && !$this->forceView) {
                return;
            }

            $view = new View($view);
        }

        if ($configuration instanceof ViewAnnotation) {
            if ($configuration->getTemplateVar()) {
                $view->setTemplateVar($configuration->getTemplateVar());
            }
            if (null !== $configuration->getStatusCode() && (null === $view->getStatusCode() || Response::HTTP_OK === $view->getStatusCode())) {
                $view->setStatusCode($configuration->getStatusCode());
            }

            $context = $view->getContext();
            if ($configuration->getSerializerGroups()) {
                if (null === $context->getGroups()) {
                    $context->setGroups($configuration->getSerializerGroups());
                } else {
                    $context->setGroups(array_merge($context->getGroups(), $configuration->getSerializerGroups()));
                }
            }
            if ($configuration->getSerializerEnableMaxDepthChecks()) {
                $context->setMaxDepth(0, false);
            }
            if (true === $configuration->getSerializerEnableMaxDepthChecks()) {
                $context->enableMaxDepth();
            } elseif (false === $configuration->getSerializerEnableMaxDepthChecks()) {
                $context->disableMaxDepth();
            }

            list($controller, $action) = $configuration->getOwner();
            $vars = $this->getDefaultVars($configuration, $controller, $action);
        } else {
            $vars = null;
        }

        if (null === $view->getFormat()) {
            $view->setFormat($request->getRequestFormat());
        }

        if ($this->viewHandler->isFormatTemplating($view->getFormat())
            && !$view->getRoute()
            && !$view->getLocation()
        ) {
            if (null !== $vars && 0 !== count($vars)) {
                $parameters = (array) $this->viewHandler->prepareTemplateParameters($view);
                foreach ($vars as $var) {
                    if (!array_key_exists($var, $parameters)) {
                        $parameters[$var] = $request->attributes->get($var);
                    }
                }
                $view->setData($parameters);
            }

            if ($configuration && ($template = $configuration->getTemplate()) && !$view->getTemplate()) {
                if ($template instanceof TemplateReferenceInterface) {
                    $template->set('format', null);
                }

                $view->setTemplate($template);
            }
        }

        $response = $this->viewHandler->handle($view, $request);

        $event->setResponse($response);
    }

    public static function getSubscribedEvents()
    {
        // Must be executed before SensioFrameworkExtraBundle's listener
        return array(
            KernelEvents::VIEW => array('onKernelView', 30),
        );
    }

    /**
     * @param Template $template
     * @param object   $controller
     * @param string   $action
     *
     * @return array
     *
     * @see \Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener::resolveDefaultParameters()
     */
    private function getDefaultVars(Template $template = null, $controller, $action)
    {
        if (0 !== count($arguments = $template->getVars())) {
            return $arguments;
        }

        if (!$template instanceof ViewAnnotation || $template->isPopulateDefaultVars()) {
            $r = new \ReflectionObject($controller);

            $arguments = array();
            foreach ($r->getMethod($action)->getParameters() as $param) {
                $arguments[] = $param->getName();
            }

            return $arguments;
        }
    }
}
