/*
 * This file is part of the Amalthea library.
 * Copyright (C) 2018-2022, 2024, 2026 Eugene 'Vindex' Stulin
 * Distributed under the BSL 1.0 or the GNU LGPL 3.0 or later.
 */

/**
 * The module implements the simplest system
 * for creating language localizations.
 */
module amalthea.langlocal;

import std.array;
import std.file : readText;
import std.json;
import std.process;

public import amalthea.libcore;
import amalthea.csv;


static string[][] localeStrings;
static string[string] langconformity;
static string currentLanguage = "en_US";


/*******************************************************************************
 * Current system language.
 */
string getSystemLanguage() {
    auto lang = environment.get("LANG", "en_US.UTF-8");
    return lang.split(".")[0];
}


/*******************************************************************************
 * Current application language by langlocal settings.
 */
string getCurrentLanguage() {
    return currentLanguage;
}


/*******************************************************************************
 * Initializes localization by a two-dimensional array of strings.
 * Titles of columns (first line - index 0) must be locales
 * in Linux style without encoding (en_US, en_GB, eo, fr_CA, ru_RU, etc.),
 * first locale is en_US always.
 *
 * Params:
 *     stringsWithLocalizations = Two-dimensional array containing columns
 *                                of strings in different languages.
 *     language = New current language (system language by default).
 *
 */
void initLocalization(
    in string[][] stringsWithLocalizations,
    string language = ""
) {
    localeStrings = cast(string[][])stringsWithLocalizations;
    if (localeStrings[0][0] != "en_US") {
        string errMsg = "Error: array in cell [0][0] must consist en_US";
        throw new LocalizationsTableException(errMsg);
    }
    if (language == "") {
        language = getSystemLanguage;
    }
    chooseLanguage(language);
}


/*******************************************************************************
 * Initializes localization by a CSV file with translations.
 * Titles of columns (first line - index 0) must be locales in Linux style
 * without encoding (en_US, en_GB, eo, fr_CA, ru_RU, etc.),
 * first locale is en_US always.
 *
 * Params:
 *     csvPath = A path to a CSV file with translations.
 *     language = A new current language (system language by default).
 *
 */
void initLocalizationWithCSV(string csvPath, string language = "") {
    auto csv = CSV(csvPath);
    initLocalization(csv.getTable(), language);
}
deprecated void initLocalization(string csvPath, string language = "") {
    initLocalizationWithCSV(csvPath, language);
}


/*******************************************************************************
 * Initializes localization by a JSON file with translations.
 * The JSON file must contain array of objects with languages as keys and with
 * text phrases as values.
 * Example: [{"en_US": "One", "eo": "Unu"},{"en_US": "Two", "eo": "Du"}]
 * Translations must contain "en_US" always.
 *
 * Params:
 *     jsonPath = A path to a JSON file with translations.
 *     language = A new current language (system language by default).
 */
void initLocalizationWithJSON(string jsonPath, string language = "") {
    JSONValue json = parseJSON(readText(jsonPath));
    return initLocalizationWithJSON(json, language);
}


/*******************************************************************************
 * Initializes localization by a std.json.JSON object with translations.
 * The JSON object must contain array of objects with languages as keys and with
 * text phrases as values.
 * Example: [{"en_US": "One", "eo": "Unu"},{"en_US": "Two", "eo": "Du"}]
 * Translations must contain "en_US" always.
 *
 * Params:
 *     json     = Filled JSON object with translations.
 *     language = A new current language (system language by default).
 */
void initLocalizationWithJSON(std.json.JSONValue json, string language = "") {
    size_t[string] langIndexes;
    size_t counter;
    foreach(key, _; json.array[0].object) {
        counter++;
        langIndexes[key] = counter;
    }
    if ("en_US" !in langIndexes) {
        auto errMsg = `Error: translations must consist "en_US"`;
        throw new LocalizationsTableException(errMsg);
    } else {
        langIndexes.remove("en_US");
        langIndexes["en_US"] = 0;  // "en_US" always in zero cell
    }
    string[][] table;
    string[] languages = new string[langIndexes.length];
    foreach(langId, index; langIndexes) {
        languages[index] = langId;
    }
    table = [languages];
    foreach(JSONValue elem; json.array) {
        string[] row = new string[langIndexes.length];
        foreach(lang, text; elem.object) {
            if (lang !in langIndexes) {
                auto msg = "Unexpected language: " ~ lang;
                throw new LocalizationsTableException(msg);
            }
            row[langIndexes[lang]] = text.str;
        }
        table ~= row;
    }
    initLocalization(table, language);
}


/*******************************************************************************
 * This function allows to choose current locale for application.
 */
void chooseLanguage(string language) {
    if (localeStrings.empty) return;
    foreach(i, locale; localeStrings[0]) {
        if (language != locale) continue;
        currentLanguage = language;
        size_t currentTableColumn = i;
        foreach(row; localeStrings) {
            if (row[currentTableColumn] == "") {
                langconformity[row[0]] = row[0];
            } else {
                langconformity[row[0]] = row[currentTableColumn];
            }
        }
        return;
    }
    currentLanguage = "en_US";
}


/*******************************************************************************
 * This function returns string corresponding to the English (en_US) version,
 * according to the current localization selected.
 */
string s_(string englishString) {
    return langconformity.get(englishString, englishString);
}
alias _s = s_;


unittest {
    initLocalization([
        ["en_US",    "ru_RU",     "eo"],
        ["Hello",    "Привет",    "Saluton"],
        ["Orange",   "Апельсин",  "Oranĝo"],
        ["Language", "Язык",      "Lingvo"],
        ["Computer", "Компьютер", "Komputilo"]
    ]);

    chooseLanguage("ru_RU");
    assert(s_("Orange") == "Апельсин");
    assert("Untranslated string"._s == "Untranslated string");
    assert("Other text" == "Other text".s_);

    chooseLanguage("eo");
    assert("Language"._s == "Lingvo");
    chooseLanguage("Quenya");  // invalid
    assert(getCurrentLanguage == "en_US");
}


/*******************************************************************************
 * Exception for incorrect localization tables.
 */
class LocalizationsTableException : Exception {
    this(string msg) {
        super(msg);
    }
}
