/**
 * Copyright JS Foundation and other contributors, http://js.foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * @ignore
 **/

/**
 * Internationalization utilities
 * @mixin @node-red/util_i18n
 */

var i18n = require("i18next");

var path = require("path");
var fs = require("fs");

var defaultLang = "en-US";

var resourceMap = {};
var resourceCache = {};
var initPromise;

/**
 * Register multiple message catalogs with i18n.
 * @memberof @node-red/util_i18n
 */
function registerMessageCatalogs(catalogs) {
    var promises = catalogs.map(function(catalog) {
        return registerMessageCatalog(catalog.namespace,catalog.dir,catalog.file).catch(err => {});
    });
    return Promise.all(promises);
}

/**
 * Register a message catalog with i18n.
 * @memberof @node-red/util_i18n
 */
async function registerMessageCatalog(namespace,dir,file) {
    return initPromise.then(function() {
        return new Promise((resolve,reject) => {
            resourceMap[namespace] = { basedir:dir, file:file, lngs: []};
            fs.readdir(dir,function(err, files) {
                if (err) {
                    resolve();
                } else {
                    files.forEach(function(f) {
                        if (fs.existsSync(path.join(dir,f,file))) {
                            resourceMap[namespace].lngs.push(f);
                        }
                    });
                    i18n.loadNamespaces(namespace,function() {
                        resolve();
                    });
                }
            })
        });
    });
}

function mergeCatalog(fallback,catalog) {
    for (var k in fallback) {
        if (fallback.hasOwnProperty(k)) {
            if (!catalog[k]) {
                catalog[k] = fallback[k];
            } else if (typeof fallback[k] === 'object') {
                mergeCatalog(fallback[k],catalog[k]);
            }
        }
    }
}

function migrateMessageCatalogV3toV4(catalog) {
    const keys = Object.keys(catalog)
    keys.forEach(key => {
        if (typeof catalog[key] === 'object') {
            catalog[key] = migrateMessageCatalogV3toV4(catalog[key])
        } else if (key.endsWith('_plural')) {
            const otherKey = key.replace('_plural', '_other')
            if (!catalog[otherKey]) {
                catalog[otherKey] = catalog[key]
            }
            delete catalog[key]
        }
    })
    return catalog
}

async function readFile(lng, ns) {
    if (/[^a-z\-]/i.test(lng)) {
        throw new Error("Invalid language: "+lng)
    }
    if (resourceCache[ns] && resourceCache[ns][lng]) {
        return resourceCache[ns][lng];
    } else if (resourceMap[ns]) {
        const file = path.join(resourceMap[ns].basedir, lng, resourceMap[ns].file);
        const content = await fs.promises.readFile(file, "utf8");
        resourceCache[ns] = resourceCache[ns] || {};
        resourceCache[ns][lng] = JSON.parse(content.replace(/^\uFEFF/, ''));

        // Message catalogues are in i18next v3 format. That is no longer supported
        // by i18next so we need to migrate any catalog to the v4 format.
        // This primarily means mapping `FOO_plural` to `FOO_other`
        resourceCache[ns][lng] = migrateMessageCatalogV3toV4(resourceCache[ns][lng])

        var baseLng = lng.split('-')[0];
        if (baseLng !== lng && resourceCache[ns][baseLng]) {
            mergeCatalog(resourceCache[ns][baseLng], resourceCache[ns][lng]);
        }
        if (lng !== defaultLang) {
            mergeCatalog(resourceCache[ns][defaultLang], resourceCache[ns][lng]);
        }
        return resourceCache[ns][lng];
    } else {
        throw new Error("Unrecognised namespace");
    }
}

var MessageFileLoader = {
    type: "backend",
    init: function (services, backendOptions, i18nextOptions) { },
    read: function (lng, ns, callback) {
        readFile(lng, ns)
            .then(data => callback(null, data))
            .catch(err => {
                if (/-/.test(lng)) {
                    // if reading language file fails -> try reading base language (e. g. 'fr' instead of 'fr-FR' or 'de' for 'de-DE')
                    var baseLng = lng.split('-')[0];
                    readFile(baseLng, ns).then(baseData => callback(null, baseData)).catch(err => callback(err));
                } else {
                    callback(err);
                }
            });
    }
}

function getCurrentLocale() {
    var env = process.env;
    for (var name of ['LC_ALL', 'LC_MESSAGES', 'LANG']) {
        if (name in env) {
            var val = env[name];
            return val.substring(0, 2);
        }
    }
    return undefined;
}

function init(settings) {
    if (!initPromise) {
        initPromise = new Promise((resolve,reject) => {
            i18n.use(MessageFileLoader);
            var opt = {
                // debug: true,
                defaultNS: "runtime",
                ns: [],
                fallbackLng: defaultLang,
                keySeparator: ".",
                nsSeparator: ":",
                interpolation: {
                    unescapeSuffix: 'HTML',
                    escapeValue: false,
                    prefix: '__',
                    suffix: '__'
                }
            };
            var lang = settings.lang || getCurrentLocale();
            if (lang) {
                opt.lng = lang;
            }
            i18n.init(opt ,function() {
                resolve();
            });
        });
    }
}


/**
 * Gets a message catalog.
 * @name catalog
 * @function
 * @memberof @node-red/util_i18n
 */
function getCatalog(namespace,lang) {
    var result = null;
    lang = lang || defaultLang;
    if (/[^a-z\-]/i.test(lang)) {
        throw new Error("Invalid language: "+lng)
    }

    if (resourceCache.hasOwnProperty(namespace)) {
        result = resourceCache[namespace][lang];
        if (!result) {
            var langParts = lang.split("-");
            if (langParts.length == 2) {
                result = resourceCache[namespace][langParts[0]];
            }
        }
    }
    return result;
}

/**
 * Gets a list of languages a given catalog is available in.
 * @name availableLanguages
 * @function
 * @memberof @node-red/util_i18n
 */
function availableLanguages(namespace) {
    if (resourceMap.hasOwnProperty(namespace)) {
        return resourceMap[namespace].lngs
    }
}

var obj = module.exports = {
    init: init,
    registerMessageCatalog: registerMessageCatalog,
    registerMessageCatalogs: registerMessageCatalogs,
    catalog: getCatalog,
    availableLanguages: availableLanguages,
    /**
     * The underlying i18n library for when direct access is really needed
     */
    i: i18n,
    /**
     * The default language of the runtime
     */
    defaultLang: defaultLang
}

/**
 * Perform a message catalog lookup.
 * @name _
 * @function
 * @memberof @node-red/util_i18n
 */
obj['_'] = function() {
    //var opts = {};
    //if (def) {
    //    opts.defaultValue = def;
    //}
    //console.log(arguments);
    var res = i18n.t.apply(i18n,arguments);
    return res;
}
