<?php

namespace Velis\Lang;

use Velis\Model\BaseModel;
use Velis\App;
use Velis\Lang;

/**
 * Language packages loader
 *
 * @author Michał Nosek <michal.nosek@velis.pl>
 * @author Olek Procki <olo@velis.pl>
 */
class Dictionary extends BaseModel
{
    const MOBILE  = 'mobile';
    const LAYOUT  = 'layout';
    const I18NEXT = 'i18next';
    const JSON    = 'json';

    /**
     * Build profile
     * @var string
     */
    protected $_profile;

    /**
     * Lang keys
     */
    protected $_keys;


    /**
     * Force to reload all files
     */
    protected $_force;


    /**
     * Full list of application languages
     * @var array
     */
    protected $_languages;


    /**
     * @var LangVariant[]
     */
    protected array $variants;


    /**
     * Constructor
     * @param string $profile
     * @param array $variants
     */
    public function __construct(string $profile, array $variants = [])
    {
        $this->_profile = $profile;
        $this->variants = $variants;

        switch ($profile) {
            case self::JSON:
            case self::MOBILE:
                if (!App::hasModule('Mobile')) {
                    return;
                }
                $this->_keys = $this->_readKeys(MODULE_PATH . 'Mobile/config/mobile-langs.conf');
                break;
            case self::LAYOUT:
                $this->_keys = [];
                foreach (App::getModuleDirs() as $module) {
                    $moduleLangsPath = MODULE_PATH . $module . '/config/langs.conf';
                    if (file_exists($moduleLangsPath)) {
                        $this->_keys = array_merge(
                            $this->_keys,
                            $this->_readKeys($moduleLangsPath)
                        );
                    }
                }
                $this->_keys = array_unique($this->_keys);
                break;

            case self::I18NEXT:
                $this->_keys = [];
                foreach (App::getModuleDirs() as $module) {
                    $moduleLangsPath = MODULE_PATH . $module . '/config/i18n.conf';
                    if (file_exists($moduleLangsPath)) {
                        $this->_keys[$module] = $this->_readKeys($moduleLangsPath);
                    }
                }
                break;
        }
    }


    /**
     * Read acronym list from $path
     * @param string $path
     * @return array
     * @throws Exception
     */
    private function _readKeys($path)
    {
        $acronymList = file($path);
        $acronymList = array_map('trim', $acronymList);
        $acronymList = array_unique(array_filter($acronymList));
        sort($acronymList);
        file_put_contents($path, implode("\n", $acronymList));

        if ($this->variants) {
            foreach ($acronymList as $acronym) {
                foreach ($this->variants as $variant) {
                    $overloaded = $variant->getOverloadedLangsMap();
                    $overloadedLang = $overloaded[$acronym];

                    if ($overloadedLang) {
                        $acronymList[] = $overloadedLang;
                    }
                }
            }
        }

        return $acronymList;
    }


    /**
     * Refreshes language files
     *
     * @param bool $force
     */
    public function refresh($force = false)
    {
        $this->_force = $force;
        $this->_languages = self::$_db->getAll('SELECT lang_id, name FROM app.lang_tab', null, 'lang_id');

        if ($this->_profile == self::I18NEXT) {
            foreach ($this->_keys as $module => $keys) {
                $this->_makeI18nextFile($module);
            }
        } elseif ($this->_profile == self::JSON) {
            foreach ($this->_languages as $lang => $name) {
                $this->_makeJsonFile($lang);
            }
        } else {
            // generate main file
            $this->_makeFile();

            // generate files for languages
            foreach ($this->_languages as $lang => $name) {
                $this->_makeFile($lang);
            }
        }
    }


    /**
     * Returns particular language folder path
     *
     * @param string $lang - language acro
     * @return string
     */
    private function _getLangPath($lang = null)
    {
        switch ($this->_profile) {
            case self::MOBILE:
                return MODULE_PATH . 'Mobile/res/js/app/nls/' . $lang;

            case self::LAYOUT:
                return ROOT_PATH . 'public/nls/' . $lang;
        }
    }


    /**
     * Returns particular language file path
     *
     * @param string $lang - language acro
     * @return string
     */
    private function _getLangFile($lang)
    {
        if ($lang) {
            return $this->_getLangPath($lang) . '/app_strings.js';
        } else {
            return $this->_getLangPath() . 'app_strings.js';
        }
    }


    /**
     * Returns line for javascript language file
     *
     * @param string $langKey - lang key acronym
     * @return string
     */
    private function _getLangString($langKey)
    {
        return '"' . $langKey . '": "' . Lang::get($langKey, true) . '",';
    }


    /**
     * Returns translation array for given $keys array
     * @param array $keys
     * @return array
     */
    private function _getTranslations($keys, $lang)
    {
        $translations = [];

        Lang::$onEmpty = Lang::BHVR_RETURN_EN;
        Lang::switchLanguage($lang, true, $this->_force);

        foreach ($keys as $langKey) {
            if ($langKey) {
                $translations[trim($langKey)] = str_replace(['"', "'"], ['”', '’'], Lang::get(trim($langKey), true));
            }
        }

        return $translations;
    }


    /**
     * Builds and writes file with translations
     * @param type $lang - language acro
     */
    private function _makeFile($lang = null)
    {
        $onEmpty  = Lang::$onEmpty;
        $language = Lang::getLanguage();

        if (!$this->_force && file_exists($this->_getLangFile($lang))) {
            return;
        }

        $content = "define(";

        $translations = $this->_getTranslations($this->_keys, $lang);
        if (!$lang) {
            $jsonArray = ['root' => $translations];
        } else {
            $jsonArray = $translations;
        }

        if (!$lang) {
            $languages = ($this->_force) ? $this->_languages : Lang::getLanguages();
            foreach ($languages as $langKey => $name) {
                $jsonArray[$langKey] = true;
            }
        }

        $content .= json_encode($jsonArray, JSON_HEX_APOS) . ');';

        if (!file_exists($this->_getLangPath($lang))) {
            mkdir($this->_getLangPath($lang));
        }

        file_put_contents($this->_getLangFile($lang), $content);

        Lang::$onEmpty = $onEmpty;
        Lang::switchLanguage($language);
    }


    /**
     * Builds i18next file for given $module
     * @param string $module
     */
    private function _makeI18nextFile($module)
    {
        $onEmpty  = Lang::$onEmpty;
        $language = Lang::getLanguage();
        $content  = '';

        foreach ($this->_languages as $lang => $name) {
            $content .= "$lang: {
                translations: " . json_encode($this->_getTranslations($this->_keys[$module], $lang), JSON_HEX_APOS) . "
            },";
        }

        $outputDir = ROOT_PATH . 'public/nls/i18n/';
        if (!file_exists($outputDir)) {
            mkdir($outputDir);
        }

        file_put_contents(
            $outputDir . '/' . $module . '.js',
            'export default ({' . trim($content, ',') . '})'
        );

        Lang::$onEmpty = $onEmpty;
        Lang::switchLanguage($language);
    }


    /**
     * Builds and writes JSON file with translations
     * @param String $lang
     */
    private function _makeJsonFile($lang)
    {
        $onEmpty = Lang::$onEmpty;
        $language = Lang::getLanguage();
        $outputDir = ROOT_PATH . 'public/nls/json/';

        $translations = $this->_getTranslations($this->_keys, $lang);


        if (!file_exists($outputDir)) {
            mkdir($outputDir);
        }

        file_put_contents($outputDir . $lang . '.json', json_encode($translations, JSON_HEX_APOS));


        Lang::$onEmpty = $onEmpty;
        Lang::switchLanguage($language);
    }
}
