<?php

namespace Opentalent\OtTemplating\ViewHelpers;

use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\Exception;

/**
 *   This view helper is a wrapper for the Fluid/CObjectViewhelper class
 *   that allow to override the TS setup of the object by given variables
 *
 *   It accepts the same arguments as \TYPO3\CMS\Fluid\ViewHelpers\CObjectViewHelper,
 *   and an additional argument 'settings'
 *
 *   @see https://docs.typo3.org/p/georgringer/news/master/en-us/Introduction/Index.html
 *
 *   example:
 *
 *     {namespace ot=Opentalent\OtTemplating\ViewHelpers}
 *
 *     <ot:cObject typoscriptObjectPath="lib.tx_ottemplating.widgets.news_list"
 *                 settings="{'settings.defaultPageUid': 1}" />
 *
 *
 * @package Opentalent\OtTemplating\ViewHelpers
 */
class CObjectViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\CObjectViewHelper {

    /**
     * Initialize arguments.
     *
     * @throws Exception
     */
    public function initializeArguments()
    {
        parent::initializeArguments();
        $this->registerArgument(
            'settings',
            'array',
            'For each key in this array, the setup entry will be replaced by the corresponding value',
            false,
            []
        );
    }

    /**
     * <!> This is a copy/paste of the parent method, slighly modified
     * to override the $setup variable with the 'settings' argument
     *
     * Renders the TypoScript object in the given TypoScript setup path.
     *
     * @param array $arguments
     * @param \Closure $renderChildrenClosure
     * @param RenderingContextInterface $renderingContext
     * @return mixed
     * @throws Exception
     */
    public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
    {
        $data = $renderChildrenClosure();
        $typoscriptObjectPath = $arguments['typoscriptObjectPath'];
        $currentValueKey = $arguments['currentValueKey'];
        $table = $arguments['table'];
        $contentObjectRenderer = static::getContentObjectRenderer($renderingContext->getRequest());
        if (!isset($GLOBALS['TSFE']) || !($GLOBALS['TSFE'] instanceof TypoScriptFrontendController)) {
            static::simulateFrontendEnvironment();
        }
        $currentValue = null;
        if (is_object($data)) {
            $data = ObjectAccess::getGettableProperties($data);
        } elseif (is_string($data) || is_numeric($data)) {
            $currentValue = (string)$data;
            $data = [$data];
        }
        $contentObjectRenderer->start($data, $table);
        if ($currentValue !== null) {
            $contentObjectRenderer->setCurrentVal($currentValue);
        } elseif ($currentValueKey !== null && isset($data[$currentValueKey])) {
            $contentObjectRenderer->setCurrentVal($data[$currentValueKey]);
        }
        $pathSegments = GeneralUtility::trimExplode('.', $typoscriptObjectPath);
        $lastSegment = array_pop($pathSegments);
        $setup = static::getConfigurationManager()->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT);
        foreach ($pathSegments as $segment) {
            if (!array_key_exists($segment . '.', $setup)) {
                throw new Exception(
                    'TypoScript object path "' . $typoscriptObjectPath . '" does not exist',
                    1253191023
                );
            }
            $setup = $setup[$segment . '.'];
        }
        if (!isset($setup[$lastSegment])) {
            throw new Exception(
                'No Content Object definition found at TypoScript object path "' . $typoscriptObjectPath . '"',
                1540246570
            );
        }

        // <---- Added by Opentalent to override the TS setup
        $setup = self::evalConfiguration($setup, $lastSegment, $arguments['settings']);
        // ----->

        $content = self::renderContentObject($contentObjectRenderer, $setup, $typoscriptObjectPath, $lastSegment);
        if (!isset($GLOBALS['TSFE']) || !($GLOBALS['TSFE'] instanceof TypoScriptFrontendController)) {
            static::resetFrontendEnvironment();
        }
        return $content;
    }

    /**
     * Recursively replace any {$var} value in the given configuration
     * if 'var' is a key in the 'overrideSetup' array
     *
     * @param array $setup
     * @param string $entry_name
     * @param array $override_settings
     * @return array
     */
    private static function evalConfiguration(array $setup, string $entry_name, array $override_settings) {
        $entry_setup = $setup[$entry_name . '.'];

        foreach ($override_settings as $key => $val) {
            $override = [];
            $path = explode('.', $key);

            foreach (array_reverse($path) as $i => $segment) {
                if ($i == 0) {
                    $override[$segment] = $val;
                } else {
                    $override = [$segment . '.' => $override];
                }
            }
            $entry_setup = self::merge($entry_setup, $override);
        }

        $setup[$entry_name . '.'] = $entry_setup;
        return $setup;
    }

    /**
     * Similar to array_merge_recursive, except that the end nodes of the
     * base array is replaced and not merged.
     *
     * @param $base_array
     * @param $override
     * @return mixed
     */
    private static function merge($base_array, $override) {
        foreach ($base_array as $key => $val) {
            if (!isset($override[$key])) {
                continue;
            }
            if (is_array($val)) {
                $base_array[$key] = self::merge($val, $override[$key]);
            } else {
                $base_array[$key] = $override[$key];
            }
        }
        return $base_array;
    }
}
