<?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\Api\Data\Reader;

use Symfony\Component\HttpFoundation\Request;
use LifeStyle\Tools\CachingBundle\Annotations\LsCache;
use LifeStyle\Tools\CachingBundle\Api\Manager as ApiManager;

/**
 * Class ReaderBase
 * @package LifeStyle\Tools\CachingBundle\Api\Data\Reader
 */
abstract class ReaderBase implements ReaderInterface
{
    /**
     * @var ApiManager
     */
    protected $apiM;

    /**
     * CachingSubscriber constructor.
     * @param ApiManager $apiM
     */
    public function __construct(ApiManager $apiM)
    {
        $this->apiM = $apiM;
    }

    /**
     * Query the cache and return an object holding additional information about the cache, state and stored content,
     * if there is any
     *
     * @param Request $request
     * @return ReaderResponse|null
     */
    public function cacheRead(Request $request)
    {
        // the single "=" in this conditional is intentional and correct
        if (!$configuration = $request->attributes->get('_lsCache')) {
            return null;
        }

        // this one mainly serves to silence "$readerResponse might not be defined" warnings
        $readerResponse = null;

        if ($configuration instanceof LsCache) {
            $cacheKey = $this->generateCacheKey($request, $configuration);
            $readerResponse = $this->initCacheResponse($request, $cacheKey);
            $readerResponse->setCacheTTL($configuration->getCacheTTL());

            // if no request object has been set in the reader response yet, we're supposed to fetch a cached copy
            // and manipulate the request accordingly
            if (null === $readerResponse->getRequest()) {
                // this is where the actual cache engine / backend is accessed
                $readerResponse = $this->fetchCachedContent($readerResponse, $request, $cacheKey);
            }
        }

        return $readerResponse;
    }

    /**
     * Query the cache and return an object holding additional information about the cache, state and stored content,
     * if there is any
     *
     * @param string $cacheKey
     * @return ReaderResponse|null
     */
    public function cacheReadByKey($cacheKey)
    {
        if (!$this->apiM->getConfig()->isCacheEnabled()) {
            return null;
        }
        return $this->fetchCachedContentByKey($cacheKey);
    }

    /**
     * Get the cache key hash for storing the content data
     *
     * @param Request $request
     * @param LsCache $configuration
     * @return string
     */
    protected function generateCacheKey(Request $request, LsCache $configuration)
    {
        return $this->apiM->data()->common()->functions()->generateCacheKey($request, $configuration);
    }

    /**
     * This function gets an instance of and initialises the cache read response object.
     * If the request object has not been set by this function, the request still needs to be processed by the
     * cache read mechanism, otherwise no additional action is needed and it can already be returned to the caller.
     *
     * @param Request $request
     * @param string $cacheKey
     * @return ReaderResponse
     */
    protected function initCacheResponse(Request $request, $cacheKey)
    {
        // init cache response object
        $readerResponse = $this->apiM->data()->readerResponse();
        $readerResponse
            ->setBuildCache(false)
            ->setCacheKey($cacheKey)
            ->setRequest(null);

        if ($request->query->has('nocache')) {
            if ($request->query->get('nocache') == '1') {
                // "non cached" response has been requested, return the unaltered request and don't trigger
                // any storing of the generated response into the cache
                $readerResponse->setRequest($request);
            }
        } elseif ($request->query->has('rebuildcache')) {
            if ($request->query->get('rebuildcache') == '1') {
                // force of cache rebuild has been requested, do not return any cached version and trigger storing
                // of generated response into cache
                $readerResponse
                    ->setRequest($request)
                    ->setBuildCache(true);
            }
        }

        return $readerResponse;
    }

    /**
     * Get the cached content (if there is any) from the used caching engine / backend, put it into the
     * original request object and set up the cache read response object
     *
     * @param ReaderResponse $readerResponse
     * @param Request $request
     * @param string $cacheKey
     * @return Request
     */
    abstract protected function fetchCachedContent(ReaderResponse $readerResponse, Request $request, $cacheKey);

    /**
     * Get the cached content (if there is any) from the used caching engine / backend
     * @param string $cacheKey
     * @return mixed|null
     */
    abstract protected function fetchCachedContentByKey($cacheKey);

    /**
     * Helper method to replace the script-time in the cached content with the current one
     *
     * @param string $content
     * @param string $format
     * @return string
     */
    protected function replaceScriptTime($content, $format)
    {
        // replace "script time" in cached content with the actual one
        if ($format == 'json') {
            $content = preg_replace('/\"ScriptSeconds\"\:\s*[\d]+\.[\d]+/',
                '"ScriptSeconds": ' . $this->apiM->scriptTimeSeconds(), $content);
        } elseif ($format == 'xml') {
            $content = preg_replace('/\<ScriptSeconds\>[\d]+\.[\d]+/',
                '<scriptTimeSec>' . $this->apiM->scriptTimeSeconds(), $content);
        }

        return $content;
    }
}
