if (fcf.isServer()) {
  var libFS = require("fs");
  var libPath = require("path");
  var libUtil = require("util");
}

fcf.module({
  name: "fcf:NSystem/languages.js",
  dependencies: [],
  module: function(){
    var Namespace = fcf.prepareObject(fcf, "NTools");

    Namespace.Languages = function(){
      var self = this;

      this.setLanguages = function(a_value){
        a_value = fcf.empty(a_value) ? {en: "English"} : a_value;
        return fcf.application.setSystemVariable("fcf:translations", a_value);
      }

      this.getLanguages = function(){
        let languages = fcf.application.getSystemVariable("fcf:languages");
        return fcf.empty(languages) ? {en: "English"} : languages;
      }

      this.getAllLanguages = function(){
        return {
            // ISO 639-1
            ab: "Caucasian",
            aa: "Afar",
            af: "Afrikaans",
            ak: "Akan",
            sq: "Albanian",
            am: "Amharic",
            ar: "Arabic",
            an: "Aragonese",
            hy: "Armenian",
            as: "Assamese",
            av: "Avaric",
            ae: "Avestan",
            ay: "Aymara",
            az: "Azerbaijani",
            bm: "Bambara",
            ba: "Bashkir",
            eu: "Basque",
            be: "Belarusian",
            bn: "Bengali",
            bh: "Bihari languages",
            bi: "Bislama",
            bs: "Bosnian",
            br: "Breton",
            bg: "Bulgarian",
            my: "Burmese",
            ca: "Catalan",
            km: "Central Khmer",
            ch: "Chamorro",
            ce: "Caucasian",
            ny: "Chichewa",
            zh: "Chinese",
            cu: "Church Slavic",
            cv: "Chuvash",
            kw: "Cornish",
            co: "Corsican",
            cr: "Cree",
            hr: "Croatian",
            cs: "Czech",
            da: "Danish",
            dv: "Divehi",
            nl: "Dutch",
            dz: "Dzongkha",
            en: "English",
            eo: "Esperanto",
            et: "Estonian",
            ee: "Ewe",
            fo: "Faroese",
            fj: "Fijian",
            fi: "Finnish",
            fr: "French",
            ff: "Fulah",
            gd: "Gaelic",
            gl: "Galician",
            lg: "Ganda",
            ka: "Georgian",
            de: "German",
            el: "Greek",
            gn: "Guarani",
            gu: "Gujarati",
            ht: "Haitian",
            ha: "Hausa",
            he: "Hebrew",
            hz: "Herero",
            hi: "Hindi",
            ho: "Hiri Motu",
            hu: "Hungarian",
            is: "Icelandic",
            io: "Ido",
            ig: "Igbo",
            id: "Indonesian",
            ia: "Interlingua",
            ie: "Interlingue",
            iu: "Inuktitut",
            ik: "Inupiaq",
            ga: "Irish",
            it: "Italian",
            ja: "Japanese",
            jv: "Javanese",
            kl: "Kalaallisut",
            kn: "Kannada",
            kr: "Kanuri",
            ks: "Kashmiri",
            kk: "Kazakh",
            ki: "Kikuyu",
            rw: "Kinyarwanda",
            ky: "Kirghiz",
            kv: "Komi",
            kg: "Kongo",
            ko: "Korean",
            kj: "Kuanyama",
            ku: "Kurdish",
            lo: "Lao",
            la: "Latin",
            lv: "Latvian",
            li: "Limburgan",
            ln: "Lingala",
            lt: "Lithuanian",
            lu: "Luba-Katanga",
            lb: "Luxembourgish",
            mk: "Macedonian",
            mg: "Malagasy",
            ms: "Malay",
            ml: "Malayalam",
            mt: "Maltese",
            gv: "Manx",
            te: "Maori",
            mr: "Marathi",
            mh: "Marshallese",
            mn: "Mongolian",
            na: "Nauru",
            nv: "Navajo",
            ng: "Ndonga",
            ne: "Nepali",
            nd: "North Ndebele",
            se: "Northern Sami",
            no: "Norwegian",
            nb: "Norwegian Bokmål",
            nn: "Norwegian Nynorsk",
            oc: "Occitan",
            oj: "Ojibwa",
            or: "Oriya",
            om: "Oromo",
            os: "Ossetian",
            pi: "Pali",
            ps: "Pashto",
            fa: "Persian",
            pl: "Polish",
            pt: "Portuguese",
            pa: "Punjabi",
            qu: "Quechua",
            ro: "Romanian",
            rm: "Romansh",
            rn: "Rundi",
            ru: "Russian",
            sm: "Samoan",
            sg: "Sango",
            sa: "Sanskrit",
            sc: "Sardinian",
            sr: "Serbian",
            sn: "Shona",
            ii: "Sichuan Yi",
            sd: "Sindhi",
            si: "Sinhala",
            sk: "Slovak",
            sl: "Slovenian",
            af: "Somali",
            nr: "South Ndebele",
            st: "Southern Sotho",
            es: "Spanish",
            su: "Sundanese",
            sw: "Swahili",
            ss: "Swati",
            sv: "Swedish",
            tl: "Tagalog",
            ty: "Tahitian",
            tg: "Tajik",
            ta: "Tamil",
            tt: "Tatar",
            te: "Telugu",
            th: "Thai",
            bo: "Tibetan",
            ti: "Tigrinya",
            to: "Tonga",
            ts: "Tsonga",
            tn: "Tswana",
            tr: "Turkish",
            tk: "Turkmen",
            tw: "Twi",
            ug: "Uighur",
            uk: "Ukrainian",
            ur: "Urdu",
            uz: "Uzbek",
            ve: "Venda",
            vi: "Vietnamese",
            vo: "Volapük",
            wa: "Walloon",
            cy: "Welsh",
            fy: "Western Frisian",
            wo: "Wolof",
            xh: "Xhosa",
            yi: "Yiddish",
            yo: "Yoruba",
            za: "Zhuang",
            zu: "Zulu",
          };
      }


      this.updateTranslations = function() {
        let result = {};
        let files  = [];
        return fcf.actions()
        .then(()=>{
          return fcf.NTools.languages.getTranslationFiles()
        })
        .then((a_files) => {
          files = a_files;
          fcf.each(fcf.NTools.languages.getLanguages(), (a_lang) => {
            result[a_lang] = {};
          });
        })
        .each(()=>{return files}, (a_key, a_fileInfo, a_res, a_act)=>{
          return fcf.load(a_fileInfo.path)
          .then((a_data)=>{
            let object = JSON.parse(a_data);
            if (!(object.language in result))
              return;
            fcf.each(object.translations, (a_phrase, a_translate)=>{
              result[object.language][a_phrase] = a_translate;
            });
          })
          .finally(()=>{
            a_act.complete();
          })
        })
        .then(()=>{
          return result;
        })
      }

      this.getTranslationsEx = function() {
        let result = {};
        let files  = [];
        return fcf.actions()
        .then(()=>{
          return fcf.NTools.languages.getTranslationFiles()
        })
        .then((a_files) => {
          files = a_files;
          fcf.each(fcf.NTools.languages.getLanguages(), (a_lang) => {
            result[a_lang] = {};
          });
        })
        .each(()=>{return files}, (a_key, a_fileInfo, a_res, a_act) => {
          return fcf.load(a_fileInfo.path)
          .then((a_data)=>{
            let object = JSON.parse(a_data);
            if (!(object.language in result))
              return;
            fcf.each(object.translations, (a_phrase, a_translate)=>{
              result[object.language][a_phrase] = {translate: a_translate, path: a_fileInfo.path};
            });
          })
          .finally(()=>{
            a_act.complete();
          })
        })
        .then(()=>{
          for(let language in result) {
            let counter = Math.floor(Number.MAX_SAFE_INTEGER/4);
            for(let phrase in result[language]) {
              result[language][phrase].weight = counter;
              --counter;
            }
          }
          return result;
        })
      }
      this.getTranslationsByFiles = function() {
        let result = {};
        let files  = [];
        let order  = {};
        return this.getTranslationsEx()
        .then((a_order)=>{
          order = a_order;
        })
        .then(()=>{
          return fcf.NTools.languages.getTranslationFiles()
        })
        .then((a_files) => {
          files = a_files;
          fcf.each(fcf.NTools.languages.getLanguages(), (a_lang) => {
            result[a_lang] = {};
          });
        })
        .each(()=>{return files}, (a_key, a_fileInfo, a_res, a_act)=>{
          return fcf.load(a_fileInfo.path)
          .then((a_data)=>{
            let object = JSON.parse(a_data);
            if (!(object.language in result))
              return;
            result[object.language][a_fileInfo.path] = {};
            fcf.each(object.translations, (a_phrase, a_translate)=>{
              result[object.language][a_fileInfo.path][a_phrase] = {translate: a_translate, path: a_fileInfo.path, weight: order[object.language][a_phrase].weight};
            });
          })
          .finally(()=>{
            a_act.complete();
          })
        })
        .then(()=>{
          return result;
        })
      }

      this.getTranslationFiles = function() {
        let languages = this.getLanguages();
        let realFilesInfo = [];

        function clReadFile(a_file){
          return fcf.load(a_file)
          .then((a_data)=>{
            let data;
            try {
              data = JSON.parse(a_data);
            } catch (e) { }
            if (typeof data !== "object" || typeof data.translations !== "object" || typeof data.language !== "string")
              throw new Error("Incorrect format file: " + a_file);
            return data;
          });
        }

        function clReadDir(a_directory, a_isSystem){
          return fcf.actions()
          // loading fcf system files
          .then((a_res, a_act)=>{
            libFS.readdir(fcf.getPath(a_directory), (a_error, a_files) => {
              fcf.actions()
              .each(a_files, (k, a_file, a_res, a_act)=>{
                return clReadFile(a_directory + "/" + a_file)
                .then((a_fileData)=>{
                  if (a_fileData.language in languages){
                    realFilesInfo.push({
                      path: a_directory + "/" + libPath.basename(a_file),
                      language: a_fileData.language,
                      enable: true,
                      editable: false,
                      system: a_isSystem,
                    });
                  }
                  a_act.complete();
                })
                .catch(()=>{
                  a_act.complete();
                })
              })
              .then(()=>{
                a_act.complete();
              })
              .catch((a_error)=>{
                a_act.error(a_error);
              })
            })
          })
        };

        return fcf.actions()
        .then((a_res, a_act)=>{
          fcf.actions()
          // loading system files
          .each(fcf.application.getPackages(), (a_packName) => {
            if (!a_packName)
              return;
            return clReadDir(a_packName + ":translations", true);
          })
          .then(async ()=>{
            // prepare local files
            let languages = fcf.application.getSystemVariable("fcf:languages");
            for(let lang in languages) {
              let filePath   = fcf.getPath(`:translations/${lang}`);
              let fileExists = await libUtil.promisify(libFS.exists)(filePath);
              if (!fileExists){
                let fileData = { language: lang, translations: {} };
                await libUtil.promisify(libFS.writeFile)(filePath, JSON.stringify(fileData, false, 2), {encoding: 'utf8'});
              }
            }
          })
          .then(()=>{
            // loading local files
            return clReadDir(":translations", false);
          })
          .then(()=>{
            let resultFilesInfo = [];
            let memoryFilesInfo = fcf.application.getSystemVariable("fcf:languageFiles");

            fcf.each(memoryFilesInfo, (k, fileInfo) => {
              let realFileInfo = realFilesInfo[fcf.find(realFilesInfo, (k, v)=>{ return fileInfo.path == v.path; })];
              if (realFileInfo){
                resultFilesInfo.push({
                  path:     realFileInfo.path,
                  language: realFileInfo.language,
                  enable:   fileInfo.enable,
                  editable: fileInfo.editable,
                  system:   realFileInfo.system,
                });
              }

            });

            fcf.each(realFilesInfo, (k, fileInfo) => {
              let resultFileInfo = resultFilesInfo[fcf.find(resultFilesInfo, (k, v)=>{ return fileInfo.path == v.path })];
              if (!resultFileInfo)
                resultFilesInfo.push(fileInfo);
            });

            let languages = fcf.application.getSystemVariable("fcf:languages");
            fcf.each(languages, (language)=>{
              let exists = false;

              fcf.each(resultFilesInfo, (k, resultFileInfo)=>{
                if (resultFileInfo.language != language)
                  return;
                if (!exists){
                  if (resultFileInfo.editable)
                    exists = true;
                } else {
                  resultFileInfo.editable = false;
                }
              });

              if (exists)
                return;

              fcf.each(resultFilesInfo, (k, resultFileInfo)=>{
                if (resultFileInfo.language != language)
                  return;
                if (resultFileInfo.system)
                  return;
                resultFileInfo.editable = true;
                exists = true;
                return false;
              });

              if (exists)
                return;

              fcf.each(resultFilesInfo, (k, resultFileInfo)=>{
                if (resultFileInfo.language != language)
                  return;
                resultFileInfo.editable = true;
                return false;
              });


            });

            a_act.complete(resultFilesInfo);
          })
          .catch((a_error)=>{
            a_act.error(a_error);
          })
        });
      }
    }

    Namespace.languages = new Namespace.Languages();
    return Namespace.languages;
  }
});
