var libExpress      = undefined;
var libBodyParser   = undefined;
var libCookieParser = undefined
var libNet          = undefined;
var libFormidable   = undefined;
var libUtil         = require('util');
var libFS           = require('fs');
var libPath         = require('path');
var libAsyncHooks   = require("async_hooks");
const EVENT_PACKAGE_HEADER_SIZE  = 76;
fcf.module({
  name: "fcf:NServer/Application.js",
  dependencies: ["fcf:NSystem/Configuration.js",
                 "fcf:NSystem/NPackage/packageLoader.js",
                 "fcf:NSystem/NPackage/tools.js",
                 "fcf:NSystem/nodeManager.js",
                 "fcf:NSystem/Cron.js",
                 "fcf:NSystem/fs.js",
                ],
  lazy:         [],
  module: function(Configuration, packageLoader, packageTools, nodeManager, Cron, FS) {
    var ControllerPage;
    var Router;
    var Request;
    var Projections;
    var Storage;
    var Render;
    var cache;
    var babel;

    fcf.prepareObject(fcf, "NServer");

    fcf.NServer.Application = class Application {

      constructor() {
        let self = this;
        fcf.application = this;


        this._systemContext = new fcf.Context();
        fcf.append(this._systemContext, {
          language: "en",
          session: {
            user: {
              groups: {root: "root"},
              roles:  {root: "root"},
              user:   "FCF SYSTEM",
            }
          }
        });

        this._isRunning     = false;
        this._actions       = fcf.actions();
        this._eventChannel  = fcf.NDetails.eventChannel;
        this._events        = {};
        self._cron          = new Cron();
        this._systemActions = {};
        this._stop          = false;

        //
        // Fill overwrite configuration
        //
        let overwriteConfiguration = {};

        if (process.argv.indexOf("--port") != -1)
          overwriteConfiguration.port  = process.argv[process.argv.indexOf("--port")+1];

        overwriteConfiguration.restart = process.argv.indexOf("--restart") != -1;

        if (process.argv.indexOf("--host") != -1 && process.argv[process.argv.indexOf("--host")+1])
          overwriteConfiguration.host  = process.argv[process.argv.indexOf("--host")+1];

        if (process.argv.indexOf("--keep-alive-timeout") != -1 && process.argv[process.argv.indexOf("--keep-alive-timeout")+1]) {
          if (!isNaN(parseInt(process.argv[process.argv.indexOf("--keep-alive-timeout")+1])))
            overwriteConfiguration.keepAliveTimeout  = parseInt(process.argv[process.argv.indexOf("--keep-alive-timeout")+1]);
        }

        if (process.argv.indexOf("--inner-control-port") != -1 && process.argv[process.argv.indexOf("--inner-control-port")+1] )
          overwriteConfiguration.innerControlPort = process.argv[process.argv.indexOf("--inner-control-port")+1];

        if (process.argv.indexOf("--server-name") != -1 && process.argv[process.argv.indexOf("--server-name")+1])
          overwriteConfiguration.serverName = process.argv[process.argv.indexOf("--server-name")+1];

        if (process.argv.indexOf("--main-server-name") != -1 && process.argv[process.argv.indexOf("--main-server-name")+1])
          overwriteConfiguration.mainServer = process.argv[process.argv.indexOf("--main-server-name")+1];

        if (process.argv.indexOf("--handler-name") != -1 && process.argv[process.argv.indexOf("--handler-name")+1])
          overwriteConfiguration.handlerName = process.argv[process.argv.indexOf("--handler-name")+1];

        if (process.argv.indexOf("--process-index") != -1 && process.argv[process.argv.indexOf("--process-index")+1]){
          overwriteConfiguration.processIndex = parseInt(process.argv[process.argv.indexOf("--process-index")+1]);
          overwriteConfiguration.processPair = Math.floor(overwriteConfiguration.processIndex/2);
        }

        let configFile = ":settings.config";
        if (process.argv.indexOf("--settings") != -1 && process.argv[process.argv.indexOf("--settings")+1]){
          configFile = process.argv[process.argv.indexOf("--settings")+1];
        }

        overwriteConfiguration.disableWeb              = fcf.find(process.argv, "--disable-web") !== undefined;
        overwriteConfiguration.disableSys              = fcf.find(process.argv, "--disable-sys") !== undefined;
        overwriteConfiguration.disableCron             = fcf.find(process.argv, "--disable-cron") !== undefined;
        overwriteConfiguration.master                  = fcf.find(process.argv, "--slave") === undefined;
        overwriteConfiguration.leading                 = fcf.find(process.argv, "--leading") !== undefined;

        overwriteConfiguration.clearDuplicatesMode = process.argv.indexOf("--clear-duplicates") != -1;
        if (overwriteConfiguration.clearDuplicatesMode){
          overwriteConfiguration.disableWeb   = true;
          overwriteConfiguration.disableSys   = true;
          overwriteConfiguration.disableCron  = true;
          overwriteConfiguration.master       = false;
        }

        this._express           = undefined;
        this._router            = undefined;
        this._projections       = undefined;
        this._render            = undefined;
        this._inheritanceModes  = {};
        this._configuration     = new Configuration();
        this._configuration.appendOverwriteConfiguration(overwriteConfiguration);

        this._storage = undefined;

        this._eventChannel.on("fcf_exit", (a_event)=>{
          self._stopSystemActions().then(()=>{
            fcf.log.log("FCF", "Process exit. Process index:", fcf.application.getConfiguration().processIndex);
            process.exit(0);
          })
        });

        this._eventChannel.on("fcf_check_ready", (a_event)=>{
          a_event.pid = process.pid;
          a_event.ready = self._isRunning;
        });

        this._eventChannel.on("fcf_get_path_info", (a_event)=>{
          let self = this;
          fcf.setContext(new fcf.Context());
          return fcf.actions()
          .then(async ()=>{
            let ci = await self._getControllerInfo(fcf.getContext(), a_event.path, {headers: a_event.headers}, a_event.context);
            a_event.shortMode = ci.shortMode;
          });
        });

        function clLoadConfigFile(a_filePath) {
          let data;
          fcf.NDetails.eventChannel.send("fcf_watch_file", {file: fcf.getPath(a_filePath)});
          if (!libFS.existsSync(fcf.getPath(a_filePath))){
            self._constructorError = "Failed read configuration file '" + a_filePath + "'";
          }
          try {
            let rawdata = libFS.readFileSync(fcf.getPath(a_filePath), 'utf8');
            data = fcf.scriptExecutor.parse(rawdata, {}, a_filePath, 0);
          } catch(e) {
            self._constructorError = "Failed read configuration file '" + a_filePath + "': " + e.message;
            return;
          }
          if (data)
            self._configuration.appendUserConfiguration(data);
        }

        clLoadConfigFile(configFile);

        fcf.each(this._configuration.configs, (a_key, a_filePath)=>{
          clLoadConfigFile(a_filePath);
        });

      }

      initialize() {
        let self = this;

        fcf.setContext(fcf.application.getSystemContext());

        if (process.argv[2] == "--help"){
          console.log("Usage: fcfnode <js-application-script> [OPTIONS]");
          console.log("Advanced options:");
          console.log("  --help         Print this message and exit");
          console.log("  --get-settings Print configuration and exit");
          console.log("  --set-user-password [USER_NAME] [PASSWORD]");
          console.log("  --keep-alive-timeout Sets the parameter value in HTTP Keap-alive in seconds");
          console.log("  --restart The application was launched when the server requested a restart");
          process.exit();
        } else if (process.argv[2] == "--get-settings"){
          console.log("FCF-SETTINGS:")
          console.log(JSON.stringify(this.getConfiguration(), undefined, 2))
          console.log("FCF-ENDSETTINGS")
          return fcf.actions().then((a_res, a_act)=>{
            setTimeout(()=>{
              process.exit(0);
            }, 100);
          });
          
        } else if (process.argv[2] == "--set-user-password"){
          self.getConfiguration().disableSys  = true;
          self.getConfiguration().disableCron = true;
          self.getConfiguration().disableWeb = true;
        }

        if (this._constructorError){
          fcf.log.err("FCF", this._constructorError);
          process.exit(1);
        }


        process.stdout.write = (function(write) {
          return function(a_string, a_encoding, a_fileDescriptor) {
            if (!fcf.NDetails._thisLoggerMessage)
              fcf.log.log("STDOUT", fcf.trim(a_string));
            write.apply(process.stdout, arguments);
          };
        })(process.stdout.write);
        process.stderr.write = (function(write) {
          return function(a_string, a_encoding, a_fileDescriptor) {
            if (!fcf.NDetails._thisLoggerMessage)
              fcf.log.err("STDOUT", fcf.trim(a_string));
            write.apply(process.stderr, arguments);
          };
        })(process.stderr.write);

        var actions = fcf.actions();

        actions.then(async ()=>{
          let modules = await fcf.require(["fcf:NSystem/cache.js"]);
          cache = modules[0];
        });

        actions.then(async ()=>{
          if (!self.getConfiguration().disableSys)
            await self._updateNodeDependencies();
        });

        actions.then(async ()=>{
          libExpress      = require('express');
          libBodyParser   = require('body-parser');
          libCookieParser = require('cookie-parser');
          libNet          = require('net');
          libFormidable   = require('formidable');

          let modules = await fcf.require([
                                  "fcf:NServer/NControllers/Page.js",
                                  "fcf:NServer/Router.js",
                                  "fcf:NServer/Request.js",
                                  "fcf:NFSQL/Projections.js",
                                  "fcf:NFSQL/Storage.js",
                                  "fcf:NRender/Render.js",
                                  "fcf:NSystem/babel.js"]);
          ControllerPage = modules[0];
          Router         = modules[1];
          Request        = modules[2];
          Projections    = modules[3];
          Storage        = modules[4];
          Render         = modules[5];
          babel          = modules[6];
        });

        actions.then((a_res, a_act) => {
          if (!self.getConfiguration().innerControlPort){
            a_act.complete();
            return;
          }
          self._serverSocket = new libNet.Socket();
          self._serverSocket.connect(self.getConfiguration().innerControlPort, "127.0.0.1");
          self._serverSocket._preffix = Buffer.alloc(0);
          self._serverSocket._preffixBegin = 0;
          self._serverSocket._preffixEnd = 0;

          function readEventSocket(a_data){
            let eventData;
            let eventBodySize;

            if (a_data && (self._serverSocket._preffixEnd - self._serverSocket._preffixBegin) == 0 && a_data.length < 4) {
              self._serverSocket._preffix = a_data;
              self._serverSocket._preffixBegin = 0;
              self._serverSocket._preffixEnd = self._serverSocket._preffix.length;
              return false;
            }

            if (a_data && (self._serverSocket._preffixEnd - self._serverSocket._preffixBegin) + a_data.length < 4) {
              self._serverSocket._preffix = Buffer.concat([self._serverSocket._preffix.slice(self._serverSocket._preffixBegin, self._serverSocket._preffixEnd), a_data]);
              self._serverSocket._preffixBegin = 0;
              self._serverSocket._preffixEnd = self._serverSocket._preffix.length;
              return false;
            }

            if (a_data && (self._serverSocket._preffixEnd - self._serverSocket._preffixBegin) < 4) {
              if (self._serverSocket._preffixEnd - self._serverSocket._preffixBegin == 0){
                self._serverSocket._preffix = a_data;
              } else {
                self._serverSocket._preffix = Buffer.concat([self._serverSocket._preffix.slice(self._serverSocket._preffixBegin, self._serverSocket._preffixEnd), a_data]);
              }
              self._serverSocket._preffixBegin = 0;
              self._serverSocket._preffixEnd = self._serverSocket._preffix.length;
              eventData = self._serverSocket._preffix;
              eventBodySize = eventData.readUInt32LE(self._serverSocket._preffixBegin);
              if ((self._serverSocket._preffixEnd - self._serverSocket._preffixBegin) < (eventBodySize + EVENT_PACKAGE_HEADER_SIZE)){
                let buffer = Buffer.alloc(eventBodySize + EVENT_PACKAGE_HEADER_SIZE);
                buffer.set(self._serverSocket._preffix);
                self._serverSocket._preffix = buffer;
                return false;
              }
            } else if (a_data){
              eventBodySize = self._serverSocket._preffix.readUInt32LE(self._serverSocket._preffixBegin);
              if ((self._serverSocket._preffix.length - self._serverSocket._preffixBegin) < Math.max((self._serverSocket._preffixEnd - self._serverSocket._preffixBegin) + a_data.length, eventBodySize + EVENT_PACKAGE_HEADER_SIZE)){
                let buffer = Buffer.alloc(Math.max((self._serverSocket._preffixEnd - self._serverSocket._preffixBegin) + a_data.length, eventBodySize + EVENT_PACKAGE_HEADER_SIZE));
                buffer.set(self._serverSocket._preffix.slice(self._serverSocket._preffixBegin, self._serverSocket._preffixEnd));
                buffer.set(a_data, self._serverSocket._preffixEnd - self._serverSocket._preffixBegin);
                self._serverSocket._preffix = buffer;
                self._serverSocket._preffixEnd = (self._serverSocket._preffixEnd - self._serverSocket._preffixBegin) + a_data.length;
                self._serverSocket._preffixBegin = 0;
              } else {
                self._serverSocket._preffix.set(a_data, self._serverSocket._preffixEnd);
                self._serverSocket._preffixEnd += a_data.length;
              }
              eventData = self._serverSocket._preffix;
            } else {
              eventBodySize = self._serverSocket._preffix.readUInt32LE(self._serverSocket._preffixBegin);
              eventData = self._serverSocket._preffix;
            }

            if ((self._serverSocket._preffixEnd - self._serverSocket._preffixBegin) < (eventBodySize + EVENT_PACKAGE_HEADER_SIZE)){
              return false;
            }

            let event         = undefined;
            let eventId       = undefined;
            let eventFlags    = 0;
            let eventItems    = [];

            try {
              eventFlags  = eventData.readUInt8(self._serverSocket._preffixBegin + 4) | (eventData.readUInt8(self._serverSocket._preffixBegin + 5) << 8);
              eventId     = Buffer.from(eventData.buffer, self._serverSocket._preffixBegin + eventData.byteOffset + 6, 18).toString();
              let start   = EVENT_PACKAGE_HEADER_SIZE;
              while(start - EVENT_PACKAGE_HEADER_SIZE < eventBodySize){
                if (eventData.readUInt8(self._serverSocket._preffixBegin + start) != 0)
                  break;
                let blockSize   = eventData.readUInt32LE(self._serverSocket._preffixBegin + start+1);
                let blockFlags  = eventData.readUInt8(self._serverSocket._preffixBegin + start + 5);
                let blockType   = blockFlags & 0x07;
                if (blockType == 1){
                  eventItems.push(Buffer.from(eventData.buffer, self._serverSocket._preffixBegin + eventData.byteOffset + start + 6, blockSize));
                } else if (blockType == 2){
                  eventItems.push(new ArrayBuffer(eventData.buffer, self._serverSocket._preffixBegin + start + 6, blockSize));
                } else if (blockType == 3){
                  eventItems.push(new Uint8Array(eventData.buffer, self._serverSocket._preffixBegin + start + 6, blockSize));
                } else if (blockType == 4){
                  eventItems.push(new Int8Array(eventData.buffer, self._serverSocket._preffixBegin + start + 6, blockSize));
                } else {
                  eventItems.push(JSON.parse(Buffer.from(eventData.buffer, self._serverSocket._preffixBegin + eventData.byteOffset + start + 6, blockSize).toString()));
                }
                start += blockSize + 6;
              }
              event = eventBodySize - start + EVENT_PACKAGE_HEADER_SIZE > 0
                                    ? JSON.parse(Buffer.from(eventData.buffer, self._serverSocket._preffixBegin + eventData.byteOffset + start, eventBodySize - start + EVENT_PACKAGE_HEADER_SIZE).toString())
                                    : {};
            } catch(e) {
              if (eventId in self._events)
                delete self._events[eventId]
              fcf.log.err("FCF", "Format error on the fcfserver message: ", e);
              self._serverSocket._preffix = Buffer.alloc(0);
              return false;
            }
            if (eventFlags & 0x01) { // response event
              if (eventId in self._events) {
                if (!(eventFlags & 0x08)) {
                  let resultEvent = !self._events[eventId].options.sync ? self._events[eventId].event : event;
                  if (!self._events[eventId].options.sync) {
                    resultEvent.eventItems = fcf.append([], self._events[eventId].event.eventItems, eventItems);
                  } else {
                    resultEvent.eventItems = eventItems;
                  }
                  self._events[eventId].act.complete(resultEvent);
                } else {
                  event = new fcf.Exception(event);
                  self._events[eventId].act.error(event);
                }
                delete self._events[eventId];
              }
            } else {
              event.eventItems = eventItems;
              let eventItemsSize = fcf.count(event.eventItems);
              self.getSystemActions().then((a_res, a_act)=>{
                setImmediate(()=>{
                  fcf.NServer.Application.setActiveMessageState(!!(eventFlags & 0x40));
                  return self._eventChannel.send(event, {_fromServer: true, _directedCall: eventFlags & 0x0200, _ignoreNoActiveCalls: eventId in self._events && eventFlags & 0x0100 })
                  .then((a_event)=>{
                    return self._sendEvent(a_event, {id:eventId, sync: !!(eventFlags & 0x04), eventItemsSize: eventItemsSize }, true);
                  })
                  .catch((a_error)=>{
                    self._sendEvent(event, {id:eventId, sync: !!(eventFlags & 0x04)}, true, a_error);
                  })
                  .finally(()=>{
                    a_act.complete();
                  });
                });
              })
            }

            self._serverSocket._preffixBegin += eventBodySize + EVENT_PACKAGE_HEADER_SIZE;
            if (self._serverSocket._preffixEnd - self._serverSocket._preffixBegin < 4){
              return false;
            } else {
              let nextBodySize = self._serverSocket._preffix.readUInt32LE(self._serverSocket._preffixBegin);
              if ((self._serverSocket._preffixEnd - self._serverSocket._preffixBegin) < (nextBodySize + EVENT_PACKAGE_HEADER_SIZE)){
                return false;
              } else {
                return true;
              }
            }
          };

          self._serverSocket.on("data", (a_data) => {
            while(readEventSocket(a_data)){
              a_data = false;
            }
          });

          self._serverSocket.on("connect", async (a_data)=>{
            try {
              await self._sendEvent({
                name:          "fcfserver_initiaize_connection",
                handler:       self.getConfiguration().handlerName,
                process_index: self.getConfiguration().processIndex,
                server:        self.getConfiguration().serverName,
              },
              {inner: true},
              false);

              let event = await self._sendEvent({
                name:          "fcfserver_get_environment",
                handler:       self.getConfiguration().handlerName,
                process_index: self.getConfiguration().processIndex,
                server:        self.getConfiguration().serverName,
              },
              {inner: true, sync: true},
              false);

              if (a_act){
                a_act.complete();
                a_act = false;
              }

              fcf.application.getConfiguration().servers = event.servers;
              fcf.application.getConfiguration().selfHandlers = event.selfHandlers;
            } catch(e){
              if (a_act){
                a_act.error(new fcf.Exception("ERROR", {error: `Error on fcfserver connection: ${e}`}));
                a_act = false;
              }
              fcf.log.err("FCF", "Error on fcfserver connection:", e);
              process.exit(1);
            }
          });

          self._serverSocket.on("error", (a_error)=>{
            if (a_act){
              a_act.error(new fcf.Exception("ERROR", {error: `Error on fcfserver connection: ${a_error}`}));
              a_act = false;
            }
            fcf.log.err("FCF", "Error on fcfserver connection:", a_error);
          });

        });

        actions.then(async ()=>{
          self._express       = libExpress();
          self._router        = new Router();
          self._projections   = new Projections();

          var connections = self.getConfiguration().dataClient && self.getConfiguration().dataClient.connections
                                ? self.getConfiguration().dataClient.connections
                                : {};
          self._storage = new Storage({ projections: self._projections, connections: connections, eventChannel: self._eventChannel});
          self._render = new Render({application: self, storage: self._storage, fileСaching: self.getConfiguration().fileСaching});

          await fcf.require(["fcf:NSystem/loggingTasks.js"]);
        });

        actions.then(()=>{
          return fcf.require([
            "fcf:NSystem/Sitemap.js",
            "fcf:NSystem/NDetails/fsEvents.js"
          ]);
        });

        actions.then(async ()=>{
          await packageLoader.load("fcf");
        });

        actions.then(async ()=>{
          await packageLoader.load(fcf.application.getConfiguration().defaultTheme);
        });

        actions.then(async ()=>{
          await fcf.each(this.getConfiguration().packages, async (a_key, a_packName)=>{
            await packageLoader.load(a_packName);
          });
        });

        actions.then(async ()=>{
          await packageLoader.load("");
        });

        actions.then(function(){
          cache.update("fcf", "translations");
        });

        actions.then(function(){
          self.getRouter().append([{
            uri:        "fcfpackages/fcf/*",
            controller: "fcf:NServer/NControllers/File.js",
            source:     "fcf:/",
          }]);
        });

        actions.then(async function(){
          if (self.getConfiguration().disableSys)
            return;
          let directorySystemProjections = fcf.getPath(self.getConfiguration().directorySystemProjections);
          FS.prepareDirectorySync(directorySystemProjections);

          let projections  = self._projections.getProjections();
          let tprojections = {};
          await fcf.each(projections, async (a_key, a_projection)=>{
            if (!a_projection.translate)
              return;
            let tprojetion = {
              alias:  "___fcftranslate___" + a_projection.alias,
              table:  "___fcftranslate___" + a_projection.alias,
              title:  "translate",
              key:    "___fcftranslate___key",
              enable: true,
              access:  a_projection.access,
              dbSync: true,
              inner: true,
              unique: [[a_projection.key, "___fcftranslate___language"]],
              fields:[
                {
                  alias:    "___fcftranslate___key",
                  field:    "___fcftranslate___key",
                  title:    "___fcftranslate___key",
                  type:     "bigint",
                  notAdd:   true,
                  notEdit:  true,
                  autoIncrement: true,
                },
                {
                  alias:    "___fcftranslate___language",
                  field:    "___fcftranslate___language",
                  type:     "string",
                  maxSize:  2,
                }
              ],
            };
            fcf.each(a_projection.fields, (a_key, a_field) => {
              if (a_field.alias == a_projection.key || a_field.translate) {
                a_field = fcf.clone(a_field);
                delete a_field.translate;
                delete a_field.notEmpty;
                delete a_field.notNull;
                delete a_field.autoIncrement;
                delete a_field.unique;
                a_field.emptyAsNull = true;
                tprojetion.fields.push(a_field);
              }
            });
            const filePath = libPath.join(directorySystemProjections, tprojetion.alias + ".projection");
            libFS.writeFileSync(filePath, JSON.stringify(tprojetion, undefined, 2));
            tprojections[tprojetion.alias] = tprojetion;
          });
          await self.getProjections().appendProjectionStructs(tprojections);
        });

        actions.then(()=>{
          self._eventChannel.send("fcf_initialize", {sender: self});
        });

        actions.then(()=>{
          let restart = false;
          let modules = [];
          fcf.each(packageLoader.getPackages(), (a_key, a_package)=>{
            if (a_package.getUpdateFlag()){
              restart = true;
              modules.push(a_package.getName());
            }
          })
          if (restart) {
            fcf.log.log("FCF", `Modules have been updated [${modules.join(", ")}]`);
            return fcf.application.getEventChannel().send({name: "fcfserver_restart_processes"}, {inner: true});
          }
        });

        actions.then(async ()=>{
          if (process.argv[2] != "--set-user-password")
            return;
          try {
            let records = (await fcf.application.getStorage().query({
              type:   "select",
              from:   fcf.application.getConfiguration().userProjectionName,
              fields: [{field: "user"}],
              where:  [{type: "=", args:[{field: "user"}, {value: process.argv[3]}]}]
            }))[0];

            if (!fcf.count(records)){
              throw new fcf.Exception("ERROR", {error: "User not found"});
            }

            records = (await fcf.application.getStorage().query({
              type:   "update",
              from:   fcf.application.getConfiguration().userProjectionName,
              where:  [{type: "=", args:[{field: "user"}, {value: process.argv[3]}]}],
              values: [{field: "password", value: process.argv[4]}],
            }));
          } catch (e){
            fcf.log.err("FCF", e.message);
            process.exit(1);
          }
          process.exit();
        });


        actions.catch((a_error)=>{
          fcf.log.err("FCF", a_error);
          process.exit(1);
        });

        return actions;
      }

      getSystemVariable(a_package, a_variable){
        if (a_variable == undefined){
          a_variable = a_package.split(":")[1];
          a_package  = a_package.split(":")[0];
        }
        let vars = fcf.NSystem.cache.get("fcf", "variables");
        return vars && vars[a_package] ? vars[a_package][a_variable] : undefined;
      }

      loadSystemVariable(a_package, a_variable) {
        if (a_variable == undefined) {
          a_variable = a_package.split(":")[1];
          a_package  = a_package.split(":")[0];
        }
        return fcf.application.getStorage().query({
          type: "select",
          from: "___fcf___variables",
          fields: [{field: "value"}],
          where:  [{logic: "and", type: "=", args: [{field: "package"}, {value: a_package}]},
                   {logic: "and", type: "=", args: [{field: "name"},    {value: a_variable}]}
                  ],
        })
        .then((a_result)=>{
          let value = a_result[0][0] ? a_result[0][0].value : undefined;
          if (value !== undefined){
            try {
              value = JSON.parse(value);
            } catch(e) {
            }
          }
          return value;
        });
      }


      setSystemVariable(a_package, a_name, a_value){
        if (arguments.length < 3){
          a_value   = a_name;
          a_name    = a_package.split(":")[1];
          a_package = a_package.split(":")[0];
        }

        let originValue = a_value;

        if (typeof a_value == "object" && a_value !== null){
          a_value = JSON.stringify(a_value);
        }

        var query = {
          type:   "update",
          from:   "___fcf___variables",
          values: [{field: "value", value: a_value}],
          where:  [{logic: "and", type: "=", args: [{field: "package"}, {value: a_package}]},
                   {logic: "and", type: "=", args: [{field: "name"},    {value: a_name}]}
                  ],
        }
        return fcf.application.getEventChannel().send(
                          "fcf_set_system_variable_before",
                          {package: a_package, variable: a_name, value: originValue})
        .then(() => {
          return fcf.application.getStorage().query({query: query, roles: ["root"]});
        })
        .then(() => {
          return fcf.application.getEventChannel().send(
                          "fcf_set_system_variable_after",
                          {package: a_package, variable: a_name, value: originValue}
                          );
        })
        .catch((error)=>{
          fcf.log.err("FCF", `Can't set system variable ${a_package}:${a_name}`);
        })
      }

      isAvailable() {
        return true;
      }

      isRunning() {
        return this._isRunning;
      }

      getPackages() {
        return packageLoader.getPackages();
      }

      getTheme(a_name, a_noException) {
        let themes = packageLoader.getThemes();
        if (fcf.empty(a_name))
          a_name = fcf.application.getConfiguration().defaultTheme;
        if (!themes[a_name] && !a_noException)
          throw new fcf.Exception("ERROR_THEME_NOT_FOUND", { theme: a_name});
        return themes[a_name];
      }

      getCron(){
        return this._cron;
      }

      getSystemActions(){
        if (this._stop)
          return fcf.actions().then((a_res, a_act)=>{});
        let self = this;
        let id = fcf.uuid();
        this._systemActions[id] = fcf.actions();
        setTimeout(() => {
          self._systemActions[id].finally(()=>{
            delete self._systemActions[id];
          })
        },10);
        return this._systemActions[id];
      }

      _stopSystemActions(){
        this._stop = true;

        return fcf.actions()
        .each(this._systemActions, (a_key, a_actions, a_res, a_act)=>{
          a_actions.finally(()=>{
            a_act.complete();
          })
        })
      }

      getInheritanceModes() {
        return this._configuration.inheritanceModes;
      }

      getEventChannel() {
        return this._eventChannel;
      }

      render(a_options) {
        return this._render.render(a_options);
      }

      getRender() {
        return this._render;
      }

      getProjections() {
        return this._projections;
      }

      getStorage() {
        return this._storage;
      }

      getConfiguration(){
        return this._configuration;
      }

      getRouter() {
        return this._router;
      }

      getSystemContext(){
        return this._systemContext;
      }

      createSystemContext(a_groups, a_roles){
        let context = fcf.append(true, new fcf.Context(), this._systemContext);
        if (typeof a_roles === "string")  a_roles = [a_roles];
        if (typeof a_groups === "string") a_groups = [a_groups];
        context.session.user.groups = fcf.map(a_groups, (k, v)=>{ return [v,v]});
        context.session.user.roles = fcf.map(a_roles, (k, v)=>{ return [v,v]});
        return context;
      }

      run() {
        var self = this;
        fcf.setContext(fcf.application.getSystemContext());

        if (this.getConfiguration().clearDuplicatesMode){
          return fcf.actions()
          .then(()=>{
            return self._clearDuplicates();
          })
          .then(()=>{
            process.exit(0);
          })
          .catch((e)=>{
            fcf.log.err("", e);
            process.exit(0);
          })
        }

        return fcf.actions()
        .then(async ()=>{
          try {
            await libUtil.promisify(libFS.mkdir)(fcf.getPath(":cache/tmp/fs-operations"), { recursive: true });
          } catch(e){
          }
        })

        .then(()=>{
          return fcf.application.getEventChannel().send("fcf_run_after");
        })
        .then(()=>{
          if (!self.getConfiguration().disableCron) {
            self._cron.run();
          }

          if (!self.getConfiguration().disableWeb) {
            let maxSizeReceived = self.getConfiguration().maxSizeReceived;
            let maxUrlCountParametersReceived = self.getConfiguration().maxUrlCountParametersReceived;

            self._express.use(libBodyParser.urlencoded({extended: true, parameterLimit: maxUrlCountParametersReceived, limit: maxSizeReceived,}));
            self._express.use(libBodyParser.json({parameterLimit: maxUrlCountParametersReceived, limit: maxSizeReceived}));
            self._express.use(libCookieParser());
            self._express.use(function(a_req, a_resp, a_next){
              fcf.setContext(new fcf.Context());
              self._doRequest(fcf.getContext(), a_req, a_resp, a_next);
            });

            let server = self.getConfiguration().host ? self._express.listen(self.getConfiguration().port, self.getConfiguration().host)
                                                      : self._express.listen(self.getConfiguration().port);

            server.keepAliveTimeout = self.getConfiguration().keepAliveTimeout*1000;

            fcf.log.log("FCF", "Listing on port " + self.getConfiguration().port);
          }

          fcf.log.log("FCF", "Applications is running ...");
        })
        .then(()=>{
          self._isRunning = true;
        });
      }

      sendEvent(a_event, a_options){
        return this._sendEvent(a_event, a_options, false, false);
      }

      _sendEvent(a_event, a_options, a_response, a_error) {
        if (!this._serverSocket)
          return fcf.actions().then(()=>{ return a_event; });

        if (a_event.name == "get_long_live_data")
          a_event.name = "accept_long_live_data";

        let eventId = a_options.id;
        if (!eventId){
          eventId = "";
          for(var i = 0; i < 18; ++i) {
            let value = Math.round(Math.random()*127);
            if (!value)
              value = 1;
            eventId += String.fromCharCode(value);
          }
        }
        let processPair  = a_options.processPair !== "*" ? a_options.processPair : -1;
        let handlerName = a_options.handler !== "*" ? a_options.handler : "";
        if (a_options.inner || !handlerName)
          handlerName = "";


        let actions = fcf.actions();
        let act = undefined;
        actions.then((a_res, a_act)=>{ act = a_act;});

        if (!a_response) {
          this._events[eventId] = {
            event:    a_event,
            options:  a_options,
            act:      act,
          };
        }
        let bheader = Buffer.from(new Uint8Array(EVENT_PACKAGE_HEADER_SIZE));
        let sevent = {};
        for(let key in a_event){
          sevent[key] = a_event[key];
        }
        delete sevent.eventItems;

        let aevent = [bheader];
        if (!a_error) {
          let counter = 0;
          let eventItemsSize = Number.isInteger(a_options.eventItemsSize) ? a_options.eventItemsSize : 0;
          fcf.each(a_event.eventItems, (a_key, a_resultItem) => {
            if (!a_options.sync) {
              if (a_response) {
                ++counter;
                if (counter <= eventItemsSize)
                  return;
              }
            }
            let buffer;
            let flags = 0;
            if (a_resultItem instanceof Buffer){
              flags = flags | 1;
              buffer = a_resultItem;
            } else if (a_resultItem instanceof ArrayBuffer) {
              flags = flags | 2;
              buffer = Buffer.from(a_resultItem);
            } else if (a_resultItem instanceof Uint8Array) {
              flags = flags | 3;
              buffer = Buffer.from(a_resultItem);
            } else if (a_resultItem instanceof Int8Array) {
              flags = flags | 4;
              buffer = Buffer.from(a_resultItem);
            } else {
              buffer = Buffer.from(JSON.stringify(a_resultItem));
            }
            let bhitem = Buffer.alloc(6);
            bhitem.writeUInt8(0, 0);
            bhitem.writeUInt32LE(buffer.length, 1);
            bhitem.writeUInt8(flags, 5);
            aevent.push(bhitem);
            aevent.push(buffer);
          });
        }
        aevent.push(Buffer.from(JSON.stringify(!a_error ? sevent : {message: a_error.toString(), stack: a_error.stack}), "utf8"));

        let size = 0;
        for(let i = 1; i < aevent.length; ++i)
          size += aevent[i].length;

        bheader.writeUInt32LE(size);

        bheader.writeUInt8((a_response                                                ? 0x01 : 0) |
                           (a_options.inner                                           ? 0x02 : 0) |
                           (a_options.sync                                            ? 0x04 : 0) |
                           (a_error                                                   ? 0x08 : 0) |
                           (!a_error && !fcf.empty(a_event.eventItems)                ? 0x10 : 0) |
                           ( typeof a_options.error == "string" &&
                             (a_options.error.indexOf("full") != -1 ||
                              a_options.error.indexOf("connection") != -1)            ? 0x20 : 0) |
                           (a_options.active                                          ? 0x40 : 0) |
                           (a_options.noactive                                        ? 0x80 : 0)
                           ,
                           4);
        bheader.writeUInt8((a_options.ignoreSelfNoActiveCalls                         ? 0x01 : 0) |
                           (a_options.directed                                        ? 0x02 : 0) |
                           (fcf.NServer.Application.getActiveMessageState()           ? 0x04 : 0) |
                           (
                            (
                              a_options.lock == "global"          ? 4 :
                              a_options.lock == "global_handler"  ? 3 :
                              a_options.lock == "server"          ? 2 :
                              a_options.lock == "server_handler"  ? 1 :
                                                                    0
                            ) << 3
                           )
                           ,
                           5);

        for(let i = 0; i < 18; ++i)
          bheader.writeUInt8(eventId.charCodeAt(i), i + 6);

        //!!!!!!: need make for all languages (UTF8)
        let destinationServer = a_options.server && a_options.server != "*" ? a_options.server : "";
        let destinationServerLength = Math.min(destinationServer.length, 32);
        for(let i = 0; i < destinationServerLength; ++i)
          bheader.writeUInt8(destinationServer.charCodeAt(i), i + 24);
        for(let i = destinationServerLength; (destinationServerLength+i) < 32; ++i){
          bheader.writeUInt8(0, i + destinationServerLength + 24);
        }

        let handlerNameLength = Math.min(handlerName.length, 16);
        for(let i = 0; i < handlerNameLength; ++i)
          bheader.writeUInt8(handlerName.charCodeAt(i), i + 56);
        for(let i = handlerNameLength; (handlerNameLength+i) < 16; ++i){
          bheader.writeUInt8(0, i + handlerNameLength + 56);
        }

        bheader.writeInt16LE(processPair,  72);

        bheader.writeInt16LE(a_options.lockCounter ? a_options.lockCounter : 0,  74);

        let bevent = Buffer.concat(aevent);
        this._serverSocket.write(bevent);

        if (a_response) {
          act.complete();
        }

        return actions;
      }

      async _clearDuplicates(){
        fcf.log.log("FCF", "Removing duplicates in translations starts ...");
        let duplicates = {};
        await fcf.each(this.getProjections().getProjections(), async (a_projAlias, a_proj) => {
          if (a_projAlias.indexOf("___fcftranslate___") != 0)
            return;
          let originProjection = fcf.application.getProjections().get(a_projAlias.substr("___fcftranslate___".length));
          if (!originProjection)
            return;
          let exists = {};
          let records = (await fcf.application.getStorage().query(
                          `select ${originProjection.key} as ref,  ___fcftranslate___key as key, ___fcftranslate___language as lang from ${a_projAlias} ORDER BY ref ASC`))[0];

          fcf.each(records, (a_key, a_record) => {
            if (!(a_record.lang in exists))
              exists[a_record.lang] = {};
            if (a_record.ref in exists[a_record.lang]){
              if (!(a_projAlias in duplicates))
                duplicates[a_projAlias] = [];
              duplicates[a_projAlias].push(a_record);
            } else {
              exists[a_record.lang][a_record.ref] = a_record;
            }
          })
        });

        for(let projection in duplicates){
          let recs = duplicates[projection];
          for(let i = 0; i < recs.length; ++i){
            await fcf.application.getStorage().query(`DELETE FROM ${projection} WHERE key() = \${1}`, [recs[i].key]);
          }
        }
      }

      async _updateNodeDependencies(){
          let config = await packageLoader.loadConfiguration("fcf")
          await packageLoader.loadConfiguration(fcf.application.getConfiguration().defaultTheme, config);
          await fcf.each(config.packages, async (a_key, a_packName)=>{
            await packageLoader.loadConfiguration(a_packName, config);
          });
          await nodeManager.updateDependencies(config.nodeDependencies);
      }

      async _getControllerInfo(a_context, a_path, a_requestInfo, a_contextInfo) {
        let routingData;
        let routeInfo = new fcf.RouteInfo(a_path);
        try {
          this._initContextForRequest(a_context, undefined, a_contextInfo, routeInfo, a_requestInfo);
          let routingData  = await this._getRoutingDataForRequest(a_context, contextInfo && contextInfo.session ? contextInfo.session.id : undefined);
        } catch (e) {
        }
        return {
          shortMode: routingData ? routingData.requestInfo.shortMode : true,
        }
      }

      async _doRequest(a_context, a_req, a_resp, a_next) {
        let formData = {};
        let formFiles = {};

        try {
          if (!this._configuration.keepAliveTimeout)
            a_resp.set("Connection", "close");

          let routeInfo = new fcf.RouteInfo({request: a_req});

          let contextInfo = undefined;
          if (a_req.cookies["___fcf___context"]) {
            try { contextInfo = JSON.parse(fcf.base64Decode(a_req.cookies["___fcf___context"])); } catch(e){ }
          }
          if(!contextInfo && !fcf.empty(routeInfo.args.___fcf___context)) {
              contextInfo = routeInfo.args.___fcf___context;
          }
          if (!contextInfo && a_req.header("fcf-context")) {
            try { contextInfo = JSON.parse(fcf.base64Decode(a_req.header("FCF-Context"))); } catch(e) {}
          }

          this._initContextForRequest(a_context, undefined, contextInfo, routeInfo, a_req);
          let routingData  = await this._getRoutingDataForRequest(a_context, contextInfo && contextInfo.session ? contextInfo.session.id : undefined);

          if (a_context.session.id) {
            await fcf.application.getStorage().query("UPDATE ___fcf___sessions SET last = ${1} WHERE session_id = ${2}",
                                                    [fcf.dateFormat(new Date(), "Y-m-d H:i:s"), a_context.session.id],
                                                    {roles: ["root"]} );
          }

          if (a_req.headers["content-type"] &&  a_req.headers["content-type"].indexOf("multipart/form-data") == 0) {
            let form = new libFormidable.IncomingForm();
            form.maxFileSize = 1024*1024*1024*1024;
            await fcf.actions()
            .then((a_res, a_act)=>{
              form.parse(a_req, function (a_error, a_fields, a_files) {
                if (!a_error) {
                  formData = a_fields;
                  fcf.each(a_files, (a_key, a_fileInfo)=>{
                    formFiles[a_key] = {
                      size:       a_fileInfo.size,
                      path:       a_fileInfo.path ? a_fileInfo.path : a_fileInfo.filepath,
                      name:       a_fileInfo.name ? a_fileInfo.name : a_fileInfo.originalFilename,
                      type:       a_fileInfo.type ? a_fileInfo.type : a_fileInfo.mimetype,
                      attributes: a_fileInfo.attributes
                    };
                  });
                }
                a_act.complete();
              })
            });
          }

          let request = new Request({ routeInfo:        a_context.route,
                                      expressResponse:  a_resp,
                                      expressRequest:   a_req,
                                      formData:         formData,
                                      files:            formFiles,
                                    });

          request.setNext(function(){
            fcf.actions()
            .each(formFiles, (a_key, a_fileInfo, a_res, a_act)=>{
              libFS.unlink(a_fileInfo.path, ()=>{
                a_act.complete();
              })
            })
            .then(()=>{
              request.flush();
              request.next();
              a_context.destroy();
            });
          });

          routingData.controller.action(request);

        } catch(error){
          fcf.actions()
          .each(formFiles, (a_key, a_fileInfo, a_res, a_act) => {
            libFS.unlink(a_fileInfo.path, ()=>{ a_act.complete(); });
          })
          .then(()=>{
            fcf.log.err("FCF:Request", error);
            this._doRequestHTMLError(error, a_resp, a_req);
          })
        }
      }

      async _getRoutingDataForRequest(a_context, a_sessionId){
        fcf.setContext(a_context);
        let node = await this._router.getNode(a_context.route.uri);
        let controller = undefined;
        let requestInfo = undefined;
        if (node && node.node && node.node.endpoint && !node.userRequest){
          let [Controller] = await fcf.require([node.node.endpoint.controller]);
          controller = new Controller({ endpoint: node.node.endpoint, route: a_context.route });
          requestInfo = controller.getRequestInfo();
        }

        if ((!controller || requestInfo.userRequest) && a_sessionId) {
          do {
            let session = (await fcf.application.getStorage().query({
              query:    "SELECT * from ___fcf___sessions WHERE session_id = ${1}",
              args:     [a_sessionId],
              roles:    ["root"],
            }))[0][0];
            if (!session)
              break;

            let user = (await fcf.application.getStorage().query({
                query: "SELECT * from \"" + fcf.application.getConfiguration().userProjectionName + "\" WHERE id = ${1}",
                args:  [session.user_id],
                roles: ["root"],
            }))[0][0];
            if (!user)
              break;
            if (!user.active || (!fcf.application.getConfiguration().loginUncreatedUser && !user.initialized))
              break;

            for(let i = 0; i < user.groups.length; ++i) {
              a_context.session.user.groups[user.groups[i].name] = user.groups[i].name;
              for(let j = 0; j < user.groups[i].roles.length; ++j) {
                a_context.session.user.roles[user.groups[i].roles[j].name] = user.groups[i].roles[j].name;
              }
            }

            a_context.session.id = a_sessionId;
            a_context.session.user.user = user.user;
            let sharedUserFields = fcf.application.getConfiguration().sharedUserFileds;
            for(let i = 0; i < sharedUserFields.length; ++i) {
              a_context.session.user[sharedUserFields[i]] = user[sharedUserFields[i]];
            }

            node = await this._router.getNode(a_context.route.uri);
            if (node && node.node && node.node.endpoint){
              let [Controller] = await fcf.require([node.node.endpoint.controller]);
              controller = new Controller({endpoint: node.node.endpoint, route: a_context.route});
            }

          } while (false);
        }

        if (!node || !controller)
          throw new fcf.Exception("ERROR_404", {"address": a_context.route.url});

        for(let key in node.args)
          a_context.route.args[key] = node.args[key];

        a_context.route.subUri       = node.subUri;
        a_context.route.title        = node.node.title;
        a_context.route.description  = node.node.endpoint && node.node.endpoint.description ? node.node.endpoint.description : "";

        return { node: node, controller: controller, requestInfo: requestInfo };
      }

      _initContextForRequest(a_context, a_contextInfo, a_clientContextInfo, a_routeInfo, a_req){
        if (a_clientContextInfo) {
          for (let key in a_clientContextInfo)
            if (key != "safeEnv")
              a_context[key] = a_clientContextInfo[key];
        }

        if (!a_contextInfo){
          a_context.session = {id: undefined, user: {roles: {}, groups: {}} };

          if (!("debug" in a_context))
            a_context.debug = false;

          a_context.route = a_routeInfo;

          a_context.needBabel = fcf.application.getConfiguration().enableBabel && babel.isNeedCompile(a_req.header("user-agent"));

          let languageIdentification = fcf.application.getSystemVariable("fcf:languageIdentification");
          let avalibleLanguages      = fcf.application.getSystemVariable("fcf:languages");
          if (!languageIdentification.byCookie || !(a_context.language in avalibleLanguages)) {
            a_context.language = fcf.application.getSystemVariable("fcf:defaultLanguage");
            if (languageIdentification.byHTTP && a_req.headers['accept-language']) {
              let bl = undefined;
              let bq = undefined;
              let alArr = a_req.headers['accept-language'].split(",");
              for(var i = 0; i < alArr.length; ++i) {
                let l = alArr[i].substr(0, 2);
                let q = parseFloat(alArr[i].split("q=")[1]);
                if (isNaN(q))
                  q = 0;
                if (l in avalibleLanguages && (bq === undefined || q > bq)){
                  bl = l;
                  bq = q;
                }
              }
              if (bl)
                a_context.language = bl;
            }

            if (languageIdentification.byParameter) {
              if (a_routeInfo.args[languageIdentification.parameter] in avalibleLanguages)
                a_context.language = a_routeInfo.args[languageIdentification.parameter];
            }

            if (languageIdentification.byPrefix) {
              let uriArr = fcf.trim(a_routeInfo.uri,"/").split("/");
              let lang = uriArr[0];
              if (lang.length==2 && lang in avalibleLanguages) {
                uriArr.shift();
                a_context.language = lang;
                a_context.route.uri = uriArr.join("/");
              }
            }
          }
        } else {
          for (let key in a_contextInfo)
            if (key != "safeEnv")
              a_context[key] = a_contextInfo[key];
        }
      }

      _doRequestHTMLError(a_error, a_resp, a_req) {
        let request = new Request({
                                  application:      this,
                                  render:           this.getRender(),
                                  routeData:        fcf.getContext().route,
                                  context:          fcf.getContext(),
                                  expressResponse:  a_resp,
                                  expressRequest:   a_req,
                                  formData:         undefined,
                                  files:            undefined,
                                });
        request.setNext(function(){
          request.flush();
          request.next();
          fcf.getContext().destroy();
        });

        if (fcf.Exception.is(a_error, "ERROR_ROUTER_URL_INCORRECT") || fcf.Exception.is(a_error, "ERROR_ROUTER_URL_NOT_FOUND") || fcf.Exception.is(a_error, "ERROR_ROUTER_URL_NOTFULL") || fcf.Exception.is(a_error, "ERROR_404")) {
          request.setStatus(404);
          var controller = new ControllerPage(
                              {
                                endpoint: {
                                  source: "@page:page404",
                                  args: {
                                    url: fcf.getContext().route.url,
                                  },
                                },
                                route: fcf.getContext().route,
                              }
                            );
        } else {
          let code = typeof a_error == "object" && a_error.responseCode ? a_error.responseCode : 500;
          request.setStatus(code);
          var controller = new ControllerPage(
                              {
                                endpoint: {
                                  source: "@page:system-error-page",
                                  args: {
                                    error: a_error,
                                  },
                                },
                                route: fcf.getContext().route,
                              }
                            );
        }
        controller.action(request);
      }
    }

    let stateStorage = new Map();
    let asyncHooks = libAsyncHooks.createHook({
      init: (a_id, a_type, a_triggerId)=>{
        let state = stateStorage.get(a_triggerId);
        if (state)
          stateStorage.set(a_id, state)
      },
      destroy: (a_id)=>{
        if (stateStorage.has(a_id))
            stateStorage.delete(a_id);
      }
    }).enable();

    fcf.NServer.Application.setContext = (a_context) => {
      let id = libAsyncHooks.executionAsyncId();
      let state = stateStorage.get(id);
      if (!state){
        stateStorage.set(id, { context: a_context });
      } else {
        stateStorage.set(id, { context: a_context, fromActiveMessageCall: state.fromActiveMessageCall });
      }
    };

    fcf.NServer.Application.getActiveMessageState = () => {
      let state = stateStorage.get(libAsyncHooks.executionAsyncId());
      return state ? state.fromActiveMessageCall : undefined;
    }

    fcf.NServer.Application.setActiveMessageState = (a_value) => {
      let id = libAsyncHooks.executionAsyncId();
      let state = stateStorage.get(id);
      if (!state){
        stateStorage.set(id, {fromActiveMessageCall: !!a_value});
      } else {
        stateStorage.set(id, { context: state.context, fromActiveMessageCall: !!a_value });
      }
    }

    fcf.NServer.Application.getContext = () => {
      let state = stateStorage.get(libAsyncHooks.executionAsyncId());
      return state ? state.context : undefined;
    };

    fcf.NServer.Application.getState = () => {
      return stateStorage.get(libAsyncHooks.executionAsyncId());
    };

    fcf.NServer.Application.setState = (a_state) => {
      if (typeof a_state !== "object")
        a_state = undefined;
      stateStorage.set(libAsyncHooks.executionAsyncId(), a_state);
    };


    fcf.NServer.Application._emptyContext = new fcf.Context();
    fcf.append(fcf.NServer.Application._emptyContext, {
      language: "en",
      session: {
        user: {
          groups: {},
          roles:  {},
          user:   "FCF EMPTY CONTEXT",
        }
      }
    });
    fcf.NServer.Application.getEmptyContext = () => {
      let stack = (new Error()).stack;
      if (fcf.application && fcf.application._isRunning)
        fcf.log.wrn("FCF", "In system using empty context !!!", stack);
      return fcf.NServer.Application._emptyContext;
    }


    fcf.application = new fcf.NServer.Application();

    return fcf.application;
  }

});
