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

fcf.module({
  name: "fcf:fcfModule.js",
  dependencies: [ "fcf:NServer/PackageHandler.js", 
                  "fcf:NTools/cache.js", 
                  "fcf:NTools/languages.js", 
                  "fcf:NTools/fileBuilder.js", 
                  "fcf:NAuthorization/authorizationHandler.js"
                ],
  module: function(PackageHandler, cache, languages, fileBuilder, authorizationHandler) {
    var NFcf = fcf.prepareObject(fcf, "packages.fcf");

    NFcf.fcfModule = function() {
      PackageHandler.call(this);

      this.initialize = function(){

        authorizationHandler.initialize();

        ///////////////////////////////////
        // Event handlers
        ///////////////////////////////////

        // 
        // Exit handler
        //
        fcf.application.getEventChannel().on("exit", true, function(){
          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;
            client.write(data);
            client.destroy();
          });
        });

        // 
        // Watch file handler
        //
        fcf.application.getEventChannel().on("watch_file", function(a_event){
          let file = a_event.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() {
              client.write(data);
              client.destroy();
            });
          });
        });
        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().recalculate();
        });


        // 
        // Access handlers
        //
        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", 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", 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", 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("fcf", "translations", function(){
          return languages.updateTranslations();
        });

        // cache variables
        cache.append("fcf", "variables", 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().recalculate();
        });

        fcf.application.getEventChannel().on("postUpdateProjectionFcf_variables", true, function(a_event){
          var data = cache.get("fcf", "variables");
          if (!data){
            cache.set("fcf", "variables", {});
            data = cache.get("fcf", "variables");
          }
          for(var i = 0; i < a_event.result.length; ++i){
            if (!data[a_event.result[i].package])
              data[a_event.result[i].package] = {};
            data[a_event.result[i].package][a_event.result[i].name] = fcf.strToObject(a_event.result[i].value);
          }
        });

        //
        // Cron
        //

        // 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 fileBuilder.build("include", 
          [
            "fcf:NRender/NDetails/Helper.js",
            "fcf:NRender/TaskInfo.js",
            "fcf:NFSQL/NFilter/Filter.js",
            "fcf:NServer/PackageHandler.js",
            "fcf:NServer/Package.js",
            "fcf:NTheme/Theme.js",
            "fcf:NTheme/Themes.js",
            "fcf:NRender/NDetails/Loader.js",
            "fcf:NRender/NDetails/TemplateRender.js",
            "fcf:NRender/NDetails/TemplateProcessor.js",
            "fcf:NRender/Template.js",
            "fcf:NRender/NDetails/ArgsBuilder.js",
            "fcf:NRender/Render.js",

            "fcf:NFSQL/NDetails/Errors.js",
            "fcf:NFSQL/NDetails/SingleBuilder.js",
            "fcf:NFSQL/Builder.js",

            "fcf:NEvent/Error.js",
            "fcf:NServer/Configuration.js",
            "fcf:NClient/LocalData.js",
            "fcf:NClient/Application.js",

            "fcf:NClient/Wrapper.js"
          ])
        })
      }

      this.install = function() {
        var actions = new fcf.Actions();
        actions.then(function(a_res, a_act){
          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});",
            args: [
              "fcf", "installModules", "[]", "Array of installed packages in the system",
              "fcf", "languages",    '{"en": "English"}', "List of available languages",
              "fcf", "defaultLanguage", 'en', "Default language",
              "fcf", "translations-files", '[]', "Information about translation files",
              "fcf", "translations-words", '{}', "Words to translate",
              "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",
            ],
          })
          .then(function(){
            a_act.complete();
          })
          .catch(function(a_error){
            a_act.error(a_error);
          })
        })
        .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});" ,
            args: ["root", "Super role",
                   "user", "Simple user role"],
          });
        })
        .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 actions;
      }

    };

    return NFcf.fcfModule;
  }
});
