<?php

declare(strict_types=1);

/*
 * Copyright (C) 2021 Daniel Siepmann <coding@daniel-siepmann.de>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

namespace DanielSiepmann\DsSite\Frontend\RssFeed;

use Psr\Http\Message\ServerRequestInterface;
use TYPO3Fluid\Fluid\View\TemplateView;
use TYPO3\CMS\Core\Http\PropagateResponseException;
use TYPO3\CMS\Core\TypoScript\TypoScriptService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\Core\Rendering\RenderingContextFactory;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\Controller\ErrorController;
use TYPO3\CMS\Seo\XmlSitemap\Exception\InvalidConfigurationException;
use TYPO3\CMS\Seo\XmlSitemap\XmlSitemapDataProviderInterface;

class XmlSitemapRenderer
{
    private array $typoScriptConfiguration = [];

    protected array $configuration;

    private ContentObjectRenderer $contentObjectRenderer;

    protected TemplateView $view;

    public function __construct(
        protected TypoScriptService $typoScriptService,
        protected RenderingContextFactory $renderingContextFactory,
    ) {
    }

    public function setContentObjectRenderer(ContentObjectRenderer $contentObjectRenderer): void
    {
        $this->contentObjectRenderer = $contentObjectRenderer;
    }

    protected function initialize(array $fullConfiguration): void
    {
        $this->configuration = $this->typoScriptService->convertTypoScriptArrayToPlainArray($fullConfiguration['plugin.']['tx_seo.'] ?? []);
        $renderingContext = $this->renderingContextFactory->create();
        $templatePaths = $renderingContext->getTemplatePaths();
        $templatePaths->setTemplateRootPaths($this->configuration['view']['templateRootPaths']);
        $templatePaths->setLayoutRootPaths($this->configuration['view']['layoutRootPaths']);
        $templatePaths->setPartialRootPaths($this->configuration['view']['partialRootPaths']);
        $templatePaths->setFormat('xml');
        $this->view = GeneralUtility::makeInstance(TemplateView::class, $renderingContext);
        $this->view->assign('settings', $this->getSettings());
    }

    public function render(string $_, array $typoScriptConfiguration, ServerRequestInterface $request): string
    {
        $this->typoScriptConfiguration = $typoScriptConfiguration;

        $this->initialize($GLOBALS['TSFE']->tmpl->setup);
        $this->view->assign('type', $GLOBALS['TSFE']->type);
        $sitemapType = $typoScriptConfiguration['sitemapType'] ?? 'xmlSitemap';
        if (!empty($sitemap = ($request->getQueryParams()['sitemap'] ?? null))) {
            return $this->renderSitemap($request, $sitemap, $sitemapType);
        }

        return $this->renderIndex($request, $sitemapType);
    }

    protected function renderIndex(ServerRequestInterface $request, string $sitemapType): string
    {
        $sitemaps = [];
        foreach ($this->configuration['config'][$sitemapType]['sitemaps'] ?? [] as $sitemap => $config) {
            if (!empty($config['provider']) && is_string($config['provider'])
                && class_exists($config['provider'])
                && is_subclass_of($config['provider'], XmlSitemapDataProviderInterface::class)
            ) {
                /** @var XmlSitemapDataProviderInterface $provider */
                $provider = GeneralUtility::makeInstance(
                    $config['provider'],
                    $request,
                    $sitemap,
                    (array)($config['config'] ?? [])
                );

                $pages = $provider->getNumberOfPages();

                for ($page = 0; $page < $pages; $page++) {
                    $sitemaps[] = [
                        'key' => $sitemap,
                        'page' => $page,
                        'lastMod' => $provider->getLastModified(),
                    ];
                }
            }
        }

        $this->view->assign('sitemapType', $sitemapType);
        $this->view->assign('sitemaps', $sitemaps);

        return $this->view->render('Index');
    }

    protected function renderSitemap(ServerRequestInterface $request, string $sitemap, string $sitemapType): string
    {
        if (!empty($sitemapConfig = $this->configuration['config'][$sitemapType]['sitemaps'][$sitemap] ?? null)) {
            if (class_exists($sitemapConfig['provider']) &&
                is_subclass_of($sitemapConfig['provider'], XmlSitemapDataProviderInterface::class)) {
                /** @var XmlSitemapDataProviderInterface $provider */
                $provider = GeneralUtility::makeInstance(
                    $sitemapConfig['provider'],
                    $request,
                    $sitemap,
                    (array)($sitemapConfig['config'] ?? [])
                );

                $items = $provider->getItems();

                $this->view->assign('items', $items);
                $this->view->assign('sitemapType', $sitemapType);

                $template = ($sitemapConfig['config']['template'] ?? false) ?: 'Sitemap';
                return $this->view->render($template);
            }
            throw new InvalidConfigurationException('No valid provider set for ' . $sitemap, 1535578522);
        }

        throw new PropagateResponseException(
            GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
                $request,
                'No valid configuration found for sitemap ' . $sitemap
            ),
            1535578569
        );
    }

    private function getSettings(): array
    {
        $settings = [];
        foreach (array_keys($this->typoScriptConfiguration['userFunc.']['variables.'] ?? []) as $variableName) {
            if (!is_string($variableName) || substr($variableName, -1) === '.') {
                continue;
            }
            $settings[$variableName] = $this->contentObjectRenderer->cObjGetSingle(
                $this->typoScriptConfiguration['userFunc.']['variables.'][$variableName] ?? '',
                $this->typoScriptConfiguration['userFunc.']['variables.'][$variableName . '.'] ?? []
            );
        }

        return $settings;
    }
}