<?php

/**
 * Lifestyle Webconsulting GmbH
 *
 * LICENSE: This Software is the property of Lifestyle Webconsulting GmbH (Aschaffenburg, Germany)
 * and is private 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
 * @link       http://www.life-style.de
 */

namespace LifeStyle\Tools\CachingBundle\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
use LifeStyle\Tools\CachingBundle\Api\Manager as ApiManager;

/**
 * Class CachingSubscriber
 * @package LifeStyle\Tools\CachingBundle\EventSubscriber
 */
class CachingSubscriber implements EventSubscriberInterface
{
    /**
     * @var ApiManager
     */
    private $apiM;

    /**
     * @var ControllerResolverInterface
     */
    private $controllerResolver;

    /**
     * @var boolean
     */
    private $buildCache;

    /**
     * @var string
     */
    private $cacheKey;

    /**
     * @var integer
     */
    private $cacheTTL;

    /**
     * @var string
     */
    private $cacheState;

    /**
     * @var float
     */
    private $startTime;

    /**
     * CachingSubscriber constructor.
     * @param ApiManager $apiM
     */
    public function __construct(ApiManager $apiM, ControllerResolverInterface $controllerResolver)
    {
        $this->apiM = $apiM;
        $this->controllerResolver = $controllerResolver;
        $this->buildCache = false;
        $this->isCached = false;
        $this->startTime = microtime(true);
    }


    /**
     * @return array
     */
    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::CONTROLLER_ARGUMENTS => ['readCache'],
            KernelEvents::TERMINATE => ['writeCache'],
            KernelEvents::RESPONSE => ['addCacheHeader']
        ];
    }


    /**
     * Event to check caching configuration (by LsCache annotation), fetch stored content and redirect to cache
     * controller to return cached content instead of calling the controller actually requested.
     *
     * @param FilterControllerEvent $event
     */
    public function readCache(FilterControllerEvent $event)
    {
        if (!$this->apiM->getConfig()->isCacheEnabled()) {
            return;
        }

        $this->cacheState = 'not-cached';

        // query cache
        $cacheResult = $this->apiM->data()->reader()->cacheRead($event->getRequest());
        // process cache result
        if (null !== $cacheResult) {
            $this->cacheState = 'miss';
            if ((null !== $cacheResult->getRequest())
                && (null !== $cacheResult->getRequest()->attributes->get('_cachedContent'))
            ) {
                // we got a valid cache result that contains a copy of cached content for this request,
                // redirect the request to the cache response controller
                $cacheResult->getRequest()->attributes->set('_controller',
                    'LifeStyleToolsCachingBundle:Cache:cachedResponse');
                $event->setController($this->controllerResolver->getController($cacheResult->getRequest()));
                $this->cacheState = 'hit';
            }
            $this->buildCache = $cacheResult->isBuildCache();
            $this->cacheKey = $cacheResult->getCacheKey();
            $this->cacheTTL = $cacheResult->getCacheTTL();
        }
    }

    /**
     * Event to store the response content to the cache, if configured
     *
     * @param PostResponseEvent $event
     */
    public function writeCache(PostResponseEvent $event)
    {
        if (!$this->apiM->getConfig()->isCacheEnabled()) {
            return;
        }

        if ($event->getResponse()->getStatusCode() >= 400) {
            // do not write cache if this is any kind of error response
            return;
        }

        if ($this->buildCache) {
            $this->apiM->data()->writer()->writeCache($event->getResponse(), $this->cacheKey, $this->cacheTTL);
        }
    }

    /**
     * Event to (always) add the X-Ls-Cache header to the HTTP response.
     * Value / state of the header is set during cacheRead().
     *
     * @param FilterResponseEvent $event
     */
    public function addCacheHeader(FilterResponseEvent $event)
    {
        if (!$this->apiM->getConfig()->isCacheEnabled()) {
            return;
        }

        $response = $event->getResponse();
        $response->headers->set('X-Ls-Cache', $this->cacheState);
        $event->setResponse($response);
    }
}
