var libExpress      = undefined;
var libBodyParser   = undefined;
var libCookieParser = undefined
var libNet          = undefined;
var libFormidable   = undefined;
var libFS           = require('fs');
var libPath         = require('path');
var libAsyncHooks   = require("async_hooks");

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",
                ],
  lazy:         [],
  module: function(Configuration, packageLoader, packageTools, nodeManager) {
    var ControllerPage;
    var Router;
    var Request;
    var Projections;
    var Storage;
    var Render;
    var Cron;
    var cache;
    var fcfFS;
    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._controlSocket = undefined;
        this._actions       = fcf.actions();
        this._eventChannel = fcf.NDetails.eventChannel;
        this._eventChannel.setOwner(this);
        this._cron = undefined;
        this._msa = {};
        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("--control-port") != -1 && process.argv[process.argv.indexOf("--control-port")+1])
          overwriteConfiguration.controlPort = process.argv[process.argv.indexOf("--control-port")+1];

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

        if (process.argv.indexOf("--group") != -1 && process.argv[process.argv.indexOf("--group")+1])
          overwriteConfiguration.serverGroup = process.argv[process.argv.indexOf("--group")+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.slave                   = fcf.find(process.argv, "--slave") !== undefined;

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

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

        this._eventChannel.attach({name: "initialize", object: {}, sender: this});
        this._storage = undefined;

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

        clLoadConfigFile(":settings.config");

        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("  --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")
          process.exit();
        }

        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/Cron.js",
                                  "fcf:NTools/fs.js",
                                  "fcf:NSystem/babel.js",
                                                   ]);
          ControllerPage = modules[0];
          Router         = modules[1];
          Request        = modules[2];
          Projections    = modules[3];
          Storage        = modules[4];
          Render         = modules[5];
          Cron           = modules[6];
          fcfFS          = modules[7];
          babel          = modules[8];



          self._express       = libExpress();
          self._cron          = new Cron();
          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(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);
          fcfFS.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("initialize", {sender: self});
        });

        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(
                          "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(
                          "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._msa[id] = fcf.actions({context: self.getSystemContext()});
        setTimeout(() => {
          self._msa[id].finally(()=>{
            delete self._msa[id];
          })
        },10);
        return this._msa[id];
      }

      _stopSystemActions(){
        this._stop = true;
        return fcf.actions()
        .each(this._msa, (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];
        let g = fcf.map(a_groups, (k, v)=>{ return [v,v]});
        let r = fcf.map(a_roles, (k, v)=>{ return [v,v]});
        context.session.user.groups = g;
        context.session.user.roles = r;
        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(()=>{
          return fcf.application.getEventChannel().send("run_after", {});
        })
        .then(()=>{

          self._isRunning = true;

          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){
              self._doRequest(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);
          }

          if (self.getConfiguration().controlPort){
            self._controlSocket = libNet.createServer(function(socket) {
              socket._preffix = Buffer.alloc(0);
              socket.on("error", function(a_data){
              });
              socket.on("data", function(a_data){
                try {
                  if (socket._preffix.length != 0){
                    a_data = Buffer.concat([socket._preffix, a_data]);
                    socket._preffix = Buffer.alloc(0);
                  }
                  let events = [];
                  let startPos = 0;
                  while(startPos < a_data.length){
                    let hrp = a_data.indexOf("\n", startPos);
                    if (hrp == -1){
                      fcf.log.err("FCF:INNER_COMMAND", "FAILED INNER COMMAND:", "Header invalid");
                      socket._preffix = Buffer.alloc(0);
                      try { socket.write("18\n{\"state\": \"error\"}"); } catch (e) {}
                      return;
                    }
                    let length = parseInt(a_data.slice(startPos, hrp).toString('utf8'));
                    if (hrp + 1 + length > a_data.length){
                      socket._preffix = Buffer.concat([socket._preffix, a_data]);
                      return;
                    }
                    let content = a_data.slice(hrp+1, hrp+1 + length).toString('utf8');
                    let event;
                    try {
                      event = JSON.parse(content);
                    } catch(e){
                      fcf.log.err("FCF:INNER_COMMAND", "FAILED INNER COMMAND", content, e);
                      socket._preffix = Buffer.alloc(0);
                      try { socket.write("18\n{\"state\": \"error\"}"); } catch (e) {}
                      return;
                    }
                    if (event.type == "event"){
                      event.data = fcf.append(new fcf.Event(), event.data)
                      event.data.prohibitionGlobal = true;
                    }
                    events.push(event);
                    startPos = hrp + 1 + length;
                  }

                  fcf.each(events, function(a_key, a_event) {
                    if (a_event.type == "event") {
                      self.getEventChannel().send(a_event.data)
                      .then(function(){
                        let message = "15\n{\"state\": \"ok\"}"
                        try {
                          socket.write(message);
                        } catch(e){
                        }
                      })
                      .catch(function(){
                        let message = "15\n{\"state\": \"ok\"}"
                        try {
                          socket.write(message);
                        } catch(e) {
                        }
                      })
                    } if (a_event.type == "check_memory_leak") {
                      self._getControllerInfo(a_event.data.path, a_event.data.context)
                      .then((a_info)=>{
                        try {
                          socket.write(
                            (a_info.memoryLeakProtection ? "1" : "0") + " " +
                            a_info.clientSocketTimeout    + " " +
                            a_info.maxRequestTimeout      + " " +
                            a_info.serverSocketTimeout    + " " +
                            a_info.maxResponseTimeout     + " "
                          );
                        } catch(e) {

                        }
                      })
                      .catch((a_error)=>{
                        try {
                          socket.write("0 -1 -1 -1 -1 ");
                        } catch(e) { }
                      })
                    } if (a_event.type == "get_pid") {
                      try {
                        socket.write(process.pid.toString());
                      } catch(e) { }
                    }
                  })
                } catch(error){
                  fcf.log.err("", "Failed process inner message", error);
                }
              });
            });
            self._controlSocket.on('error', function (e) {
              fcf.log.err("FCF", "Inner command socket error:", e);
            });
            self._controlSocket.on('uncaughtException', function (e) {
              fcf.log.err("FCF", "Inner command socket uncaught exception:", e, e.stack);
            });
            self._controlSocket.listen(self.getConfiguration().controlPort, "localhost");
          }

          fcf.log.log("FCF", "Applications is running ...");
        });
      }

      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);
      }

      _getControllerInfo(a_path, a_context) {
        let self = this;
        let request = undefined;
        let nodeInfo = undefined;
        let eventPreRoute = undefined;
        let formData = {};
        let formFiles = {};
        let sentContext = false;
        let defaultRouteInfo = new fcf.RouteInfo("/" + a_path);
        let routeInfo = defaultRouteInfo;
        let context = new fcf.Context();
        context.set("language", fcf.application.getSystemVariable("fcf:defaultLanguage"));
        context.set("debug", false);
        context.set("session", {user: {roles: {}, groups: {}} });
        fcf.setContext(context);
        return fcf.actions()
        .then(()=>{
          // Fill context
          try {
            sentContext = JSON.parse(fcf.base64Decode(a_context));
          } catch(e) {}
          if (!sentContext) {
            sentContext = {};
          }

          for(var k in sentContext)
            if (k != "_id" && k != "language" && k != "safeEnv")
              context.set(k, sentContext[k]);

          if (typeof context.session !== "object")
            context.session = {};
          context.session.user = {};
          context.session.user.groups = {};
          context.session.user.roles = {};
          let newRoute = new fcf.RouteInfo(routeInfo);
          context.set("route", newRoute);
          return {};
        })
        .then((a_info)=>{
          return self._router.getNode(routeInfo.uri)
          .then((a_nodeInfo)=>{
            a_info.nodeInfo = a_nodeInfo;
            return a_info;
          })
        })
        .then((a_info) => {
          if (!context.session.id)
            return a_info;

          if (a_info.nodeInfo && !a_info.nodeInfo.userImportance)
            return a_info;

          return fcf.application.getStorage().query({
            query:    "SELECT * from ___fcf___sessions WHERE session_id = ${1}",
            args:     [context.session.id],
            roles:    ["root"],
          })
          .then((a_records) => {
            if (!a_records[0][0]){
              context.session = {user: {roles: {}, groups: {}}};
              return a_info;
            }
            a_info.userId = a_records[0][0].user_id;

            return fcf.application.getStorage().query({
              query: "SELECT * from \"" + fcf.application.getConfiguration().userProjectionName + "\" WHERE id = ${1}",
              args:  [a_info.userId],
              roles: ["root"],
            })
            .then((a_records)=>{
              let user = fcf.append({}, a_records[0][0]);
              //TODO Бросать ошибку при отсутствии user | user.groups | user.active
              if (!user || !user.groups){
                a_info.userId        = undefined;
                context.session.user = {};
                context.set("session", context.session);
                return a_info;
              }

              if (!user.active || (!fcf.application.getConfiguration().loginUncreatedUser && !a_records[0][0].initialized)){
                context.session.user = {};
                context.set("session", context.session);
                return a_info;
              }

              let sharedUserFileds = fcf.application.getConfiguration().sharedUserFileds;
              let roles = {};
              let groups = {};
              for(let i = 0; i < user.groups.length; ++i){
                groups[user.groups[i].name] = user.groups[i].name;
                for(let j = 0; j < user.groups[i].roles.length; ++j){
                  roles[user.groups[i].roles[j].name] = user.groups[i].roles[j].name;
                }
              }
              for(let k in user)
                if (fcf.find(sharedUserFileds, k) === undefined && k != "user")
                  delete user[k];
              user.roles = roles;
              user.groups = groups;
              context.session.user = user;
              context.set("session", context.session);
              return a_info;
            })
          })
        })
        .then((a_info)=>{
          if (a_info.nodeInfo && !a_info.nodeInfo.userImportance)
            return a_info;
          return self._router.getNode(routeInfo.uri)
          .then((a_nodeInfo)=>{
            a_info.nodeInfo = a_nodeInfo;
            return a_info;
          })
        })
        .then((a_info)=>{
          if (!a_info.nodeInfo)
            throw new fcf.Exception("ERROR_404", {"address": routeInfo.url});
          let r = context.get("route");
          fcf.append(r.args, a_info.nodeInfo.args);
          r.subUri      = a_info.nodeInfo.subUri;
          r.title       = a_info.nodeInfo.node.title;
          r.description = a_info.nodeInfo.node.endpoint && a_info.nodeInfo.node.endpoint.description ? a_info.nodeInfo.node.endpoint.description : "";
          context.set("route", r);
          return a_info;
        })
        .then((a_info, a_act)=>{
          if (!a_info.nodeInfo.node.endpoint)
            throw new fcf.Exception("ERROR_404", {address: context.route.url})

          fcf.requireEx([a_info.nodeInfo.node.endpoint.controller], function(a_error, Controller) {
            if (a_error){
              a_act.error(a_error);
              return;
            }
            let controller = new Controller(a_info.nodeInfo.node.endpoint);
            context.destroy();
            a_act.complete({
              memoryLeakProtection: controller.memoryLeakProtection,
              clientSocketTimeout:  controller.clientSocketTimeout,
              maxRequestTimeout:    controller.maxRequestTimeout,
              serverSocketTimeout:  controller.serverSocketTimeout,
              maxResponseTimeout:   controller.maxResponseTimeout
            });
          });
        })
        .catch(()=>{
          context.destroy();
        })
      }

      _doRequest(a_req, a_resp, a_next) {
        let self = this;
        let nodeInfo = undefined;
        let eventPreRoute = undefined;
        let formData = {};
        let formFiles = {};
        let sentContext = false;
        let defaultRouteInfo = new fcf.RouteInfo({request: a_req});
        let routeInfo = defaultRouteInfo;
        let context = new fcf.Context();
        context.set("language", fcf.application.getSystemVariable("fcf:defaultLanguage"));
        context.set("debug", false);
        context.set("session", {user: {roles: {}, groups: {}} });
        fcf.setContext(context);

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

        return fcf.actions()
        .then((a_info, a_act)=>{
          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;
            form.parse(a_req, function (err, a_fields, a_files) {
              if (err) {
                a_act.complete({});
                return;
              }
              formData = a_fields;
              formFiles = a_files;
              a_act.complete({});
            })
          } else {
            a_act.complete({});
          }
        })
        .then((a_info)=>{
          // Fill context
          if (a_req.cookies["___fcf___context"]){
            try {
              sentContext = JSON.parse(fcf.base64Decode(a_req.cookies["___fcf___context"]));
            } catch(e){ }
          }
          if(!sentContext && !fcf.empty(defaultRouteInfo.args.___fcf___context)){
            sentContext = defaultRouteInfo.args.___fcf___context;
          }
          if (!sentContext && a_req.header("FCF-Context")){
            try {
              sentContext = JSON.parse(fcf.base64Decode(a_req.header("FCF-Context")));
            } catch(e) {}
          }
          if (!sentContext){
            sentContext = {};
          }

          for(var k in sentContext)
            if (k != "_id" && k != "language" && k != "safeEnv")
              context.set(k, sentContext[k]);

          if (typeof context.session !== "object")
            context.session = {};

          context.session.user = {};
          context.session.user.groups = {};
          context.session.user.roles = {};

          context.set("needBabel", fcf.application.getConfiguration().enableBabel &&
                                   babel.isNeedCompile(a_req.header("user-agent")));

          let newRoute = new fcf.RouteInfo(routeInfo);
          context.set("route", newRoute);

          // Detect language
          var languageIdentification = fcf.application.getSystemVariable("fcf:languageIdentification");
          var avalibleLanguages      = fcf.application.getSystemVariable("fcf:languages");

          if (languageIdentification.byHTTP){
            var al = a_req.headers['accept-language'];
            if (al){
              var alArr = al.split(",");
              for(var i = 0; i < alArr.length; ++i){
                var l = fcf.trim(alArr[i]).substr(0,2);
                if (l in avalibleLanguages){
                  context.set("language", l);
                  break;
                }
              }
            }
          }
          if (languageIdentification.byCookie){
            if (sentContext.language in avalibleLanguages)
              context.set("language", sentContext.language);
          }
          if (languageIdentification.byParameter){
            if (routeInfo.args[languageIdentification.parameter] in avalibleLanguages)
              context.set("language", routeInfo.args[languageIdentification.parameter]);
          }
          if (languageIdentification.byPrefix){
            var uri = routeInfo.uri;
            var uriArr = fcf.trim(uri,"/").split("/");
            let lang = uriArr[0];
            if (lang.length==2 && lang in avalibleLanguages){
              uriArr.shift();
              uri = uriArr.join("/")
              context.set("language", lang);
              routeInfo.uri = uri ;
            }
          }

          return a_info;
        })
        .then((a_info)=>{
          return self._router.getNode(routeInfo.uri)
          .then((a_nodeInfo)=>{
            a_info.nodeInfo = a_nodeInfo;
            return a_info;
          })
        })
        .then((a_info, a_act)=>{
          if (!a_info.nodeInfo || !a_info.nodeInfo.node || !a_info.nodeInfo.node.endpoint || a_info.nodeInfo.userImportance){
            a_act.complete(a_info);
            return;
          }

          fcf.requireEx([a_info.nodeInfo.node.endpoint.controller], function(a_error, Controller) {
            if (a_error) {
              a_act.error(a_error);
              return;
            }
            try {
              a_info.controller = new Controller(a_info.nodeInfo.node.endpoint);
            } catch(e){
              a_act.error(e);
              return;
            }
            a_act.complete(a_info);
          });
        })
        .then((a_info) => {
          if (!context.session.id)
            return a_info;

          if (a_info.nodeInfo && !a_info.nodeInfo.userImportance && (!a_info.controller || !a_info.controller.userImportance))
            return a_info;

          return fcf.application.getStorage().query({
            query:    "SELECT * from ___fcf___sessions WHERE session_id = ${1}",
            args:     [context.session.id],
            roles:    ["root"],
          })
          .then((a_records) => {
            if (!a_records[0][0]){
              context.session = {user: {roles: {}, groups: {}}};
              return a_info;
            }
            a_info.userId = a_records[0][0].user_id;

            return fcf.application.getStorage().query({
              query: "SELECT * from \"" + fcf.application.getConfiguration().userProjectionName + "\" WHERE id = ${1}",
              args:  [a_info.userId],
              roles: ["root"],
            })
            .then((a_records)=>{
              let user = fcf.append({}, a_records[0][0]);
              //TODO Бросать ошибку при отсутствии user | user.groups | user.active
              if (!user || !user.groups){
                a_info.userId        = undefined;
                context.session.user = {};
                context.set("session", context.session);
                return a_info;
              }

              if (!user.active || (!fcf.application.getConfiguration().loginUncreatedUser && !a_records[0][0].initialized)){
                context.session.user = {};
                context.set("session", context.session);
                return a_info;
              }

              let sharedUserFileds = fcf.application.getConfiguration().sharedUserFileds;
              let roles = {};
              let groups = {};
              for(let i = 0; i < user.groups.length; ++i){
                groups[user.groups[i].name] = user.groups[i].name;
                for(let j = 0; j < user.groups[i].roles.length; ++j){
                  roles[user.groups[i].roles[j].name] = user.groups[i].roles[j].name;
                }
              }
              for(let k in user)
                if (fcf.find(sharedUserFileds, k) === undefined && k != "user")
                  delete user[k];
              user.roles = roles;
              user.groups = groups;
              context.session.user = user;
              context.set("session", context.session);
              return a_info;
            })
          })
        })
        .then((a_info)=>{
          if (a_info.nodeInfo && !a_info.nodeInfo.userImportance)
            return a_info;
          return self._router.getNode(routeInfo.uri)
          .then((a_nodeInfo)=>{
            a_info.nodeInfo = a_nodeInfo;
            return a_info;
          })
        })
        .then((a_info)=>{
          if (!a_info.nodeInfo)
            throw new fcf.Exception("ERROR_404", {"address": routeInfo.url});
          let r = context.get("route");
          fcf.append(r.args, a_info.nodeInfo.args);
          r.subUri       = a_info.nodeInfo.subUri;
          r.title        = a_info.nodeInfo.node.title;
          r.description  = a_info.nodeInfo.node.endpoint && a_info.nodeInfo.node.endpoint.description ? a_info.nodeInfo.node.endpoint.description : "";
          context.set("route", r);

          return fcf.application.getEventChannel().send("request", {context: context})
          .then(()=>{
            return a_info;
          })
        })
        .then((a_info, a_act)=>{
          a_info.request = new Request({
                                  application:      self,
                                  render:           self.getRender(),
                                  routeData:        context.route,
                                  context:          context,
                                  expressResponse:  a_resp,
                                  expressRequest:   a_req,
                                  formData:         formData,
                                  files:            formFiles,
                                });

          if (!a_info.nodeInfo.node.endpoint)
            throw new fcf.Exception("ERROR_404", {address: context.route.url})

          if (a_info.controller){
            a_act.complete(a_info);
            return;
          }

          fcf.requireEx([a_info.nodeInfo.node.endpoint.controller], function(a_error, Controller) {
            if (a_error){
              a_act.error(a_error);
              return;
            }
            try {
              a_info.controller = new Controller(a_info.nodeInfo.node.endpoint);
            } catch(e){
              a_act.error(e);
              return;
            }
            a_act.complete(a_info);
          });
        })
        // update session last time
        .then((a_info) => {
          if (!context.session.id)
            return a_info;
          if (!a_info.nodeInfo.userImportance && !a_info.controller.userImportance)
            return a_info;
          return fcf.application.getStorage().query("UPDATE ___fcf___sessions SET last = ${1} WHERE session_id = ${2}",
                                                    [fcf.dateFormat(new Date(), "Y-m-d H:i:s"), context.session.id],
                                                    {roles: ["root"]} )
          .then(()=>{
            return a_info;
          })
        })
        .then((a_info, a_act)=>{
          a_info.request.setNext(function(){
            fcf.actions()
            .each(formFiles, (a_key, a_fileInfo, a_res, a_act)=>{
              libFS.unlink(a_fileInfo.path, ()=>{
                a_act.complete();
              })
            })
            .then(()=>{
              a_info.request.flush();
              a_info.request.next();
              a_act.complete();
              context.destroy();
              a_info.request = null;
              context = null;
            })
          });
          return a_info.controller.action(a_info.request);
        })
        .catch((a_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", a_error);
            self._doRequestHTMLError(a_error, a_resp, a_req);
          })
        })
        .finally(()=>{
        })
      }

      _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(
                              { source: "@page:page404",
                                args: {
                                  url: fcf.getContext().route.url,
                                },
                              }
                            );
        } else {
          let code = typeof a_error == "object" && a_error.responseCode ? a_error.responseCode : 500;
          request.setStatus(code);
          var controller = new ControllerPage(
                              { source: "@page:system-error-page",
                                args: {
                                  error: a_error,
                                },
                              }
                            );
        }
        controller.action(request);
      }
    }


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

    fcf.NServer.Application.setContext = (a_context) => {
      storageContext.set(libAsyncHooks.executionAsyncId(), a_context);
    };

    fcf.NServer.Application.getContext = () => {
        return storageContext.get(libAsyncHooks.executionAsyncId());
    };

    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.geEmptyContext = () => {
      let stack = (new Error()).stack;
      if (fcf.application && fcf.application._isRunning)
        fcf.log.wrn("FCF", "In sytem using empty context !!!", stack);
      return fcf.NServer.Application._emptyContext;
    }


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

    return fcf.application;
  }

});
