<?php

namespace SilverStripe\i18n\Tests;

use SilverStripe\Control\Director;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Manifest\ClassManifest;
use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Core\Manifest\ModuleLoader;
use SilverStripe\Core\Manifest\ModuleManifest;
use SilverStripe\i18n\i18n;
use SilverStripe\i18n\Messages\MessageProvider;
use SilverStripe\i18n\Messages\Symfony\ModuleYamlLoader;
use SilverStripe\i18n\Messages\Symfony\SymfonyMessageProvider;
use SilverStripe\i18n\Messages\YamlReader;
use SilverStripe\i18n\Tests\i18nTest\MyObject;
use SilverStripe\i18n\Tests\i18nTest\MySubObject;
use SilverStripe\i18n\Tests\i18nTest\TestDataObject;
use SilverStripe\View\SSViewer;
use SilverStripe\View\SSViewer_DataPresenter;
use SilverStripe\View\ThemeResourceLoader;
use SilverStripe\View\ThemeManifest;
use SilverStripe\View\ViewableData;
use Symfony\Component\Translation\Loader\ArrayLoader;
use Symfony\Component\Translation\Translator;

/**
 * Helper trait for bootstrapping test manifest for i18n tests
 */
trait i18nTestManifest
{
    /**
     * Fake webroot with a single module /i18ntestmodule which contains some files with _t() calls.
     *
     * @var string
     */
    protected $alternateBasePath;

    /**
     * Number of test manifests
     *
     * @var int
     */
    protected $manifests = 0;

    /**
     * Number of module manifests
     *
     * @var int
     */
    protected $moduleManifests = 0;

    public static function getExtraDataObjects()
    {
        return [
            TestDataObject::class,
            MyObject::class,
            MySubObject::class,
        ];
    }

    /**
     * @var ThemeResourceLoader
     */
    protected $oldThemeResourceLoader = null;

    /**
     * @var string
     */
    protected $originalLocale = null;

    /**
     * @skipUpgrade
     */
    public function setupManifest()
    {
        // force SSViewer_DataPresenter to cache global template vars before we switch to the
        // test-project class manifest (since it will lose visibility of core classes)
        $presenter = new SSViewer_DataPresenter(new ViewableData());
        unset($presenter);

        // Switch to test manifest
        $s = DIRECTORY_SEPARATOR;
        $this->alternateBasePath = __DIR__ . $s . 'i18nTest' . $s . "_fakewebroot";
        Director::config()->update('alternate_base_folder', $this->alternateBasePath);

        // New module manifest
        $moduleManifest = new ModuleManifest($this->alternateBasePath);
        $moduleManifest->init(true);
        $this->pushModuleManifest($moduleManifest);

        // Replace old template loader with new one with alternate base path
        $this->oldThemeResourceLoader = ThemeResourceLoader::inst();
        ThemeResourceLoader::set_instance($loader = new ThemeResourceLoader($this->alternateBasePath));
        $loader->addSet(
            '$default',
            $default = new ThemeManifest($this->alternateBasePath, project())
        );
        $default->init(true);

        SSViewer::set_themes([
            'testtheme1',
            '$default',
        ]);

        $this->originalLocale = i18n::get_locale();
        i18n::set_locale('en_US');

        // Set new manifest against the root
        $classManifest = new ClassManifest($this->alternateBasePath);
        $classManifest->init(true);
        $this->pushManifest($classManifest);

        // Setup uncached translator
        // This should pull the module list from the above manifest
        $translator = new Translator('en');
        $translator->setFallbackLocales(['en']);
        $loader = new ModuleYamlLoader();
        $loader->setReader(new YamlReader());
        $translator->addLoader('ss', $loader); // Standard ss module loader
        $translator->addLoader('array', new ArrayLoader()); // Note: array loader isn't added by default
        $provider = new SymfonyMessageProvider();
        $provider->setTranslator($translator);
        Injector::inst()->registerService($provider, MessageProvider::class);
    }

    public function tearDownManifest()
    {
        ThemeResourceLoader::set_instance($this->oldThemeResourceLoader);
        i18n::set_locale($this->originalLocale);

        // Reset any manifests pushed during this test
        $this->popManifests();
    }

    /**
     * Safely push a new class manifest.
     * These will be cleaned up on tearDown()
     *
     * @param ClassManifest $manifest
     */
    protected function pushManifest(ClassManifest $manifest)
    {
        $this->manifests++;
        ClassLoader::inst()->pushManifest($manifest);
    }

    protected function pushModuleManifest(ModuleManifest $manifest)
    {
        $this->moduleManifests++;
        ModuleLoader::inst()->pushManifest($manifest);
    }

    /**
     * Pop off all extra manifests
     */
    protected function popManifests()
    {
        // Reset any manifests pushed during this test
        while ($this->manifests > 0) {
            ClassLoader::inst()->popManifest();
            $this->manifests--;
        }
        while ($this->moduleManifests > 0) {
            ModuleLoader::inst()->popManifest();
            $this->moduleManifests--;
        }
    }
}