<?php

namespace Shaarli;

use Gettext\GettextTranslator;
use Gettext\Translations;
use Gettext\Translator;
use Gettext\TranslatorInterface;
use Shaarli\Config\ConfigManager;

/**
 * Class Languages
 *
 * Load Shaarli translations using 'gettext/gettext'.
 * This class allows to either use PHP gettext extension, or a PHP implementation of gettext,
 * with a fixed language, or dynamically using autoLocale().
 *
 * Translation files PO/MO files follow gettext standard and must be placed under:
 *   <translation path>/<language>/LC_MESSAGES/<domain>.[po|mo]
 *
 * Pros/cons:
 *   - gettext extension is faster
 *   - gettext is very system dependent (PHP extension, the locale must be installed, and web server reloaded)
 *
 * Settings:
 *   - translation.mode:
 *     - auto: use default setting (PHP implementation)
 *     - php: use PHP implementation
 *     - gettext: use gettext wrapper
 *   - translation.language:
 *     - auto: use autoLocale() and the language change according to user HTTP headers
 *     - fixed language: e.g. 'fr'
 *   - translation.extensions:
 *     - domain => translation_path: allow plugins and themes to extend the defaut extension
 *       The domain must be unique, and translation path must be relative, and contains the tree mentioned above.
 *
 * @package Shaarli
 */
class Languages
{
    /**
     * Core translations domain
     */
    public const DEFAULT_DOMAIN = 'shaarli';

    /**
     * @var TranslatorInterface
     */
    protected $translator;

    /**
     * @var string
     */
    protected $language;

    /**
     * @var ConfigManager
     */
    protected $conf;

    /**
     * Languages constructor.
     *
     * @param string        $language lang determined by autoLocale(), can be overridden.
     * @param ConfigManager $conf     instance.
     */
    public function __construct($language, $conf)
    {
        $this->conf = $conf;
        $confLanguage = $this->conf->get('translation.language', 'auto');
        // Auto mode or invalid parameter, use the detected language.
        // If the detected language is invalid, it doesn't matter, it will use English.
        if ($confLanguage === 'auto' || ! $this->isValidLanguage($confLanguage)) {
            $this->language = substr($language, 0, 5);
        } else {
            $this->language = $confLanguage;
        }

        if (
            ! extension_loaded('gettext')
            || in_array($this->conf->get('translation.mode', 'auto'), ['auto', 'php'])
        ) {
            $this->initPhpTranslator();
        } else {
            $this->initGettextTranslator();
        }

        // Register default functions (e.g. '__()') to use our Translator
        $this->translator->register();
    }

    /**
     * Initialize the translator using php gettext extension (gettext dependency act as a wrapper).
     */
    protected function initGettextTranslator()
    {
        $this->translator = new GettextTranslator();
        $this->translator->setLanguage($this->language);
        $this->translator->loadDomain(self::DEFAULT_DOMAIN, 'inc/languages');

        // Default extension translation from the current theme
        $themeTransFolder = rtrim($this->conf->get('raintpl_tpl'), '/') . '/' . $this->conf->get('theme') . '/language';
        if (is_dir($themeTransFolder)) {
            $this->translator->loadDomain($this->conf->get('theme'), $themeTransFolder, false);
        }

        foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) {
            if ($domain !== self::DEFAULT_DOMAIN) {
                $this->translator->loadDomain($domain, $translationPath, false);
            }
        }
    }

    /**
     * Initialize the translator using a PHP implementation of gettext.
     *
     * Note that if language po file doesn't exist, errors are ignored (e.g. not installed language).
     */
    protected function initPhpTranslator()
    {
        $this->translator = new Translator();
        $translations = new Translations();
        // Core translations
        try {
            $translations = $translations->addFromPoFile(
                'inc/languages/' . $this->language . '/LC_MESSAGES/shaarli.po'
            );
            $translations->setDomain('shaarli');
            $this->translator->loadTranslations($translations);
        } catch (\InvalidArgumentException $e) {
        }

        // Default extension translation from the current theme
        $theme = $this->conf->get('theme');
        $themeTransFolder = rtrim($this->conf->get('raintpl_tpl'), '/') . '/' . $theme . '/language';
        if (is_dir($themeTransFolder)) {
            try {
                $translations = Translations::fromPoFile(
                    $themeTransFolder . '/' . $this->language . '/LC_MESSAGES/' . $theme . '.po'
                );
                $translations->setDomain($theme);
                $this->translator->loadTranslations($translations);
            } catch (\InvalidArgumentException $e) {
            }
        }

        // Extension translations (plugins, themes, etc.).
        foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) {
            if ($domain === self::DEFAULT_DOMAIN) {
                continue;
            }

            try {
                $extension = Translations::fromPoFile(
                    $translationPath . $this->language . '/LC_MESSAGES/' . $domain . '.po'
                );
                $extension->setDomain($domain);
                $this->translator->loadTranslations($extension);
            } catch (\InvalidArgumentException $e) {
            }
        }
    }

    /**
     * Checks if a language string is valid.
     *
     * @param string $language e.g. 'fr' or 'en_US'
     *
     * @return bool true if valid, false otherwise
     */
    protected function isValidLanguage($language)
    {
        return preg_match('/^[a-z]{2}(_[A-Z]{2})?/', $language) === 1;
    }

    /**
     * Get the list of available languages for Shaarli.
     *
     * @return array List of available languages, with their label.
     */
    public static function getAvailableLanguages()
    {
        return [
            'auto' => t('Automatic'),
            'de' => t('German'),
            'en' => t('English'),
            'fr' => t('French'),
            'jp' => t('Japanese'),
            'ru' => t('Russian'),
            'zh_CN' => t('Chinese (Simplified)'),
        ];
    }
}
