<?php

namespace Comitium5\DesignerBundle\Manager\Designer;


use Comitium5\DesignerBundle\Controller\Widget\AbstractDesignerWidgetController;
use Comitium5\DesignerBundle\Entity\User;
use Comitium5\DesignerBundle\Helper\Utils;
use Comitium5\DesignerBundle\Manager\Layout\LayoutManager;
use Comitium5\DesignerBundle\Manager\Layout\LayoutPageManager;
use Comitium5\DesignerBundle\Manager\Layout\LayoutPageWidgetManager;
use Comitium5\DesignerBundle\Manager\Page\PageManager;
use Comitium5\DesignerBundle\Manager\Template\TemplateGroupManager;
use Comitium5\DesignerBundle\Manager\Template\TemplateManager;
use Comitium5\DesignerBundle\Manager\View\ViewManager;
use Comitium5\DesignerBundle\Manager\Widget\WidgetManager;
use Comitium5\DesignerBundle\Model\Interfaces\Layout\LayoutInterface;
use Comitium5\DesignerBundle\Model\Interfaces\Layout\LayoutPageInterface;
use Comitium5\DesignerBundle\Model\Interfaces\Page\PageInterface;
use Comitium5\DesignerBundle\Model\Interfaces\Template\TemplateInterface;
use Comitium5\DesignerBundle\Model\Interfaces\View\ViewInterface;
use Comitium5\DesignerBundle\Provider\SubsiteProvider;
use Comitium5\DesignerBundle\Publisher\Page\PageContentResolver;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Templating\EngineInterface;

/**
 * Class DesignerManager
 *
 * @author Carles Gómez <carles@bab-soft.com>
 * @package Comitium5\DesignerBundle\Manager\Designer
 */
class DesignerManager
{
    const BUNDLE_TWIG_PATH = '@Comitium/Designer/%s';
    const CACHE_TWIG_PATH = '@Comitium/app/Resources/views/cache/tmp/%s';
    const DESIGNER_TWIG_PATH = 'Comitium5DesignerBundle:Designer:%s';
    const TWIG_PATH = 'Resources/views/Designer/%s';
    const BLOCK_CONTENT = 'cscontent';
    const RE_TAG = '/<%s((\s[\w\-]+="[^"]*")+)(\s\/)?>/i';
    const RE_CONTENT_TAG = '/<%s\b[^<]*(?:(?!<\/%s>)<[^<]*)*<\/%s>/i';
    const RE_CLOSE_TAG = '/<\/%s>/i';
    const RE_ATTRIBUTES = '/([\w\-]+)="([^"]*)"/i';
    const LAYOUT_TAG = 'cslayout';
    const WIDGET_TAG = 'cswidget';
    const SCRIPT_TAG = 'script';
    const ID_ATTRIBUTE = 'id';
    const NAME_ATTRIBUTE = 'name';
    const CLASS_ATTRIBUTE = 'class';
    const LOCKED_ATTRIBUTE = 'locked';
    const LINKED_ATTRIBUTE = 'linked';
    const CSS_ATTRIBUTE = 'style';
    const SUBSITE_FIELD = '_cs_subsite';
    const PAGE_FIELD = '_cs_page';
    const PAGE_REQUEST_FIELD = '_cs_page_request';
    const SUBSITE_REQUEST_FIELD = '_cs_subsite_request';
    const ENTITY_FIELD = '_cs_entity';
    const GA_DIMENSIONS_FIELD = '_cs_ga_dimensions';
    const DATE_FIELD = '_cs_currentDate';
    const METADATA_FIELD = '_cs_metaData';
    const CSS_BLOCK = '{% block CS_STYLES %}';
    const JS_BLOCK = '{% block CS_JS %}';
    const VARS_BLOCK = '{% block CS_VARS %}';
    const END_BLOCK = '{% endblock %}';
    const CSS_COMMENT = '{# CS_STYLES #}';
    const JS_COMMENT = '{# CS_JS #}';
    const VARS_COMMENT = '{# CS_VARS #}';
    const HEAD_CLOSE_TAG = '</head>';
    const BODY_CLOSE_TAG = '</body>';
    const CSS_TAG = '<style id="csstyles"></style>';
    const BASE_CSS_TEMPLATE = 'designer-basecss-template.html.twig';
    const BASE_JS_TEMPLATE = 'designer-basejs-template.html.twig';

    /**
     * @var TemplateManager
     */
    private $templateManager;

    /**
     * @var PageManager
     */
    private $pageManager;

    /**
     * @var LayoutManager
     */
    private $layoutManager;

    /**
     * @var LayoutPageManager
     */
    private $layoutPageManager;

    /**
     * @var LayoutPageWidgetManager
     */
    private $layoutPageWidgetManager;

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

    /**
     * @var \Twig_Environment
     */
    private $environment;

    /**
     * @var TemplateGroupManager
     */
    private $templateGroupManager;

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

    /**
     * @var int[]
     */
    private $tmpLayoutIds = array();

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

    /**
     * @var bool
     */
    private $forceTmp = false;

    /**
     * @var \DateTime
     */
    private $dateFieldValue;

    /**
     * @var ViewManager
     */
    private $viewManager;

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

    /**
     * DesignerManager constructor.
     *
     * @param TemplateManager $templateManager
     * @param PageManager $pageManager
     * @param LayoutManager $layoutManager
     * @param LayoutPageManager $layoutPageManager
     * @param LayoutPageWidgetManager $layoutPageWidgetManager
     * @param EngineInterface $templating
     * @param \Twig_Environment $environment
     * @param TemplateGroupManager $templateGroupManager
     * @param WidgetManager $widgetManager
     * @param ViewManager $viewManager
     * @param SubsiteProvider $subsiteProvider
     */
    public function __construct(
        TemplateManager $templateManager,
        PageManager $pageManager,
        LayoutManager $layoutManager,
        LayoutPageManager $layoutPageManager,
        LayoutPageWidgetManager $layoutPageWidgetManager,
        EngineInterface $templating,
        \Twig_Environment $environment,
        TemplateGroupManager $templateGroupManager,
        WidgetManager $widgetManager,
        ViewManager $viewManager,
        SubsiteProvider $subsiteProvider
    ) {
        $this->templateManager = $templateManager;
        $this->pageManager = $pageManager;
        $this->layoutManager = $layoutManager;
        $this->layoutPageManager = $layoutPageManager;
        $this->layoutPageWidgetManager = $layoutPageWidgetManager;
        $this->templating = $templating;
        $this->environment = $environment;
        $this->templateGroupManager = $templateGroupManager;
        $this->widgetManager = $widgetManager;
        $this->viewManager = $viewManager;
        $this->subsiteProvider = $subsiteProvider;
    }

    /**
     * @param $originPageId
     *
     * @return PageInterface|null
     */
    public function getPage($originPageId): ?PageInterface
    {
        return $this
            ->pageManager
            ->getRepository()
            ->findOneBy(
                ["originId" => $originPageId]
            );
    }

    /**
     * @param  int               $templateId
     * @return TemplateInterface
     */
    public function getTemplate($templateId)
    {
        return $this->templateManager->find($templateId);
    }

    /**
     * @param  int             $layoutId
     * @return LayoutInterface
     */
    public function getLayout($layoutId)
    {
        return $this->layoutManager->find($layoutId);
    }

    /**
     * @param PageInterface $page
     * @param bool $metaCode
     *
     * @return mixed|null|string|string[]
     * @throws \Exception
     */
    public function generateHtmlPage(PageInterface $page, $metaCode = false)
    {
        $initCode = $this->generateInitCode($page);

        if ($metaCode) {
            $code = $this->pageManager->getCode(
                $page->getTemplate()->getFilePath()
            );

            $code = $this->replaceTwigCode($code, false);

            $code = $this->replaceBlocks(
                $code, array(
                self::CSS_BLOCK => self::CSS_TAG
                    ."\t{% include '".sprintf(
                        self::DESIGNER_TWIG_PATH,
                        self::BASE_CSS_TEMPLATE
                    )."' %}\n",
                self::JS_BLOCK => "\t{% include '".sprintf(
                        self::DESIGNER_TWIG_PATH,
                        self::BASE_JS_TEMPLATE
                    )."' %}\n",
                self::VARS_BLOCK => $initCode,
            ), !is_null($page->getTemplate()->getParent())
            );

            $this
                ->pageManager
                ->saveFile($page, $code, true);

            $code = $this->generateContentLayouts(
                $this->getContentFromFile(
                    $page->getFilePath(true)
                ),
                $page,
                $page->getTemplate()->getLayouts(),
                true
            );

            $code = $this->cleanScripts($code);
        } else {
            $isTmp = $this->isTmp();

            $code = $this->generateContentLayouts(
                $this->getContentFromFile(
                    $page->getFilePath($isTmp)
                ),
                $page,
                $page->getTemplate()->getLayouts()
            );

            $code = $this->replaceTwigCode($code, false);
        }

        $code = str_replace(
            [
                self::VARS_COMMENT,
            ],
            [
                $initCode."\n".self::VARS_COMMENT,
            ],
            $code
        );

        return $code;
    }

    public function generateContentLayouts($code, PageInterface $page, Collection $layouts = null, $metaCode = false)
    {
        if ($layouts && !$layouts->isEmpty()) {
            $matches = array();
            if (preg_match_all(
                sprintf(self::RE_TAG, self::LAYOUT_TAG),
                $code,
                $matches,
                PREG_SET_ORDER
            )) {
                foreach ($matches as $data) {
                    $values = array();
                    $attributes = array();
                    if (preg_match_all(self::RE_ATTRIBUTES, $data[1], $attributes)) {
                        $values = array_combine($attributes[1], $attributes[2]);
                    }
                    $layout = null;
                    if (isset($values[self::ID_ATTRIBUTE])) {
                        $layout = $layouts
                            ->filter(function ($layout) use ($values) {
                                return ($layout->getId() == intval($values[self::ID_ATTRIBUTE]));
                            })
                            ->first();
                    } elseif (isset($values[self::NAME_ATTRIBUTE])) {
                        $layout = $layouts
                            ->filter(function ($layout) use ($values) {
                                return ($layout->getName() == trim($values[self::NAME_ATTRIBUTE]));
                            })
                            ->first();
                    }
                    if (!empty($layout)) {
                        $layoutCode = '';
                        $layoutPage = $layout->getLayoutPage($page);
                        if (empty($layoutPage)) {
                            $layoutPage = $layout->getLayoutPage();
                        }

                        $attributes = array();
                        if (isset($values[self::CLASS_ATTRIBUTE])) {
                            $attributes[] = self::CLASS_ATTRIBUTE.
                                '="'.$values[self::CLASS_ATTRIBUTE].'"';
                        }
                        if (isset($values[self::CSS_ATTRIBUTE])) {
                            $attributes[] = self::CSS_ATTRIBUTE.
                                '="'.$values[self::CSS_ATTRIBUTE].'"';
                        }
                        if ($metaCode) {
                            $attributes[] = self::ID_ATTRIBUTE.'="'.$layout->getId().'"';
                            if ($layout->getName()) {
                                $attributes[] = self::NAME_ATTRIBUTE.'="'.$layout->getName().'"';
                            }
                            if ($layout->getLocked()) {
                                $attributes[] = self::LOCKED_ATTRIBUTE.'="true"';
                            }
                            if ($layout->getLinked()) {
                                $attributes[] = self::LINKED_ATTRIBUTE.'="true"';
                            }
                            if (!empty($layoutPage)) {
                                $layoutCode = $layoutPage->getHtml();
                            }
                            $tagCode = '<'.self::LAYOUT_TAG.' '.implode(' ', $attributes).'>';
                            $endTagCode = '</'.self::LAYOUT_TAG.'>';
                        } else {
                            $tagCode = '<div'.(
                                (count($attributes) > 0) ? ' '.implode(' ', $attributes) : ''
                                ).'>';
                            $endTagCode = '</div>';
                            if (!empty($layoutPage)) {
                                $layoutCode = $layoutPage->getHtml();
                                if ($layoutPage->getStartAt()) {
                                    $tagCode = "{% if date('"
                                        .$layoutPage->getStartAt()->format('Y-m-d H:i:s')
                                        ."') <= ".self::DATE_FIELD." %}\n"
                                        .$tagCode;
                                    $endTagCode .= "\n{% endif %}";
                                }
                                if ($layoutPage->getEndAt()) {
                                    $tagCode = "{% if date('"
                                        .$layoutPage->getEndAt()->format('Y-m-d H:i:s')
                                        ."') > ".self::DATE_FIELD." %}\n"
                                        .$tagCode;
                                    $endTagCode .= "\n{% endif %}";
                                }
                            }
                        }
                        if (
                            $layout->getChildren()->isEmpty() &&
                            $layout->getLayouts()->isEmpty()
                        ) {
                            $layoutCode = $tagCode.$layoutCode.$endTagCode;

                            $start = strpos($code, $data[0]);
                            if ($start !== false) {
                                $end = strpos($code, '</'.self::LAYOUT_TAG.'>', $start);
                                $end += strlen('</'.self::LAYOUT_TAG.'>');
                                $code = substr($code, 0, $start)
                                    .$layoutCode.substr($code, $end);
                            }
                        } else {
                            $code = $this->generateContentLayouts(
                                $code, $page, $layout->getChildren(), $metaCode
                            );
                            $code = $this->generateContentLayouts(
                                $code, $page, $layout->getLayouts(), $metaCode
                            );
                            if (!$metaCode) {
                                $start = strpos($code, $data[0]);
                                while ($start !== false) {
                                    $start = strpos($code, '</'.self::LAYOUT_TAG.'>', $start);
                                    $code = substr($code, 0, $start).$endTagCode.substr(
                                            $code, $start+strlen('</'.self::LAYOUT_TAG.'>')
                                        );
                                    $start = strpos($code, $data[0], $start);
                                }
                                $code = str_replace(
                                    $data[0],
                                    $tagCode,
                                    $code
                                );
                            }
                        }
                    }
                }
            }
        }

        return $code;
    }

    /**
     * @param PageInterface $page
     *
     * @return bool
     */
    public function generateFilePage(PageInterface $page)
    {
        $code = $this->pageManager->getCode(
            $page->getTemplate()->getFilePath()
        );
        $code = $this->replaceBlocks(
            $code, array(
            self::CSS_BLOCK => self::CSS_COMMENT,
            self::JS_BLOCK => self::JS_COMMENT,
            self::VARS_BLOCK => self::VARS_COMMENT,
        ), !is_null($page->getTemplate()->getParent())
        );

        return $this->pageManager->saveFile(
            $page, $this->replaceTwigCode($code)
        );
    }

    /**
     * @param TemplateInterface $template
     * @param bool $generateHtml
     * @return mixed|null|string|string[]
     * @throws \Exception
     */
    public function generateHtmlTemplate(TemplateInterface $template, $generateHtml = true)
    {
        if (!is_null($template->getParent())) {
            $this->generateHtmlTemplate(
                $template->getParent(), false
            );
        }
        $code = $this->addBlocks(
            $template->getHtml(), is_null($template->getParent())
        );

        if ($generateHtml) {
            $code = $this->replaceBlocks(
                $code, array(
                    self::CSS_BLOCK => self::CSS_TAG
                        ."\t{% include '".sprintf(
                            self::DESIGNER_TWIG_PATH,
                            self::BASE_CSS_TEMPLATE
                        )."' %}\n",
                    self::JS_BLOCK => "\t{% include '".sprintf(
                            self::DESIGNER_TWIG_PATH,
                            self::BASE_JS_TEMPLATE
                        )."' %}\n",
                    self::VARS_BLOCK => $this->generateInitCode(),
                )
            );
        }
        //        $template->setHtml($code);

        $this
            ->templateManager
            ->saveCode($template, $code, true);

        if ($generateHtml) {
            $code = $this->generateHtmlLayouts(
                $this->getContentFromFile(
                    $template->getFilePath(true)
                ),
                $template->getLayouts()
            );

            $code = $this->cleanScripts($code);
        }

        return $code;
    }

    public function cleanScripts($code)
    {
        $matches = array();
        if (preg_match_all(
            sprintf(
                self::RE_CONTENT_TAG,
                self::SCRIPT_TAG,
                self::SCRIPT_TAG,
                self::SCRIPT_TAG
            ),
            $code,
            $matches,
            PREG_SET_ORDER
        )) {
            foreach ($matches as $data) {
                $code = str_replace($data[0], '', $code);
            }
        }

        return preg_replace('/[\n\r\t]+/', "\n", $code);
    }

    public function generateHtmlLayouts($code, Collection $layouts = null, $publish = false)
    {
        if (!$layouts->isEmpty()) {
            $matches = array();
            if (preg_match_all(
                sprintf(self::RE_TAG, self::LAYOUT_TAG),
                $code,
                $matches,
                PREG_SET_ORDER
            )) {
                foreach ($matches as $data) {
                    $values = array();
                    $attributes = array();
                    if (preg_match_all(self::RE_ATTRIBUTES, $data[1], $attributes)) {
                        $values = array_combine($attributes[1], $attributes[2]);
                    }
                    $layout = null;
                    if (isset($values[self::ID_ATTRIBUTE])) {
                        $layout = $layouts
                            ->filter(function ($layout) use ($values) {
                                return ($layout->getId() == intval($values[self::ID_ATTRIBUTE]));
                            })
                            ->first();
                    } elseif (isset($values[self::NAME_ATTRIBUTE])) {
                        $layout = $layouts
                            ->filter(function ($layout) use ($values) {
                                return ($layout->getName() == trim($values[self::NAME_ATTRIBUTE]));
                            })
                            ->first();
                    }
                    if (!empty($layout)) {
                        $layoutCode = $this->generateHtmlLayouts(
                            $layout->getHtml(),
                            $layout->getChildren(),
                            $publish
                        );
                        $layoutCode = $this->generateHtmlLayouts(
                            $layoutCode,
                            $layout->getLayouts(),
                            $publish
                        );
                        /*$layoutCode = preg_replace(
                            sprintf(self::RE_TAG, self::WIDGET_TAG),
                            '',
                            $layoutCode
                        );*/
                        $attributes = array(
                            self::ID_ATTRIBUTE.'="'.$layout->getId().'"',
                        );
                        if (isset($values[self::CLASS_ATTRIBUTE])) {
                            $attributes[] = self::CLASS_ATTRIBUTE.
                                '="'.$values[self::CLASS_ATTRIBUTE].'"';
                        }
                        if ($layout->getName()) {
                            $attributes[] = self::NAME_ATTRIBUTE.'="'.$layout->getName().'"';
                        }
                        if ($layout->getLocked()) {
                            $attributes[] = self::LOCKED_ATTRIBUTE.'="true"';
                        }
                        if ($layout->getLinked()) {
                            $attributes[] = self::LINKED_ATTRIBUTE.'="true"';
                        }
                        if ($layout->getCss()) {
                            $attributes[] = self::CSS_ATTRIBUTE.'="'.$layout->getCss().'"';
                        }
                        if ($publish && $layout->getPredefined()) {
                            $layoutCode = '<'.self::LAYOUT_TAG.' '
                                .implode(' ', $attributes).'>'.$layoutCode
                                .'</'.self::LAYOUT_TAG.'>';
                            $this->layoutManager->saveFile(
                                $layout, $layoutCode
                            );
                            $code = str_replace(
                                $data[0],
                                "{% include '".$this->layoutManager->getNamespacePath($layout)."' %}\n",
                                $code
                            );
                        } else {
                            $code = str_replace(
                                $data[0],
                                '<'.self::LAYOUT_TAG.' '.implode(' ', $attributes).'>'.$layoutCode.'</'.self::LAYOUT_TAG.'>',
                                $code
                            );
                        }
                    }
                }
            }
        }

        return $code;
    }

    /**
     * @todo ComitiumSuite\Components\Widget\Entity\Widget::generateCode()
     * @param  string     $code
     * @param  Collection $widgets
     * @return string
     */
    public function generateHtmlWidgets($code, Collection $widgets = null)
    {
        if (!$widgets->isEmpty()) {
            $matches = array();
            if (preg_match_all(
                sprintf(self::RE_TAG, self::WIDGET_TAG),
                $code,
                $matches,
                PREG_SET_ORDER
            )) {
                foreach ($matches as $data) {
                    $values = array();
                    $attributes = array();
                    if (preg_match_all(self::RE_ATTRIBUTES, $data[1], $attributes)) {
                        $values = array_combine($attributes[1], $attributes[2]);
                    }
                    if (isset($values[self::ID_ATTRIBUTE])) {
                        $widget = $widgets->filter(
                            create_function(
                                '$widget',
                                'return ($widget->getId() == '.intval($values[self::ID_ATTRIBUTE]).');'
                            )
                        );
                    }
                    if (!empty($widget)) {
                        $widgetCode = $widget->generateCode();
                        $code = str_replace(
                            $data[0],
                            '<'.self::WIDGET_TAG.' '.$data[1].'>'.$widgetCode.'</'.self::WIDGET_TAG.'>',
                            $widgetCode,
                            $code
                        );
                    }
                }
            }
        }

        return $code;
    }

    /**
     * @param PageInterface $page
     *
     * @return string
     */
    public function generateJsonPage(PageInterface $page)
    {
        return $page->toJSON();
    }

    /**
     * @param  TemplateInterface $template
     * @return string
     */
    public function generateJsonTemplate(TemplateInterface $template)
    {
        return $template->toJSON();
    }

    /**
     * @param  TemplateInterface $template
     * @return array
     */
    public function getBreakpoints(TemplateInterface $template)
    {
        $results = array();

        $breakpoints = $template->getBreakpoints();
        if ($breakpoints->count() == 0) {
            $parent = $template->getParent();
            while ($breakpoints->count() == 0 && !empty($parent)) {
                $breakpoints = $parent->getBreakpoints();
                $parent = $parent->getParent();
            }
        }
        foreach ($breakpoints as $breakpoint) {
            if ($breakpoint->isEnabled()) {
                if (!isset($results[$breakpoint->getType()])) {
                    $results[$breakpoint->getType()] = array(
                        'name' => $breakpoint->getName(),
                        'key' => Utils::makeSlug($breakpoint->getName()),
                        'sizes' => array(),
                    );
                }
                $results[$breakpoint->getType()]['sizes'][] = $breakpoint;
            }
        }

        return $results;
    }

    /**
     * @param $path
     *
     * @return string
     * @throws \Exception
     */
    public function getContentFromFile($path)
    {
        $this->environment->enableAutoReload();

        $filePath = sprintf(self::BUNDLE_TWIG_PATH, $path);

        $view = $this
            ->viewManager
            ->findByName($filePath);

        if ($view instanceof ViewInterface) {
            return $this->templating->render(
                $view->getName()
            );
        }

        throw new \Exception("No view found with name ".$filePath);
    }

    /**
     * @param  string $search
     * @return array
     */
    public function getLayouts($search)
    {
        $results = array();

        $layouts = $this
            ->layoutManager
            ->getRepository()
            ->searchResult(
                $search,
                array('parent' => null)
            );

        foreach ($layouts as $layout) {
            $data = $layout->toArray();
            $templates = $this->templateManager
                ->getRepository()
                ->findByLayoutResult(
                    $layout->getId()
                );
            $data['removable'] = empty($templates);
            $data['code'] = $this->generateHtmlLayouts(
                $layout->getHtml(),
                $layout->getChildren()
            );

            $results[] = $data;
            unset($layout, $data);
        }

        return $results;
    }

    /**
     * @param string $search
     * @param User $user
     * @param bool $assoc
     *
     * @return array
     */
    public function getWidgets($search = '', User $user, $assoc = true)
    {
        $results = array();

        $widgets= $this
            ->widgetManager
            ->getRepository()
            ->findByVisualEditor(
                $search,
                [
//                    StateBehaviours::PRODUCTION,
//                    StateBehaviours::STAND_BY
                ],
                $user
            );

        if ($assoc && !empty($widgets)) {
            foreach ($widgets as $widget) {
                $data = $widget->toArray();
                $results[] = $data;
                unset($widget, $data);
            }
        } else {
            $results = $widgets;
        }

        return $results;
    }

    /**
     * @param  int   $templateId
     * @param  mixed $data
     * @return bool
     */
    public function saveTemplate($templateId, $data)
    {
        $success = false;

        $template = $this->templateManager
            ->fromArray($data, $this->templateGroupManager);

        $success = ($template->getId() == $templateId);
        if ($success) {
            $template = $this->templateManager->save($template);
            if (!empty($template)) {
                $code = (!empty($data['code']) ? $data['code'] : '');
                $this->replaceNewLayoutIds($template, $code);
                $template = $this->templateManager->save($template);
                $success = (!empty($template));
                if ($success && !empty($data['removedLayouts'])) {
                    foreach ($data['removedLayouts'] as $layoutId) {
                        $layout = $this->layoutManager->find(
                            $layoutId
                        );
                        if (
                        !$layout->isPredefined()
                        ) {
                            $this->layoutManager->remove($layout);
                        } else {
                            $layout->setParent(null);
                            $this->layoutManager->save($layout);
                        }
                    }
                }
            }
        }

        return $success;
    }

    /**
     * @param  mixed $data
     * @param  int   $templateId (optional)
     * @param  int   $pageId     (optional)
     * @return bool
     */
    public function saveContent($data, $templateId = null, $pageId = null)
    {
        $success = false;
        $allLayoutPages = array();
        $layoutPageWidgets = array();

        if (!empty($pageId)) {
            $page = $this->getPage($pageId);
            $template = $page->getTemplate();
        } else {
            $template = $this->getTemplate($templateId);
        }

        if (!empty($template)) {
            if (!empty($data['layouts'])) {
                foreach ($data['layouts'] as $layoutData) {
                    $id = $layoutData['layoutId'];
                    if (isset($this->tmpLayoutIds[$id])) {
                        $id = $this->tmpLayoutIds[$id];
                    }
                    $layout = $this->layoutManager->find($id);
                    if (!empty($layout)) {
                        $layoutPage = $this->layoutPageManager
                            ->getRepository()->findOneBy(array(
                                'layout' => $id,
                                'page' => ($layout->isLinked() ? null : $pageId),
                            ));
                        if (empty($layoutPage) && !empty($pageId)) {
                            $layoutPage = $this->layoutPageManager->getFactory()->create();
                            $layoutPage->setLayout($layout);
                            $page->addLayoutPage($layoutPage);
                        }
                        if (!empty($layoutPage)) {
                            if (
                                $layout->getChildren()->count() == 0 &&
                                $layout->getLayouts()->count() == 0
                            ) {
                                $layoutPage->setHtml(
                                    $layoutData['html']
                                );
                            }
                            $layoutPage->setPosition(max(1, intval($layoutData['position'])));
                            $layoutPage->setStartAt(Utils::getDateTime(
                                $layoutData['startAt']
                            ));
                            $layoutPage->setEndAt(Utils::getDateTime(
                                $layoutData['endAt']
                            ));
                            $allLayoutPages[$layoutData['layoutId']] = $layoutPage;
                        }
                    }
                }
            }
            if (!empty($data['widgets'])) {
                foreach ($data['widgets'] as $widgetInstanceData) {
                    if (is_string($widgetInstanceData['id'])) {
                        $widgetInstanceData['tmpId'] = $widgetInstanceData['id'];
                        $widgetInstanceData['id'] = 0;
                    }
                    if (isset(
                        $allLayoutPages[$widgetInstanceData['layoutId']]
                    )) {
                        $layoutPage = $allLayoutPages[
                        $widgetInstanceData['layoutId']
                        ];
                        $widgetInstanceData['layoutPage'] = array(
                            'id' => $layoutPage->getId(),
                        );
                        $layoutPageWidget = $this->layoutPageWidgetManager
                            ->fromArray(
                                $widgetInstanceData,
                                $this->layoutPageManager,
                                $this->layoutManager,
                                $this->pageManager
                            );

                        $layoutPage->addLayoutPageWidget($layoutPageWidget);
                        if ($layoutPageWidget->getId() > 0) {
                            $layoutPageWidgets[] = $layoutPageWidget->getId();
                        } else {
                            $layoutPageWidgets[] = $layoutPageWidget->getTmpId();
                        }
                    }
                }
            }
            $success = (count($allLayoutPages) == 0);
            foreach ($allLayoutPages as $id => $layoutPage) {
                foreach ($layoutPage->getLayoutPageWidgets() as $layoutPageWidget) {
                    if (
                        !in_array($layoutPageWidget->getId(), $layoutPageWidgets) &&
                        !in_array($layoutPageWidget->getTmpId(), $layoutPageWidgets, true)
                    ) {
                        $layoutPage->removeLayoutPageWidget($layoutPageWidget);
                        $this->layoutPageWidgetManager
                            ->remove($layoutPageWidget);
                    }
                }
                $layoutPage = $this->layoutPageManager->save($layoutPage);
                if (!empty($layoutPage)) {
                    $this->replaceNewLayoutPageWidgetIds($layoutPage);
                    $layoutPage = $this->layoutPageManager->save($layoutPage);
                    $success = (!empty($layoutPage));
                }
            }
            if ($success) {
                if (!empty($pageId)) {
                    //$page->setUpdatedAt(new \DateTime());
                    //$success = $this->pageManager->save($page);
                } else {
                    $template->setUpdatedAt(new \DateTime());
                    $success = $this->templateManager->save($template);
                }
            }
        }

        return $success;
    }

    /**
     * @param LayoutPageInterface $layoutPage
     */
    public function replaceNewLayoutPageWidgetIds(LayoutPageInterface $layoutPage)
    {
        $attribute = self::ID_ATTRIBUTE.'="%s"';
        $html = $layoutPage->getHtml();
        foreach ($layoutPage->getLayoutPageWidgets() as $layoutPageWidget) {
            $tmpId = $layoutPageWidget->getTmpId();
            if (!empty($tmpId)) {
                $html = str_replace(
                    sprintf($attribute, $tmpId),
                    sprintf($attribute, $layoutPageWidget->getId()),
                    $html
                );
            }
        }
        $layoutPage->setHtml($html);
    }

    /**
     * @param  int  $templateId
     * @return bool
     */
    public function publishTemplate($templateId)
    {
        $success = false;

        $template = $this->getTemplate(
            $templateId
        );
        if (!empty($template)) {
            if (
                !is_null($template->getParent())
                && !$this->templateManager->fileExists($template->getParent())
            ) {
                $this->publishTemplate(
                    $template->getParent()->getId()
                );
            }
            $originalCode = $template->getHtml();

            $code = $this->replaceTwigCode(
                $originalCode
            );
            $code = $this->addBlocks(
                $code, is_null($template->getParent())
            );
            $code = $this->generateHtmlLayouts(
                $code,
                $template->getLayouts(),
                true
            );
            $template->setHtml($code);
            $success = $this->templateManager
                ->saveCode($template, $code);
            if ($success) {
                $template->setHtml($originalCode);
                $template->setPublishedAt(new \DateTime());
                $success = $this->templateManager->save($template);
            }
        }

        return $success;
    }

    /**
     * @param PageInterface|null $page
     * @param bool $define
     *
     * @return string
     * @throws \Exception
     */
    public function generateInitCode(PageInterface $page = null, $define = false)
    {
        if ($define) {
            $code = "{% set ".self::SUBSITE_FIELD." = null %}\n";
            $code .= "{% set ".self::PAGE_FIELD." = null %}\n";
            $code .= "{% set ".self::PAGE_REQUEST_FIELD." = null %}\n";
            $code .= "{% set ".self::SUBSITE_REQUEST_FIELD." = null %}\n";
            $code .= "{% set ".self::ENTITY_FIELD." = null %}\n";
            $code .= "{% set ".self::GA_DIMENSIONS_FIELD." = null %}\n";

            if ($this->isTmpMode === true && $this->dateFieldValue !== null) {
                $code .= "{% set ".self::DATE_FIELD." = date('".$this->dateFieldValue->format('Y-m-d H:i:s')."') %}\n";
            } else {
                $code .= "{% set ".self::DATE_FIELD." = date('now') %}\n";
            }

            $code .= "{% set ".self::METADATA_FIELD." = null %}\n";
        } else {
            $code = "{% set ".self::SUBSITE_FIELD." = ".json_encode(
                    $this->subsiteProvider->getSubSite(), JSON_UNESCAPED_UNICODE
                )." %}\n";
            $pageCode = 'null';
            if (!empty($page)) {
                $pageCode = json_encode($page->toArray(array(
                    'id', 'categories', 'tags', 'lang', 'type', 'redirectUrl', 'public', 'private',
                )), JSON_UNESCAPED_UNICODE);
            }
            $code .= "{% set ".self::PAGE_FIELD." = ".$pageCode." %}\n";
            $code .= "{% set ".self::PAGE_REQUEST_FIELD." = app.request.attributes.get('_page_request') %}\n";
            $code .= "{% set ".self::SUBSITE_REQUEST_FIELD." = app.request.attributes.get('" . AbstractDesignerWidgetController::SUBSITE_REQUEST_KEY . "') %}\n";
            $code .= "{% set ".self::ENTITY_FIELD." = app.request.attributes.get('_entity') %}\n";
            $code .= "{% set ".self::GA_DIMENSIONS_FIELD." = app.request.attributes.get('_ga_dimensions') %}\n";

            if ($this->isTmpMode === true && $this->dateFieldValue !== null) {
                $code .= "{% set ".self::DATE_FIELD." = date('".$this->dateFieldValue->format('Y-m-d H:i:s')."') %}\n";
            } else {
                $code .= "{% set ".self::DATE_FIELD." = date('now') %}\n";
            }

            $code .= "{% set ".self::METADATA_FIELD." = app.request.attributes.get('_metaData') %}\n";

        }

        return $code;
    }

    /**
     * @param $code
     * @param bool $encode
     * @param bool $inheritances
     * @return mixed
     */
    public function replaceTwigCode(
        $code, $encode = true, $inheritances = false
    ) {
        $twigTags = array('{{', '}}', '{#', '#}', '{%', '%}');
        $encodeTags = array('[[', ']]', '[#', '#]', '[%', '%]');
        if ($encode) {
            if (!$inheritances) {
                $code = preg_replace(
                    array(
                        '/\{% (block \w+) %\}/i',
                        '/\{\{ parent\(\) \}\}/i',
                        '/\{% endblock %\}/i',
                        '/\{% (extends \'[^\']+\') %\}/i',
                        '/\{% (include \'[^\']+\') %\}/i'
                    ), array(
                    '#% $1 %#',
                    '#%# parent() #%#',
                    '#% endblock %#',
                    '#% $1 %#',
                    '#% $1 %#'
                ), $code
                );
            }
            $code = str_replace(
                $twigTags,
                $encodeTags,
                $code
            );
            if (!$inheritances) {
                $code = preg_replace(
                    array(
                        '/#% (block \w+) %#/i',
                        '/#%# parent\(\) #%#/i',
                        '/#% endblock %#/i',
                        '/#% (extends \'[^\']+\') %#/i',
                        '/#% (include \'[^\']+\') %#/i'
                    ), array(
                    '{% $1 %}',
                    '{{ parent() }}',
                    '{% endblock %}',
                    '{% $1 %}',
                    '{% $1 %}'
                ), $code
                );
            }
        } else {
            $code = str_replace(
                $encodeTags,
                $twigTags,
                $code
            );
        }

        return $code;
    }

    /**
     * @param TemplateInterface $template
     * @param string            $html
     */
    public function replaceNewLayoutIds(TemplateInterface $template, $html)
    {
        $attribute = self::ID_ATTRIBUTE.'="%s"';

        foreach ($template->getLayouts() as $layout) {
            $tmpId = $layout->getTmpId();
            if (!empty($tmpId)) {
                $this->tmpLayoutIds[$tmpId] = $layout->getId();
                $html = str_replace(
                    sprintf($attribute, $tmpId),
                    sprintf($attribute, $layout->getId()),
                    $html
                );
            }
            $this->replaceNewChildrenIds($layout);
        }
        if (!empty($html)) {
            $template->defineBlockContent(
                $html
            );
        }
    }

    /**
     * @param LayoutInterface $layout
     */
    public function replaceNewChildrenIds(LayoutInterface $layout)
    {
        $attribute = self::ID_ATTRIBUTE.'="%s"';

        foreach ($layout->getLayoutPages() as $layoutPage) {
            $html = $layoutPage->getHtml();
            foreach ($layout->getChildren() as $child) {
                $tmpId = $child->getTmpId();
                if (!empty($tmpId)) {
                    $this->tmpLayoutIds[$tmpId] = $child->getId();
                    $html = str_replace(
                        sprintf($attribute, $tmpId),
                        sprintf($attribute, $child->getId()),
                        $html
                    );
                }
                $this->replaceNewChildrenIds($child);
            }
            $layoutPage->setHtml($html);
        }
    }

    /**
     * @param $code
     * @param bool $isParent
     * @return mixed|string
     * @throws \Exception
     */
    protected function addBlocks($code, $isParent = false)
    {
        if (strpos($code, self::VARS_BLOCK) === false) {
            $code = self::VARS_BLOCK.self::END_BLOCK."\n".$code;
            if ($isParent) {
                $code = $this->generateInitCode(null, true).$code;
            }
        }
        if (strpos($code, self::CSS_BLOCK) === false) {
            if (strpos($code, self::HEAD_CLOSE_TAG) === false) {
                $code .= self::CSS_BLOCK.self::END_BLOCK."\n";
            } else {
                $code = str_replace(
                    self::HEAD_CLOSE_TAG,
                    "\t".self::CSS_BLOCK.self::END_BLOCK."\n\t".self::HEAD_CLOSE_TAG,
                    $code
                );
            }
        }
        if (strpos($code, self::JS_BLOCK) === false) {
            if (strpos($code, self::BODY_CLOSE_TAG) === false) {
                $code .= self::JS_BLOCK.self::END_BLOCK."\n";
            } else {
                $code = str_replace(
                    self::BODY_CLOSE_TAG,
                    "\t".self::JS_BLOCK.self::END_BLOCK."\n\t".self::BODY_CLOSE_TAG,
                    $code
                );
            }
        }

        return $code;
    }

    /**
     * @param $code
     * @param $replaceCodes
     * @param bool $addParent
     * @return mixed
     */
    protected function replaceBlocks($code, $replaceCodes, $addParent = false)
    {
        if (count($replaceCodes) > 0) {
            foreach ($replaceCodes as $search => $replaceCode) {
                $code = str_replace(
                    $search,
                    $search."\n\t"
                    .($addParent ? "{{ parent() }}\n\t" : '')
                    .$replaceCode,
                    $code
                );
            }
        }

        return $code;
    }

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

    /**
     * @param boolean $isTmpMode
     */
    public function setIsTmpMode($isTmpMode)
    {
        $this->isTmpMode = $isTmpMode;
    }

    /**
     * @return \DateTime
     */
    public function getDateFieldValue()
    {
        return $this->dateFieldValue;
    }

    /**
     * @param \DateTime $dateFieldValue
     * @return $this
     */
    public function setDateFieldValue(\DateTime $dateFieldValue)
    {
        $this->dateFieldValue = $dateFieldValue;

        return $this;
    }

    /**
     * @return boolean
     */
    public  function isForceTmp()
    {
        return $this->forceTmp;
    }

    /**
     * @param boolean $forceTmp
     * @return $this
     */
    public function setForceTmp($forceTmp)
    {
        $this->forceTmp = $forceTmp;

        return $this;
    }

    /**
     * @return bool
     */
    private function isTmp(): bool
    {
        return $this->isTmpMode || $this->forceTmp;
    }
}
