const libFS = require('fs');
const libUtil = require('util');

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

    class Sitemap {
      constructor() {
        this._site             = fcf.trim(fcf.application.getConfiguration().site, "/");
        this._languages        = fcf.application.getSystemVariable("fcf:defaultLanguage");
        this._sitemapDirectory = ":sitemap";
        this._sitemapFile      = "sitemap.xml";
      }

      initializeCronTasks(){
        fcf.application.getCron().append("Sitemap update", "0 */1 * * *", {hidden: true}, async ()=>{
          let directory = fcf.getPath(this._sitemapDirectory);
          let file      = directory+"/"+this._sitemapFile

          let sitemapSettings = fcf.application.getSystemVariable("fcf:sitemapSettings");
          if (!sitemapSettings)
            return;
          if (!sitemapSettings.enableAutoUpdate)
            return;

          let needRun = false;

          try {
            let stat = await libUtil.promisify(libFS.stat)(file);
            let feq  = 24;
            if (sitemapSettings && typeof sitemapSettings.autoUpdateDays == "number" && typeof sitemapSettings.autoUpdateHours == "number")
              feq = sitemapSettings.autoUpdateDays * 24 + sitemapSettings.autoUpdateHours;
            feq *= 60*60*1000;
            let diff = (new Date()).getTime() - stat.mtime.getTime();
            if (diff >= (feq-(5*60*1000)))
              needRun = true;
          } catch(e){
          }

          if (!needRun)
            return;

          fcf.log.log("FCF:CRON", "Update sitemap file");
          let sitemap = new Namespace.Sitemap();
          sitemap.writeSitemapFile();
        });
      }

      async writeSitemapFile() {
        let directory = fcf.getPath(this._sitemapDirectory);
        let exists = await libUtil.promisify(libFS.exists)(directory);
        if (!exists)
          await libUtil.promisify(libFS.mkdir)(directory);
        let data = await this.buildSitemapData();
        await libUtil.promisify(libFS.writeFile)(directory+"/"+this._sitemapFile, data);
      }

      async buildSitemapData(){
        let sitemapSettings         = fcf.application.getSystemVariable("fcf:sitemapSettings");
        let languageIdentification  = fcf.application.getSystemVariable("fcf:languageIdentification");
        let languages               = fcf.application.getSystemVariable("fcf:languages");
        let a_enableMultilingual    = this.getMultilingual();
        let items                   = await this.getItems();

        let result = "";
        result += '<?xml version="1.0"?>\n';
        result += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n';
        result += this._buildSitemapUrlsString(items, a_enableMultilingual, languages, languageIdentification.byPrefix, languageIdentification.parameter);
        if (!fcf.empty(sitemapSettings.additionalSitemapItems))
          result += this._buildSitemapUrlsString(sitemapSettings.additionalSitemapItems, a_enableMultilingual, languages, languageIdentification.byPrefix, languageIdentification.parameter);
        result += '</urlset>\n';
        return result;
      }

      getMultilingual(){
        if (!this.resulutionMultilingual())
          return false;
        let sitemapSettings = fcf.application.getSystemVariable("fcf:sitemapSettings");
        return !!sitemapSettings.multilingual;
      }

      resulutionMultilingual() {
        let languages = fcf.application.getSystemVariable("fcf:languages");
        if (fcf.count(languages) < 2)
          return false;

        let languageIdentification = fcf.application.getSystemVariable("fcf:languageIdentification");
        return languageIdentification &&
              (languageIdentification.byPrefix || (languageIdentification.byParameter && !fcf.empty(languageIdentification.parameter)));
      }

      _buildSitemapUrlsString(a_items, a_enableMultilingual, a_languages, a_enableByPrefix, a_parameterName){
        let result = "";
        fcf.each(a_items, (k, a_item)=>{
          if (!a_item.enable)
            return;
          if (!a_enableMultilingual || !a_item.multilingual) {
            result += '  <url>\n';
            result += '    <loc>'      + a_item.url + '</loc>\n';
            result += '    <priority>' + a_item.priority + '</priority>\n';
            result += '    <changefreq>' + a_item.changefreq + '</changefreq>\n';
            result += '  </url>\n';
          } else {
            fcf.each(a_languages, (a_lang)=>{
              let url = a_item.url;
              if (a_enableByPrefix){
                let ppos = url.indexOf("://");
                let spos = ppos !== -1 ? url.indexOf("/", ppos+3) : -1;
                if (spos != -1){
                  url = url.substring(0,spos+1) + a_lang + "/" + url.substring(spos+1);
                } else {
                  url = url[0] == "/" ? "/" + a_lang + url : a_lang + "/" + url;
                }
              } else {
                url = url.indexOf("?") != -1 ? url + "&" + a_parameterName + "=" + a_lang :
                                               url + "?" + a_parameterName + "=" + a_lang;
              }
              result += '  <url>\n';
              result += '    <loc>'      + url + '</loc>\n';
              result += '    <priority>' + a_item.priority + '</priority>\n';
              result += '    <changefreq>' + a_item.changefreq + '</changefreq>\n';
              result += '  </url>\n';
            })
          }
        })
        return result;
      }

      async getItems() {
        let menuTree;
        let context = fcf.getContext();
        try {
          fcf.setContext(fcf.application.createSystemContext(["root"], ["root", "sitemap"]));
          menuTree = await fcf.application.getRouter().getMenuNode("/", true, false);
          fcf.setContext(context);
        } catch(e){
          fcf.setContext(context);
          throw e;
        }
        let list     = this._menuTreeToList(menuTree);
        let sitemapSettings = await fcf.application.getSystemVariable("fcf:sitemapSettings");
        if (sitemapSettings && Array.isArray(sitemapSettings.sitemapItems))
          list = this._merge(list, sitemapSettings.sitemapItems);
        return list;
      }

      _merge(a_realList, a_configList){
        let configMap = fcf.map(a_configList, (k, a_item)=>{ return [a_item.url, a_item]});
        fcf.each(a_realList, (k, a_item)=>{
          if (a_item.url in configMap){
            let confItem = configMap[a_item.url];

            a_item.priorityDefault = a_item.priority;
            a_item.priorityDefaultFlag = confItem.priorityDefaultFlag;
            if (!confItem.priorityDefaultFlag)
              a_item.priority = confItem.priority;

            a_item.multilingualDefault = a_item.multilingual;
            a_item.multilingualDefaultFlag = confItem.multilingualDefaultFlag;
            if (!confItem.multilingualDefaultFlag)
              a_item.multilingual = confItem.multilingual;

            a_item.enable = confItem.enable;

            a_item.changefreqDefault = a_item.changefreq;
            a_item.changefreqDefaultFlag = confItem.changefreqDefaultFlag;
            if (!confItem.changefreqDefaultFlag)
              a_item.changefreq = confItem.changefreq;
          }
        })
        return a_realList;
      }

      _menuTreeToList(a_menuTree, a_dstlist){
        let self = this;
        if (!a_dstlist)
          a_dstlist = [];
        let sitemapSettings   = fcf.application.getSystemVariable("fcf:sitemapSettings");
        let priorityDefault   = sitemapSettings && !isNaN(sitemapSettings.priorityDefault) ? sitemapSettings.priorityDefault : 0.5;
        let changefreqDefault = sitemapSettings && sitemapSettings.changefreqDefault ? sitemapSettings.changefreqDefault : "monthly";
        if (fcf.find(["always", "hourly", "daily", "weekly", "monthly", "yearly", "never"], changefreqDefault) === undefined)
          changefreqDefault = "monthly";
        let multilingualDefault = sitemapSettings ? !!sitemapSettings.multilingualDefault : true;

        if (this._menuItemFilter(a_menuTree)){
          let priorityCalc     = "priority" in a_menuTree.endpoint && !isNaN(a_menuTree.endpoint.priority);
          let multilingualCalc = "multilingual" in a_menuTree.endpoint;
          let changefreqCalc   = "changefreq" in a_menuTree.endpoint && fcf.find(["always", "hourly", "daily", "weekly", "monthly", "yearly", "never"], a_menuTree.endpoint.changefreq) !== undefined;

          let item = {
            url:              a_menuTree.uri[0] == "/" ? this._site + a_menuTree.uri : this._site + "/" + a_menuTree.uri,
            priority:         priorityCalc ? a_menuTree.endpoint.priority : priorityDefault,
            multilingual:     multilingualCalc ? !!a_menuTree.endpoint.multilingual : multilingualDefault,
            changefreq:       changefreqCalc ? a_menuTree.endpoint.changefreq : changefreqDefault,
            enable:           true,
          };
          item.priorityCalc = priorityCalc;
          item.priorityDefault = item.priority;
          item.priorityDefaultFlag = true;
          item.multilingualCalc = multilingualCalc;
          item.multilingualDefault = item.multilingual;
          item.multilingualDefaultFlag = true;
          item.changefreqCalc    = changefreqCalc;
          item.changefreqDefault = item.changefreq;
          item.changefreqDefaultFlag = true;

          a_dstlist.push(item);
        }

        if (a_menuTree.endpoint && (a_menuTree.endpoint.ignoreSiteMap == "all" || a_menuTree.endpoint.ignoreSiteMap == "*"))
          return a_dstlist;

        fcf.each(a_menuTree.childs, (a_key, a_child)=>{
          self._menuTreeToList(a_child, a_dstlist);
        })
        return a_dstlist;
      }

      _menuItemFilter(a_menuItem){
        if (!a_menuItem.endpoint)
          return false;
        if (a_menuItem.endpoint.controller != "fcf:NServer/NControllers/Page.js")
          return false;
        if (a_menuItem.endpoint.ignoreSiteMap)
          return false;
        return true;
      }
    };

    Namespace.Sitemap = Sitemap;

    return Namespace.Sitemap;
  }
});
