var fcf = require("../fcf");
var express = require('express');
var modBodyParser = require('body-parser');
var modCookieParser = require('cookie-parser')
var modNet = require('net');
var modFormidable = require('formidable');
var libFS = require('fs');
var libPath = require('path');
var libAsyncHooks = require("async_hooks");
fcf.module({
  name: "fcf:NServer/Application.js",
  dependencies: ["fcf:NFSQL/Projections.js",
                 "fcf:NServer/NServer.js",
                 "fcf:NServer/Router.js",
                 "fcf:NServer/Request.js",
                 "fcf:NServer/NControllers/File.js",
                 "fcf:NServer/NControllers/Page.js",
                 "fcf:NFSQL/Storage.js",
                 "fcf:NRender/Render.js",
                 "fcf:NServer/Package.js",
                 "fcf:NEvent/Initialize.js",
                 "fcf:NTheme/Theme.js",
                 "fcf:NTheme/Themes.js",
                 "fcf:NServer/NEvent/FcfServerPreroute.js",
                 "fcf:NServer/NEvent/FcfServerFailedRoute.js",
                 "fcf:NServer/Configuration.js",
                 "fcf:NEvent/ControllerPreprocessing.js",
                 "fcf:NServer/Cron.js",
                 "fcf:NTools/cache.js",
                 "fcf:NFSQL/NDetails/DBBuilder.js",
                 "fcf:NTools/fs.js",
                 "fcf:NTools/babel.js",
                 ],
  module: function(Projections, NServer, Router, Request, ControllerFile, ControllerPage,
                   Storage, Render, Package, EventInitialize,
                   Theme, Themes, EventFcfServerPreroute, EventFcfServerFailedRoute,
                   Configuration, EventControllerPreprocessing, Cron, cache, DBBuilder, fcfFS, babel) {
    fcf.prepareObject(fcf, "NServer");

    fcf.NServer.Application = class Application {

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

        this._isRunning     = false;
        this._controlSocket = undefined;
        this._actions       = fcf.actions();
        this._eventChannel = fcf.NDetails.eventChannel;
        this._eventChannel.setOwner(this);
        this._cron = new Cron();

        //
        // 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.disableUpdateProjection = fcf.find(process.argv, "--disable-update-projection") !== undefined;
        overwriteConfiguration.slave                   = fcf.find(process.argv, "--slave") !== undefined;

        this._app               = express();
        this._router            = new Router();
        this._projections       = new Projections();
        this._packages          = {};
        this._render            = undefined;
        this._inheritanceModes  = {};
        this._configuration     = new Configuration();
        this._configuration.appendOverwriteConfiguration(overwriteConfiguration);

        this._eventChannel.attach({name: "initialize", object: {}, sender: this});
        this._storage = undefined;
        this._themes  = new Themes({
          eventChannel:      this._eventChannel,
          sources:           ["fcf:themes", "fcf:packages"],
          defaultTheme:      "defaultTheme",
          configuration:     this._configuration,
        });


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

        clLoadConfigFile(":settings.config");

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

      }

      initialize() {
        let self = this;

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

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

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

        try {
          libFS.mkdirSync(fcf.getPath(":cache/wrappers"), { recursive: true });
        } catch(e) {}
        try {
          libFS.mkdirSync(fcf.getPath(":cache/fileBuilder"), { recursive: true });
        } catch(e) {}
        try {
          libFS.mkdirSync(fcf.getPath(":cache/fileBuilderES5"), { recursive: true });
        } catch(e) {}


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

        actions.then(()=>{
          return self._loadCorePackage();
        })
        
        actions.then(()=>{
          return self._loadPackages();
        })
        
        actions.then(function(){
          return self._themes.initialize();
        })

        actions.then(()=>{
          if (!fcf.empty(self.getConfiguration().projectionDirectories)) {
            for(var i = 0; i < self.getConfiguration().projectionDirectories.length; ++i) {
              self._projections.loadFromDirectory(self.getConfiguration().projectionDirectories[i]);
            }
          }
        })

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

        actions.then(function(){
          if (self.getConfiguration().disableUpdateProjection)
            return;
          let directorySystemProjections = fcf.getPath(self.getConfiguration().directorySystemProjections);
          fcfFS.prepareDirectorySync(directorySystemProjections);

          let projections = self._projections.getProjections();
          fcf.each(projections, (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: [["___fcftranslate___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));
            self.getProjections().appendProjectionStruct(tprojetion);
          });
        });

        actions.then(function(a_res){
          if (self.getConfiguration().disableUpdateProjection)
            return;
          let dbBuilder = new DBBuilder(self._storage, self._projections.getProjections());
          return dbBuilder.build()
          .then(()=>{
            return dbBuilder.completionInitialization();
          })
        });

        actions.then(()=>{
          self._eventChannel.send(new EventInitialize({sender: self}));
        });

        actions.catch((a_error)=>{
          self._eventChannel.send(new EventInitialize({error: a_error, sender: self}));
          fcf.error("FCF", a_error.message);
          process.exit(1);
        });

        return actions;
      }


      selectSystemVariables(a_variables, a_options, a_cb) {
        if (typeof a_options === "function"){
          a_cb = a_options;
          a_options = {};
        }

        if (!a_options)
          a_options = {};

        var result = {};
        a_variables = Array.isArray(a_variables) ? a_variables : [a_variables];
        var query = {
          type:   "select",
          from:   "___fcf___variables",
          fields: [{field: "value"},{field: "name"},{field: "package"}],
          where: [],
        }
        for (var i = 0; i < a_variables.length; ++i) {
          result[a_variables[i]] = null;
          var pos = a_variables[i].indexOf(":");
          var pack = pos != -1 ? a_variables[i].substr(0, pos) : "";
          var name    = pos != -1 ? a_variables[i].substr(pos+1) : a_variables[i];
          query.where.push({logic: "or", type: "block", args: [
            {logic: "and", type: "=", args: [{field: "package"}, {value: pack}]},
            {logic: "and", type: "=", args: [{field: "name"}, {value: name}]},
          ]});
        }

        var queryOptions = fcf.append({}, a_options, {query: query});

        return this.getStorage().query(queryOptions)
        .then(function(a_data) {
          for(var i = 0; i < a_data[0].length; ++i){
            result[a_data[0][i].package + ":" + a_data[0][i].name] = fcf.strToObject(a_data[0][i].value);
          }
          if (a_cb)
            a_cb(undefined, result);
          return result;
        })
        .catch(function(a_error) {
          a_cb(a_error);
        });
      }

      selectSystemVariable(a_part, a_variable) {
        if (a_variable == undefined){
          a_variable = a_part.split(":")[1];
          a_part     = a_part.split(":")[0];
        }
        var vars = cache.get("fcf", "variables");
        if (!vars[a_part])
          return;
        return vars[a_part][a_variable]
      }


      updateSystemVariables(a_objectVariables, a_options, a_cb) {
        let self = this;
        if (typeof a_options === "function"){
          a_cb = a_options;
          a_options = {};
        }

        if (!a_options)
          a_options = {};

        var result = {};
        var queries = [];
        for (var varName in a_objectVariables) {
          var query = {
            type:   "update",
            from:   "___fcf___variables",
            values: [],
            where: [],
          }
          var pos = varName.indexOf(":");
          var pack = pos != -1 ? varName.substr(0, pos) : "";
          var name    = pos != -1 ? varName.substr(pos+1) : varName;
          query.values.push({field: "value", value: JSON.stringify(a_objectVariables[varName])});
          query.where.push({logic: "or", type: "block", args: [
            {logic: "and", type: "=", args: [{field: "package"}, {value: pack}]},
            {logic: "and", type: "=", args: [{field: "name"}, {value: name}]},
          ]});
          queries.push(query);
        }

        var queryOptions = fcf.append({}, a_options, {query: queries});
        return fcf.actions()
        .then(function(a_res, a_act){
          self.getStorage().query(queryOptions, function(a_error, a_data){
            if (a_cb)
              a_cb(a_error);
            if (a_error){
              a_act.error(a_error);
            } else {
              a_act.complete();
            }
          });
        })
      }

      isRunning() {
        return this._isRunning;
      }

      getPackages() {
        return this._packages;
      }

      appendPackage(a_package) {
        if (!this._packages[a_package.getName()])
          this._packages[a_package.getName()] = a_package;
      }

      getThemes() {
        return this._themes;
      }

      getCron(){
        return this._cron;
      }

      getSystemActions(){
        return this._actions;
      }


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

      getEventChannel() {
        return this._eventChannel;
      }

      appendTemplateAlias(a_alias, a_uri) {
        var objAlias = {};
        objAlias[a_alias] = a_uri;
        this.getThemes().setAliases(objAlias);
      }

      appendViewAlias(a_alias, a_uri) {
        var objAlias = {};
        objAlias[a_alias] = a_uri;
        this.getThemes().setViews(objAlias);
      }

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

      run() {
        var self = this;

        this._isRunning = true;

        if (!this.getConfiguration().disableCron) {
          this._cron.run();
        }

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

          this._app.use(modBodyParser.urlencoded({extended: true, parameterLimit: maxUrlCountParametersReceived, limit: maxSizeReceived,}));
          this._app.use(modBodyParser.json({parameterLimit: maxUrlCountParametersReceived, limit: maxSizeReceived}));
          this._app.use(modCookieParser());


          this._app.use(function(a_req, a_resp, a_next){
            fcf.rebuildEvalEnvironment();
            self._doRequest(a_req, a_resp, a_next);
          });


          let server = this.getConfiguration().host ? this._app.listen(this.getConfiguration().port, this.getConfiguration().host)
                                                    : this._app.listen(this.getConfiguration().port);

          if (self.getConfiguration().keepAliveTimeout != 0)
            server.keepAliveTimeout = self.getConfiguration().keepAliveTimeout*1000;

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

        if (self.getConfiguration().controlPort){
          this._controlSocket = modNet.createServer(function(socket) {
            socket.on("data", function(a_data){
              var array = []; 
              for (var i = 0; i < a_data.length; ++i) 
                array[i] = a_data[i];
              var text = String.fromCharCode.apply(String, array);
              var totalLength = text.length;
              var events = [];
              var startPos = 0;
              while(startPos < totalLength){
                var hrp  = text.indexOf("\n", startPos);
                if (hrp == -1)
                  break;
                var length = parseInt(text.substr(startPos, hrp));
                var content = text.substr(hrp+1, length);
                var event;
                try {
                  event = JSON.parse(content);
                } catch(e){
                  console.error("FAILED COMMAND", content, 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 + content.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) { }
                  })
                }
              })



            });
          });
          this._controlSocket.listen(self.getConfiguration().controlPort, "localhost");
        }

        setTimeout(function(){
          fcf.log.log("FCF", "Applications is running ...");
        }, 100);
      }

      _loadCorePackage() {
        let self = this;
        let pack = new Package({
          application: this,
          configuration: this._configuration,
          package:   "fcf",
          sources:   ["fcf:"],
          extension: "package",
        });
        return pack.initialize()
        .then(function(){
          self._packages["fcf"] = pack;
        })
      }

      _loadPackages() {
        let self = this;
        return fcf.actions().each(self.getConfiguration().packages, (a_key, a_packName)=>{
          let pack = new Package({
            application:    this,
            configuration:  this._configuration,
            package:        a_packName,
            sources:        self._configuration.packageDirectories,
            extension:      "package",
          });

          return pack.initialize()
          .then(()=>{
            self._packages[a_packName] = pack;
          })

        });
      }


      _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.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;
          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.getSystemVariable("fcf:defaultLanguage"));
        context.set("debug", false);
        context.set("session", {user: {roles: {}, groups: {}} });
        fcf.setContext(context);


        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 modFormidable.IncomingForm();
            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.getSystemVariable("fcf:languageIdentification");
          var avalibleLanguages      = fcf.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;
          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._complete();
              a_info.request.next();
              a_act.complete();
              context.destroy();
              a_info.request = null;
              context = null;
            })
          });
          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(()=>{
            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._complete();
          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.application = new fcf.NServer.Application();

    return fcf.application;
  }
});
