<?php
// Copyright (c) Pickware GmbH. All rights reserved.
// This file is part of software that is released under a proprietary license.
// You must not copy, modify, distribute, make publicly available, or execute
// its contents or parts thereof without express permission by the copyright
// holder, unless otherwise permitted by law.

namespace Shopware\Plugins\ViisonCommon\Components;

use Shopware\Components\DependencyInjection\Container;
use Shopware\Components\Model\ModelManager;
use Shopware\Components\Theme\Inheritance as ThemeInheritanceService;
use Shopware\Models\Document\Document as DocumentType;
use Shopware\Models\Shop\Locale;
use Shopware\Models\Shop\Shop;
use Shopware\Plugins\ViisonCommon\Classes\Document\GridPaperLayout;
use Shopware\Plugins\ViisonCommon\Classes\Document\PaperLayout;
use Shopware\Plugins\ViisonCommon\Classes\Document\RenderingContext\DocumentRenderingContext;
use Shopware\Plugins\ViisonCommon\Classes\Document\RenderingContext\GridLayoutRenderingContext;
use Shopware\Plugins\ViisonCommon\Classes\Document\RenderingContext\HtmlRenderingContext;
use Shopware\Plugins\ViisonCommon\Classes\Document\RenderingContext\TemplateRenderingContext;
use Shopware\Plugins\ViisonCommon\Classes\Document\RenderingEngine\DomPdfRenderingEngine;
use Shopware\Plugins\ViisonCommon\Classes\Document\RenderingEngine\MPdfRenderingEngine;
use Shopware\Plugins\ViisonCommon\Classes\Document\RenderingEngine\RenderingEngine;
use Shopware\Plugins\ViisonCommon\Classes\SmartyPlugins\Modifier\CurrencyModifier;
use Shopware\Plugins\ViisonCommon\Classes\Util\Util;
use Shopware\Plugins\ViisonCommon\Components\FileStorage\FileStorage;
use Shopware_Components_Snippet_Manager as SnippetManager;

/**
 * A factory service to create different rendering contexts.
 *
 * Available contexts:
 *  - Render a smarty template
 *  - Render a grid layouted document
 *  - Render a (Shopware) Document type
 */
class DocumentRenderingContextFactoryService
{
    const RENDERER_DOMPDF = 'dompdf';
    const RENDERER_MPDF = 'mpdf';

    /**
     * @var \Enlight_Template_Manager
     */
    protected $templateEngine;

    /**
     * @var ThemeInheritanceService|null
     */
    protected $themeInheritanceService;

    /**
     * @var ModelManager
     */
    protected $modelManager;

    /**
     * @var SnippetManager
     */
    protected $snippetManager;

    /**
     * @var string If true, missing snippets will be rendered as their name wrapped in hash signs.
     */
    protected $showSnippetPlaceholder;

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

    /**
     * @var FileStorage
     */
    private $documentFileStorage;

    /**
     * @param Container $container
     */
    public function __construct(Container $container)
    {
        $this->templateEngine = $container->get('template');
        $this->modelManager = $container->get('models');
        $this->documentFileStorage = $container->get('viison_common.document_file_storage_service');
        $this->snippetManager = $container->get('snippets');
        $this->showSnippetPlaceholder = $container->getParameter('shopware.snippet.showSnippetPlaceholder');
        $this->documentFileStorage = $container->get('viison_common.document_file_storage_service');

        try {
            $this->mpdfDefaultConfig = $container->getParameter('shopware.mpdf.defaultConfig');
        } catch (\InvalidArgumentException $e) {
            // The parameter only exists since SW 5.5. Use an empty array for all earlier versions.
            $this->mpdfDefaultConfig = [];
        }

        try {
            $this->themeInheritanceService = $container->get('theme_inheritance');
        } catch (\Exception $e) { // @codingStandardsIgnoreLine ; Empty catch not allowed
            // Do nothing, just avoid uncaught exception errors when a service does not exist.
        }
    }

    /**
     * Builds a RenderingContext that can render a smarty template stored at $templatePath
     *
     * @param string $templatePath Absolute or relative path to the template to render
     * @param string $renderer The renderer to use
     * @param $paperLayout $paperLayout
     * @return TemplateRenderingContext
     */
    public function createTemplateRenderingContext($templatePath, PaperLayout $paperLayout, $renderer = self::RENDERER_DOMPDF)
    {
        return new TemplateRenderingContext(
            $this->createRenderingEngine($renderer),
            $this->createTemplateEngine(),
            $templatePath,
            $paperLayout
        );
    }

    /**
     * Builds a rendering context capable of rendering a Shopware order document based on a given document type.
     * (Shopware\Models\Order\Document\Document)
     *
     * By default DomPdfRenderingEngine is used as the rendering engine. Pay attention that Shopware uses mPdf to render
     * order documents. This may result in different rendering result.
     *
     * The following template variables are automatically available:
     *  -  $Containers (containing the translated document boxes)
     *
     * The snippet resource is automatically added with snippets from the $locale. Also the theme from the $shop is
     * inherited.
     *
     * @param DocumentType $documentType The document type that should be rendered
     * @param $paperLayout $paperLayout
     * @param Locale|null $locale Localization to use for snippets, if null, the locale of the $languageSubShop is used
     * @param Shop|null $languageSubShop The shop to use for template inheritance and document elements. Normally the
     * language subshop of an order should be given here. If null, the active default shop is used.
     * @param string $renderer The renderer to use
     * @return DocumentRenderingContext
     */
    public function createDocumentRenderingContext(
        DocumentType $documentType,
        PaperLayout $paperLayout,
        Locale $locale = null,
        Shop $languageSubShop = null,
        $renderer = self::RENDERER_DOMPDF
    ) {
        if (!$languageSubShop) {
            $languageSubShop = $this->modelManager->getRepository('Shopware\\Models\\Shop\\Shop')->getActiveDefault();
        }
        if (!$locale) {
            $locale = $languageSubShop->getLocale();
        }

        $renderingEngine = $this->createRenderingEngine($renderer);
        if ($renderingEngine instanceof MPdfRenderingEngine) {
            $renderingEngine->setPageMargins(
                $documentType->getLeft(),
                $documentType->getRight(),
                $documentType->getTop(),
                $documentType->getBottom()
            );
        }

        $templateEngine = $this->createLocalizedTemplateEngine($languageSubShop, $locale);

        $documentRenderingContext = new DocumentRenderingContext(
            $renderingEngine,
            $templateEngine,
            $documentType,
            $languageSubShop,
            $this->modelManager,
            $paperLayout,
            $this->documentFileStorage
        );

        // Add the theme inheritance service if it exists
        // Template inheritance is only supported for theme versions >= 3 and Shopware 5
        if ($this->themeInheritanceService) {
            $documentRenderingContext->setThemeInheritanceService($this->themeInheritanceService);
            $documentRenderingContext->inheritFromShopTemplate($languageSubShop);
        }

        return $documentRenderingContext;
    }

    /**
     * Builds a GridLayoutRenderingContext that helps to render grid layouted documents
     *
     * @param string $templateFilePath
     * @param GridPaperLayout $gridPaperLayout
     * @param string $renderer
     * @return GridLayoutRenderingContext
     */
    public function createGridLayoutRenderingContext($templateFilePath, GridPaperLayout $gridPaperLayout, $renderer = self::RENDERER_DOMPDF)
    {
        $renderingEngine = $this->createRenderingEngine($renderer);
        $templateEngine = $this->createTemplateEngine();

        $renderingContext = new GridLayoutRenderingContext($renderingEngine, $templateEngine, $gridPaperLayout, $templateFilePath);

        return $renderingContext;
    }

    /**
     * Builds a HtmlRenderingContext that helps to render plain HTML.
     *
     * @param $html
     * @param PaperLayout $paperLayout
     * @param string $renderer
     * @return HtmlRenderingContext
     */
    public function createHtmlRenderingContext($html, PaperLayout $paperLayout, $renderer = self::RENDERER_DOMPDF)
    {
        $renderingEngine = $this->createRenderingEngine($renderer);

        return new HtmlRenderingContext($renderingEngine, $paperLayout, $html);
    }

    /**
     * Builds a new rendering engine instance according to a given renderer type.
     *
     * @param string $renderer
     * @return RenderingEngine
     */
    protected function createRenderingEngine($renderer = self::RENDERER_DOMPDF)
    {
        switch ($renderer) {
            case self::RENDERER_DOMPDF:
                return new DomPdfRenderingEngine();
            case self::RENDERER_MPDF:
                return new MPdfRenderingEngine($this->mpdfDefaultConfig);
        }
        throw new \InvalidArgumentException('The given parameter $renderer = "' . $renderer . '" is not a valid renderer.');
    }

    /**
     * Get a new localized template engine.
     *
     * @param Shop $shop
     * @param Locale $locale
     * @return \Enlight_Template_Manager
     */
    protected function createLocalizedTemplateEngine(Shop $shop, Locale $locale)
    {
        $templateEngine = $this->createTemplateEngine();

        $snippetManager = $this->createSnippetManager($shop, $locale);
        // Configure template engine to use the custom snippet manager
        $snippetResource = new \Enlight_Components_Snippet_Resource(
            $snippetManager,
            $this->showSnippetPlaceholder
        );
        $templateEngine->registerResource('snippet', $snippetResource);

        // Create a Zend_Currency using the shop's currency and the given locale and use it to register a
        // custom formatter
        $currency = new \Zend_Currency(
            $shop->getCurrency()->toArray(),
            new \Zend_Locale($locale->getLocale())
        );
        $currencyModifier = new CurrencyModifier($currency);
        $currencyModifier->registerInSmarty($templateEngine);

        $templateEngine->compile_id = sprintf(
            'viison_document_%d_%d',
            $shop->getId(),
            $locale->getId()
        );

        return $templateEngine;
    }

    /**
     * Get a new template engine.
     *
     * This returns a clone of \Enlight_Template_Manager. This enables us to modify the TemplateEngine as we want to,
     * without affecting the original template system of Shopware. For example you can add template paths as you want
     * to.
     *
     * @return \Enlight_Template_Manager
     */
    protected function createTemplateEngine()
    {
        // Clone template manager in a smarty secure aware way
        return Util::cloneTemplateManager($this->templateEngine);
    }

    /**
     * Creates a new snippet manager by cloning the existing one and changing its shop and locale, which both affect
     * the snippets loaded from the database. Cloning the snippet manager allows us to reconfigure it only for the
     * document creation, without affecting any other parts of Shopware.
     *
     * @param Shop $shop
     * @param Locale|null $locale
     * @return SnippetManager
     */
    protected function createSnippetManager(Shop $shop, Locale $locale = null)
    {
        $snippetManagerClone = clone $this->snippetManager;
        $snippetManagerClone->setShop($shop);
        if ($locale) {
            // Overwrite the locale after setting the shop, since 'setShop()' already sets the shop's locale
            $snippetManagerClone->setLocale($locale);
        }

        return $snippetManagerClone;
    }
}
