<?php

namespace Comitium5\DesignerBundle\Publisher\Page;

use Comitium5\DesignerBundle\Controller\Widget\AbstractDesignerWidgetController;
use Comitium5\DesignerBundle\Entity\LayoutPageWidget;
use Comitium5\DesignerBundle\Entity\Page;
use Comitium5\DesignerBundle\Manager\Designer\DesignerManager;
use Comitium5\DesignerBundle\Manager\Widget\WidgetManager;
use Comitium5\DesignerBundle\Model\Interfaces\LocaleInterface;
use Comitium5\DesignerBundle\Model\Interfaces\Page\PageInterface;
use Comitium5\DesignerBundle\Model\Interfaces\Widget\WidgetInterface;
use Comitium5\DesignerBundle\Model\PageType;
use Comitium5\DesignerBundle\Model\WidgetType;
use Comitium5\DesignerBundle\Provider\SubsiteProvider;
use Comitium5\DesignerBundle\Publisher\Widget\WidgetBuilder;
use Comitium5\DesignerBundle\Repository\Layout\LayoutPageWidgetRepository;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Templating\EngineInterface;

/**
 * Class PageContentResolver
 *
 * @author Carles Gómez <carles@bab-soft.com>
 * @package Comitium5\DesignerBundle\Publisher\Page
 */
class PageContentResolver
{
//    use SubSiteProviderTrait;

    const CACHE_FILENAME = "%s_%s.html.twig";

    const STATIC_HTML_FOLDER = "widgets_html";

    const PAGE_TWIG_PATH = "AdminBundle:Designer/Templates/tmp:%s.html.twig";

    const WIDGET_TAG = "<cswidget id=\"%s\"></cswidget>";

    const FIELD_TYPE_REGEX = '/<csfield type=".+" name="%s"((?!<\/csfield>).)*<\/csfield>/s';

    const CSS_REPLACE = "{# CS_STYLES #}";

    const JS_REPLACE = "{# CS_JS #}";

    const CSS_ASSETIC = "{%% stylesheets %s filter='cssrewrite,uglifycss' %%}<link rel=\"stylesheet\" href=\"{{ asset(asset_url) }}?v=%s\" />{%% endstylesheets %%}";

    const CSS_ASSETS = "<link rel=\"stylesheet\" href=\"{{ asset(%s) }}\"/>";

    const JS_ASSETIC = "{%% javascripts %s filter='uglifyjs2' %%}<script type=\"text/javascript\" src=\"{{ asset(asset_url) }}?v=%s\"></script>{%% endjavascripts %%}";

    const JS_ASSETS = "<script type=\"text/javascript\" src=\"{{ asset(%s) }}\"></script>";

    const DATE_FIELD = '_cs_currentDate';

    /**
     * @var EngineInterface
     */
    private $templating;

    /**
     * @var WidgetBuilder
     */
    private $widgetBuilder;

    /**
     * @var array
     */
    private $javascripts;

    /**
     * @var array
     */
    private $css;

    /**
     * @var LocaleInterface
     */
    private $locale;

    /**
     * @var array
     */
    private $contents;

    /**
     * @var DesignerManager
     */
    private $designerManager;

    /**
     * @var WidgetManager
     */
    private $widgetManager;


    /**
     * @var LayoutPageWidgetRepository
     */
    private $layoutPageWidgetRepository;

    /**
     * @var array
     */
    private $widgetCachedKeys;

    /**
     * @var bool
     */
    private $isTmpMode;

    /**
     * @var SubsiteProvider
     */
    private $subsiteProvider;

    /**
     * @var Page
     */
    private $page;

    /**
     * PageContentResolver constructor.
     * @param EngineInterface $twigEngine
     * @param WidgetBuilder $widgetBuilder
     * @param DesignerManager $designerManager
     * @param WidgetManager $widgetManager
     * @param LayoutPageWidgetRepository $layoutPageWidgetRepository
     */
    public function __construct(
        EngineInterface $twigEngine,
        WidgetBuilder $widgetBuilder,
        DesignerManager $designerManager,
        WidgetManager $widgetManager,
        LayoutPageWidgetRepository $layoutPageWidgetRepository,
        SubsiteProvider $subsiteProvider
    ) {
        $this->templating = $twigEngine;
        $this->widgetBuilder = $widgetBuilder;
        $this->designerManager = $designerManager;
        $this->widgetManager = $widgetManager;
        $this->layoutPageWidgetRepository = $layoutPageWidgetRepository;
        $this->subsiteProvider = $subsiteProvider;

        $this->javascripts = [];
        $this->css = [];
        $this->widgetCachedKeys = [];
        $this->isTmpMode = false;
        $this->contents = [
            'content' => '',
            'statics' => [],
        ];
    }

    /**
     * @param PageInterface $page
     *
     * @return array|mixed
     * @throws \Exception
     */
    public function resolve(PageInterface $page)
    {
        if ($this->locale === null) {
            throw new \Exception("Locale must be set");
        }

        $this->page    = $page;
//        $page->setDefaultLocale($this->locale);

        try {
            if ($this->isTmpMode === true) {
                $this->designerManager->setIsTmpMode(true);
            }

            $content = $this
                ->designerManager
                ->generateHtmlPage($page);
        } catch (\Throwable $e) {
            throw new \Exception($e->getMessage());
        }

        $content = $this->replaceWidgetContent($content);

//        if ($page->allowedToInsertResourcesOnTwigTemplates()) {
//            $content = $this->insertResources($content);
//        }

        $this->contents["content"] = $content;

        return $this->contents;
    }

    /**
     * @param PageInterface $page
     *
     * @return string
     * @throws \Exception
     */
    public function resolveCacheFileName(PageInterface $page)
    {
        if ($this->locale === null) {
            throw new \Exception("Locale must be set");
        }

        $fileName = $page->getOriginId();

        if ($page->getType() === PageType::ERROR_404 && $this->isTmpMode !== true ) {
            $fileName = "error_" . Response::HTTP_NOT_FOUND;
        }

        if ($page->getType() === PageType::ERROR_500 && $this->isTmpMode !== true ) {
            $fileName = "error_" . Response::HTTP_INTERNAL_SERVER_ERROR;
        }

        return sprintf(self::CACHE_FILENAME, $fileName, $this->locale->getCode());
    }

    /**
     * @param $content
     *
     * @return mixed
     */
    public static function resolveScapeBrackets($content)
    {
        return str_replace(
            ["[%", "%]", "[[", "]]"],
            ["{%", "%}", "{{", "}}"],
            $content
        );
    }

    /**
     * @param $content
     *
     * @return mixed
     * @throws
     */
    public function replaceWidgetContent($content)
    {
        foreach ($this->resolveLayoutPageWidgets($content) as $layoutPageWidget) {
            $widgetRender = trim($this->buildWidgetRender($layoutPageWidget));

            if ($layoutPageWidget->getStartAt()) {
                $widgetRender = "{% if date('"
                    .$layoutPageWidget->getStartAt()->format('Y-m-d H:i:s')
                    ."') <= ".self::DATE_FIELD." %}\n"
                    .$widgetRender
                    ."\n{% endif %}";
            }
            if ($layoutPageWidget->getEndAt()) {
                $widgetRender = "{% if date('"
                    .$layoutPageWidget->getEndAt()->format('Y-m-d H:i:s')
                    ."') > ".self::DATE_FIELD." %}\n"
                    .$widgetRender
                    ."\n{% endif %}";
            }

            $content = str_replace(
                sprintf(self::WIDGET_TAG, $layoutPageWidget->getId()),
                $widgetRender,
                $content
            );
        }

        return $content;
    }

    /**
     * @param LayoutPageWidget $layoutPageWidget
     *
     * @return string|void
     * @throws \Exception
     */
    public function buildWidgetRender(LayoutPageWidget $layoutPageWidget)
    {
        $widget     = $layoutPageWidget->getWidget();
        $parameters = $layoutPageWidget->getParameters();

        if (!isset($parameters["parameters"])) {
            throw new \Exception("Parameters key must be provided");
        }

        $this->buildResources($layoutPageWidget);

        if ($widget->getType() !== WidgetType::HTML) {
            $controller = $this->widgetBuilder->buildWidgetController($widget);
            $parameters = $this->formatParameters($parameters, $widget);

            // Necessary to build partial cache key. Used to remove cache keys on publish action
            $controller->setParameters($parameters);
            $this->widgetCachedKeys[] = $controller->buildPartialCacheKey();

            $sentence = "{{ render(controller('ComitiumSuite\\\\Bundle\\\\CSBundle\\\\Widgets\\\\%s\\\\Controller\\\\WidgetController::renderAction', {parameters: %s})) }}";
            $sentence = sprintf($sentence, str_replace("\\", "\\\\", $widget->buildFolderName()), $this->encodeParameters($parameters));

            return $sentence;
        }

        return $this->getWidgetHtmlContent($layoutPageWidget);
    }

    /**
     * @return mixed
     */
    public function getLocale()
    {
        return $this->locale;
    }

    /**
     * @param LocaleInterface $locale
     *
     * @return mixed
     */
    public function setLocale(LocaleInterface $locale)
    {
        $this->locale = $locale;

        return $this;
    }

    /**
     * @param bool $tmpMode
     *
     * @return mixed
     */
    public function setIsTmpMode($tmpMode)
    {
        $this->isTmpMode = $tmpMode;
    }

    /**
     * @return bool
     */
    public function isTmpMode()
    {
        return $this->isTmpMode;
    }

    /**
     * @return array
     */
    public function getWidgetCachedKeys()
    {
        return $this->widgetCachedKeys;
    }

    /**
     * @param array $widgetCachedKeys
     *
     * @return mixed
     */
    public function setWidgetCachedKeys(array $widgetCachedKeys)
    {
        $this->widgetCachedKeys = $widgetCachedKeys;

        return $this;
    }

    /**
     * @param LayoutPageWidget $layoutPageWidget
     */
    private function buildResources(LayoutPageWidget $layoutPageWidget)
    {
        $widget     = $layoutPageWidget->getWidget();

        $controller = $this->widgetBuilder->buildWidgetController($widget);

        if (!isset($this->css[$widget->getId()])) {
            $this->css[$widget->getId()] = $controller->getCssResourcesPaths();
        }

        if (!isset($this->javascripts[$widget->getId()])) {
            $path = $controller->getJsResourcesPaths();
            $path = str_replace("/bundles/cs", "@CSBundle/Resources/public", $path);
            $this->javascripts[$widget->getId()] = $path;
        }
    }

    /**
     * @param LayoutPageWidget $layoutPageWidget
     *
     * @return mixed|string|void
     */
    private function getWidgetHtmlContent(LayoutPageWidget $layoutPageWidget)
    {
        $widget     = $layoutPageWidget->getWidget();
        $parameters = $layoutPageWidget->getParameters();
        $controller = $this->widgetBuilder->buildWidgetController($widget);

        if ($controller === null) {
            return;
        }

        $parameters = $this->formatParameters($parameters, $widget);

        $content = $controller->renderAction($parameters);

        if (!$content instanceof Response) {
            return;
        }

        //        $fields  = isset($parameters["_fields"]) ? $parameters["_fields"] : [];

        $content = $content->getContent();

        $content = preg_replace('/<csfield type=".*" name=".+" data-entity-id(=")?(\d+)?(")?>/', '', $content);
        $content = preg_replace('/<\/csfield>/', '', $content);



        //        if (isset($fields[$this->locale->getCode()])) {
        //            $patterns = [];
        //            $replacements = [];
        //
        //            foreach ($fields[$this->locale->getCode()] as $fieldKey => $fieldContent) {
        //                $staticKey =
        //                    DIRECTORY_SEPARATOR.
        //                    self::STATIC_HTML_FOLDER.
        //                    DIRECTORY_SEPARATOR.
        //                    $this->buildStaticFileName($layoutPageWidget->getId(), $fieldKey);
        //
        //                if ($widget->isPublished() !== false) {
        //                    $this->contents["statics"][$staticKey] = $content;
        //                }
        //
        //                $patterns[]     = sprintf(self::FIELD_TYPE_REGEX, $fieldKey);
        //                $replacements[] = $this->buildStaticBlock($layoutPageWidget->getId(), $fieldKey);
        //            }
        //
        //            if (count($patterns) && count($replacements)) {
        //                $content = preg_replace($patterns, $replacements, $content);
        //            }
        //        }

        return $content;
    }

    /**
     * @param array $relationParameters
     * @param WidgetInterface $widget
     *
     * @return array
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    private function formatParameters(array $relationParameters, WidgetInterface $widget)
    {
        $parameters = $relationParameters["parameters"];
        $fields     = $relationParameters["fields"];

        $parameters["_fields"]  = $fields;
        $parameters["_locale"]  = $this->locale->getCode();
        $parameters["_page"] = $this
            ->page
            ->toArray(array('id', 'categories', 'tags', 'lang', 'type', 'redirectUrl', 'public', 'private', 'ttl'));

        if (isset($parameters[AbstractDesignerWidgetController::WIDGET_INDEX])) {
            $parameters[AbstractDesignerWidgetController::WIDGET_INDEX] = [
                "id"   => $widget->getId(),
                "type" => $widget->getType(),
                "name" => $widget->formatName(),
            ];
        }

        if ($this->isTmpMode) {
            $parameters["_preview"] = true;
        }

        return $parameters;
    }

    /**
     * @param $parameters
     * @return mixed|string
     */
    private function encodeParameters($parameters)
    {
        $params = json_encode($parameters, JSON_UNESCAPED_UNICODE);
        //        $params = str_replace('"_cs_page|default(null)"', '_cs_page|default(null)', $params);
        //        $params = str_replace('"_cs_subsite|default(null)"', '_cs_subsite|default(null)', $params);

        return $params;
    }

    /**
     * @param $content
     *
     * @return mixed
     */
    private function insertResources($content)
    {
        $css = [];
        $js  = [];

        foreach ($this->css as $cssPaths) {
            foreach ($cssPaths as $cssPath) {
                if (empty($cssPath)) {
                    continue;
                }

                $css[] = "'".$cssPath."'";
            }
        }

        foreach ($this->javascripts as $jsPaths) {
            foreach ($jsPaths as $jsPath) {
                if (empty($jsPath)) {
                    continue;
                }

                $js[] = "'".$jsPath."'";
            }
        }

        if (count($js)) {
            if ($this->isTmpMode === false) {
                $jsAssetic = sprintf(self::JS_ASSETIC, implode(" ", $js), uniqid());
                $content   = str_replace(self::JS_REPLACE, $jsAssetic, $content);
            } else {
                $jsAssets = '';

                foreach ($js as $resource) {
                    $jsAssets .= self::resolveAssetsJSTags($resource);
                }

                $content = str_replace(self::JS_REPLACE, $jsAssets, $content);
            }
        }

        if (count($css)) {
            if ($this->isTmpMode === false) {
                $cssAssetic = sprintf(self::CSS_ASSETIC, implode(" ", $css), uniqid());
                $content    = str_replace(self::CSS_REPLACE, $cssAssetic, $content);
            } else {
                $cssAssets = '';

                foreach ($css as $resource) {
                    $cssAssets .= self::resolveAssetsCssTags($resource);
                }

                $content = str_replace(self::CSS_REPLACE, $cssAssets, $content);
            }
        }

        return $content;
    }

    /**
     * @param $resource
     *
     * @return string
     */
    public static function resolveAssetsCssTags($resource)
    {
        $resourceFixed = str_replace("'bundles", "'/bundles", $resource);

        return sprintf(self::CSS_ASSETS, $resourceFixed)."\n";
    }

    /**
     * @param $resource
     *
     * @return string
     */
    public static function resolveAssetsJSTags($resource)
    {
        $isExternal = preg_match("/^http(.+)/", $resource);
        $resourceFixed = $isExternal === false ? str_replace("'bundles", "'/bundles", $resource) : $resource;

        return sprintf(self::JS_ASSETS, $resourceFixed)."\n";
    }

    /**
     * @param $id
     * @param $key
     *
     * @return string
     */
    private function buildStaticFileName($id, $key)
    {
        return "{$id}_{$this->locale->getCode()}_{$key}.html.twig";
    }

    /**
     * @param $id
     * @param $key
     *
     * @return string
     */
    private function buildStaticBlock($id, $key)
    {
        return "{% include ':cache".DIRECTORY_SEPARATOR.self::STATIC_HTML_FOLDER.":{$this->buildStaticFileName($id, $key)}' %}";
    }

    /**
     * @param $content
     *
     * @return array
     */
    public function resolveLayoutPageWidgets($content)
    {
        $entities = [];

        preg_match_all('/<cswidget id="(\d+)"><\/cswidget>/', $content, $matches);

        if (isset($matches[1])) {
            foreach ($matches[1] as $id) {
                $entity = $this->layoutPageWidgetRepository->find($id);

                if ($entity !== null) {
                    $entities[]  = $entity;
                }
            }
        }

        return $entities;
    }

    /**
     * @return DesignerManager
     */
    public function getDesignerManager()
    {
        return $this->designerManager;
    }

    /**
     * @param DesignerManager $designerManager
     * @return $this
     */
    public function setDesignerManager(DesignerManager $designerManager)
    {
        $this->designerManager = $designerManager;

        return $this;
    }

    /**
     * @return WidgetBuilder
     */
    public function getWidgetBuilder()
    {
        return $this->widgetBuilder;
    }

    /**
     * @param WidgetBuilder $widgetBuilder
     * @return $this
     */
    public function setWidgetBuilder($widgetBuilder)
    {
        $this->widgetBuilder = $widgetBuilder;

        return $this;
    }

    /**
     *
     */
    public function resetResources()
    {
        $this->javascripts = [];
        $this->css = [];
    }
}
