let libFS = require("fs");
var libNet = require('net');

fcf.module({
  name: "fcf:package.js",
  dependencies: [ "fcf:NSystem/NPackage/PackageHandler.js",
                  "fcf:NSystem/cache.js",
                  "fcf:NSystem/languages.js",
                  "fcf:NSystem/builder.js",
                  "fcf:NSystem/authorizationHandler.js",
                  "fcf:NSystem/Sitemap.js",
                  "fcf:NClient/mainClientInclude.js"
                ],
  lazy:         ["fcf:NServer/NControllers/FileCounter.js"],
  module: function(PackageHandler, cache, languages, builder, authorizationHandler, Sitemap, mainClientInclude) {
    class Package extends PackageHandler {

      async prepare(){
        let records = await fcf.application.getStorage().query("SELECT name FROM ___fcf___variables WHERE package = ${1} and name = ${2}", ["fcf", "installModules"]);
        if (!records[0][0]) {
          await fcf.application.getStorage().query({
            query: "INSERT INTO ___fcf___variables (package, name, value, description) VALUES (${1}, ${2}, ${3}, ${4});" +
                   "INSERT INTO ___fcf___variables (package, name, value, description) VALUES (${5}, ${6}, ${7}, ${8});" +
                   "INSERT INTO ___fcf___variables (package, name, value, description) VALUES (${9}, ${10}, ${11}, ${12});" +
                   "INSERT INTO ___fcf___variables (package, name, value, description) VALUES (${13}, ${14}, ${15}, ${16});" +
                   "INSERT INTO ___fcf___variables (package, name, value, description) VALUES (${17}, ${18}, ${19}, ${20});" +
                   "INSERT INTO ___fcf___variables (package, name, value, description) VALUES (${21}, ${22}, ${23}, ${24});" +
                   "INSERT INTO ___fcf___variables (package, name, value, description) VALUES (${25}, ${26}, ${27}, ${28});" +
                   "INSERT INTO ___fcf___variables (package, name, value, description) VALUES (${29}, ${30}, ${31}, ${32});" +
                   "INSERT INTO ___fcf___variables (package, name, value, description) VALUES (${33}, ${34}, ${35}, ${36});" +
                   "INSERT INTO ___fcf___variables (package, name, value, description) VALUES (${37}, ${38}, ${39}, ${40});" +
                   "INSERT INTO ___fcf___variables (package, name, value, description) VALUES (${41}, ${42}, ${43}, ${44});" +
                   "INSERT INTO ___fcf___variables (package, name, value, description) VALUES (${45}, ${46}, ${47}, ${48});"
                   ,
            args: [
              "fcf", "installModules", "[]", "Array of installed packages in the system",
              "fcf", "languages",    '{"en": "English"}', "List of available languages",
              "fcf", "defaultLanguage", 'en', "Default language",
              "fcf", "languageIdentification", JSON.stringify({byPrefix: true, byCookie: true, byParameter: false, parameter: "language", byHTTP: true}), "Methods for determining the language",
              "fcf", "editTranslations", JSON.stringify({en: ":translations/en"}), "Editable translation files",
              "fcf", "languageFiles", "{}", "Information about translation files",
              "fcf", "autoFillJoinTables", "{}", "Contains autocomplete relationships for related tables",
              "fcf", "autoFillTranslateTables", "{}", "Contains autocomplete relationships for translated tables",
              "fcf", "lastCron", "", "Time of the last cron launch",
              "fcf", "sitemapSettings", JSON.stringify({"multilingual": true, "enableAutoUpdate": true, "autoUpdateDays": 1, "autoUpdateHours": 0, "priorityDefault": 0.5, "changefreqDefault": "monthly", "multilingualDefault": true, "additionalSitemapItems": [], "sitemapItems": [] }), "Settings for creating a sitemap file",
              "fcf", "htmlHeader", "", "The part of the HTML header that is included in all pages of the site",
              "fcf", "packageVersions", JSON.stringify({fcf: this.configuration.version}), "Package versions",
            ],
          })
        }

        // cache variables
        cache.append({
          part: "fcf",
          name: "variables",
          recalculate: function(){
            return fcf.application.getStorage().query({query: "SELECT package, name, value FROM ___fcf___variables"})
            .then(function(a_response){
              var data = {};
              var resords = a_response[0];
              for(var i = 0; i < resords.length; ++i){
                if (!data[resords[i].package])
                  data[resords[i].package] = {};
                var value = resords[i].value;
                data[resords[i].package][resords[i].name] = fcf.strToObject(resords[i].value);
              }
              return data;
            });
          }
        });

        fcf.application.getEventChannel().on("set_system_variable_after", true, function(a_event){
          let variables = cache.get("fcf", "variables");
          variables[a_event.package][a_event.variable] = a_event.value;
        });


        fcf.application.getEventChannel().on("cache_update_after", function(a_event){
          if (a_event.part == "fcf" && a_event.variable == "translations")
            fcf.application.getProjections().retranslate();
        });

        fcf.application.getEventChannel().on("fsql_update_after:___fcf___variables", true, async function(a_event){
          var data = cache.get("fcf", "variables");
          if (!data){
            cache.set("fcf", "variables", {});
            data = cache.get("fcf", "variables");
          }
          let records = (await fcf.application.getStorage().query({
            type:   "select",
            from:   "___fcf___variables",
            fields: [{field: "*"}],
            where:  a_event.query.where,
            limit: a_event.query.limit,
            offset: a_event.query.offset,
          }))[0];
          for(var i = 0; i < records.length; ++i){
            if (!data[records[i].package])
              data[records[i].package] = {};
            data[records[i].package][records[i].name] = fcf.strToObject(records[i].value);
          }
        });

        fcf.application.getEventChannel().on("fsql_insert_after:___fcf___variables", true, async(a_event)=>{
          await cache.update("fcf", "variables");
        });

        await cache.update("fcf", "variables");
      }

      async initialize(){
        ///////////////////////////////////
        // Event handlers
        ///////////////////////////////////

        //
        // Exit handler
        //
        fcf.application.getEventChannel().on("exit", function(){
          fcf.application._stopSystemActions()
          .then(()=>{
            process.exit(0);
          })
        });

        //
        // Restart handler
        //
        fcf.application.getEventChannel().on("restart", function(a_event){
          let client  = new libNet.Socket();
          client.connect(fcf.application.getConfiguration().serverControlPort, '127.0.0.1', function() {
            let message = JSON.stringify({name: "restart", group: fcf.application.getConfiguration().serverGroup, cause: "application"});
            let data    = message.length + "\n" + message;
            try {
              client.write(data);
            } catch(e) {
              fcf.log.err("FCF", "Can't send restart message!", e);
            }
            try {
              client.destroy();
            }catch(e){
            }
          });
          client.on("error", ()=>{});
        });

        //
        // Watch file handler
        //
        let watchFiles = {};
        fcf.application.getEventChannel().on("watch_file", function(a_event){
          let file = fcf.getPath(a_event.file);
          if (file in watchFiles)
            return;
          watchFiles[file] = file;
          libFS.watchFile(a_event.file, {interval: 1000}, (a_eventType)=>{
            if (!a_eventType.mode)
              return;
            if (!fcf.application.getConfiguration().serverControlPort)
              return;
            let message = JSON.stringify({name: "restart", group: fcf.application.getConfiguration().serverGroup, cause: "file_update", file: file});
            let data    = message.length + "\n" + message;
            let client  = new libNet.Socket();
            client.connect(fcf.application.getConfiguration().serverControlPort, '127.0.0.1', function() {
              try {
                client.write(data);
              } catch(e) {
                fcf.log.err("FCF", "Can't send restart message!", e);
              }
              try {
                client.destroy();
              }catch(e){
              }
            });
            client.on("error", ()=>{});
          });
        });
        let mainScript = undefined;
        for(let i = 1; i < process.argv.length; ++i){
          if (process.argv[i].indexOf("--") != 0){
            mainScript = process.argv[i];
            break;
          }
        }
        if (mainScript)
          fcf.application.getEventChannel().send("watch_file", {file: fcf.getPath(mainScript)});
        fcf.application.getEventChannel().send("watch_file", {file: fcf.getPath("fcf:fcf.js")});
        fcf.application.getEventChannel().startupDeferredEvents("watch_file");

        //
        // Update environment handler
        //
        fcf.application.getEventChannel().on("environment", function(a_event){
          fcf.application.getEventChannel().setProcessEnvironment(a_event.processes);
          cache.updateAll();
        });

        //
        // Update translation handler
        //
        fcf.application.getEventChannel().on("translations", true, function(a_event){
          cache.update("fcf", "translations");
        });

        //
        // Update languages by modify fcf::languages system variable
        //
        fcf.application.getEventChannel().on("set_system_variable_after", true, function(a_event){
          if (a_event.package != "fcf" || a_event.variable != "languages")
            return;
          fcf.application.getProjections().retranslate();
        });


        //
        // Access handlers RENDER
        //
        authorizationHandler.initialize();

        //
        // Access handlers FSQL
        //
        fcf.application.getEventChannel().on("fsql_select_check_projection", function(a_event){
          let projection    = fcf.application.getProjections().get(a_event.projection);
          if (!projection)
            return;
          if (!fcf.checkAccessObject(projection.access, "select", a_event.options.roles)){
            throw new fcf.Exception("ACCESS_SELECT_PROJECTION", {projection: a_event.projection})
          }
        });

        fcf.application.getEventChannel().on("fsql_update_after", function(a_event){
          let projection    = fcf.application.getProjections().get(a_event.projection);
          if (!projection)
            return;
          if (!fcf.checkAccessObject(projection.access, "update", a_event.options.roles))
            throw new fcf.Exception("ACCESS_UPDATE_PROJECTION", {projection: a_event.projection})
        });

        fcf.application.getEventChannel().on("fsql_insert_after", function(a_event){
          let projection    = fcf.application.getProjections().get(a_event.projection);
          if (!projection)
            return;
          if (!fcf.checkAccessObject(projection.access, "insert", a_event.options.roles))
            throw new fcf.Exception("ACCESS_INSERT_PROJECTION", {projection: a_event.projection})
        });

        fcf.application.getEventChannel().on('fsql_delete_after', function(a_event){
          let projection    = fcf.application.getProjections().get(a_event.projection);
          if (!projection)
            return;
          if (!fcf.checkAccessObject(projection.access, "delete", a_event.options.roles))
            throw new fcf.Exception("ACCESS_DELETE_PROJECTION", {projection: a_event.projection})
        });

        //
        // Cache handlers
        //

        // cache translations
        // The update is called from fcfManager
        cache.append({
          part: "fcf",
          name: "translations",
          recalculate: function(){
            return languages.updateTranslations();
          }
        });



        //
        // Cron
        //

        // Sitemap generation
        let sitemap = new Sitemap();
        sitemap.initializeCronTasks();

        // Clearing sessions
        fcf.application.getCron().append("Сlearing sessions", "0 */5 * * * master", ()=>{
          return fcf.actions()
          .then(()=>{
            if (!fcf.application.getConfiguration().sessionLifetime)
              return;
            let last = fcf.dateFormat(
                        new Date((new Date()).getTime() - (fcf.application.getConfiguration().sessionLifetime * 24 * 60 * 60 * 1000)),
                        "Y-m-d H:i:s"
                        );
            return fcf.application.getStorage().query("DELETE FROM ___fcf___sessions WHERE last < ${1}", [last], {roles: ["root"]});
          });
        })

        // Cleaning uninitialized users
        fcf.application.getCron().append("Cleaning uninitialized users", "0 */5 * * * master", ()=>{
          return fcf.actions()
          .then(()=>{
            if (!fcf.application.getConfiguration().lifetimeNotInitializedUser)
              return;
            let last = fcf.dateFormat(
                        new Date((new Date()).getTime() - (fcf.application.getConfiguration().lifetimeNotInitializedUser * 24 * 60 * 60 * 1000)),
                        "Y-m-d H:i:s"
                        );
            return fcf.application.getStorage().query(
                "DELETE FROM " + fcf.application.getConfiguration().userProjectionName + " WHERE initialized <> ${1} and time_creation < ${2}",
                [ true, last ],
                { roles: ["root"] },
                );
          });
        })


        return fcf.actions()
        .then(function(){
          return cache.update("fcf", "variables");
        })
        .then(function(){
          return cache.update("fcf", "translations");
        })
        .then(()=>{
          return builder.appendGroup("js", "fcf:include.js", mainClientInclude);
        })
      }

      async install() {
        return fcf.actions()
        .then(()=>{
          return fcf.application.getStorage().query({
            query: "INSERT INTO ___fcf___roles (name, description) VALUES (${1}, ${2});" +
                   "INSERT INTO ___fcf___roles (name, description) VALUES (${3}, ${4});" +
                   "INSERT INTO ___fcf___roles (name, description) VALUES (${5}, ${6});" +
                   "INSERT INTO ___fcf___roles (name, description) VALUES (${7}, ${8});",
            args: ["root",    "Super role",
                   "user",    "Simple user role",
                   "render",  "An auxiliary role that is used when programmatically fetching from the database inside the template hook.",
                   "menu",    "An auxiliary role that is used when fetching from the database when building the menu."
                   ],
          });
        })
        .then((a_records)=>{
          return fcf.application.getStorage().query({
            query: "INSERT INTO ___fcf___groups (name, description, roles) VALUES (${1}, ${2}, ${3});" +
                   "INSERT INTO ___fcf___groups (name, description, roles) VALUES (${4}, ${5}, ${6});",
            args: ["root", "Super users", [a_records[0][0]["@key"]],
                   "user", "Simple users", [a_records[1][0]["@key"]]],
          });
        })
        .then((a_records)=>{
          return fcf.application.getStorage().query({
            query: "INSERT INTO ___fcf___users (user, password, groups, active, time_creation) VALUES (${1}, ${2}, ${3}, ${4}, ${5})",
            args: ["root", "root", [a_records[0][0]["@key"]], true, fcf.dateFormat(new Date(), "Y-m-d H:i:s")],
            roles: ["root"],
          });
        })
      }


    };

    return Package;
  }
});
