fcf.module({
  name: "fcf:NRender/NDetails/ArgsBuilder.js",
  dependencies: ["fcf:NRender/NDetails/Loader.js",
                 "fcf:NRender/NDetails/TemplateProcessor.js",
                 "fcf:NRender/Template.js",
                 "fcf:NRender/NDetails/Helper.js",
                 "fcf:NRender/TaskInfo.js"
               ],
  module: function(Loader, TemplateProcessor, Template, helper, TaskInfo) {
    var NDetails = fcf.prepareObject(fcf, "NRender.NDetails");



    /*
    * @class fcf::NRender::NDetails::ArgsBuilder
    * @brief Класс сборки аргументов шаблона
    **/
    NDetails.ArgsBuilder = function(a_initializeOptions) {
      var self = this;
      this._templateProcessor = new TemplateProcessor({});

      this.build = function(a_options) {
        fcf.setContext(a_options.context);

        var fcfId = a_options.id;
        var ti = new TaskInfo({
          owner:                    this,
          storage:                  fcf.application.getStorage(),
          route:                    a_options.route,
          request:                  a_options.request,
          context:                  a_options.context,
          _render:                  a_options.render,
          templatePath:             a_options.templatePath,
          template:                 a_options.template,
          srcArgs:                  fcf.append({fcfWrapper: true}, a_options.args),
          fullSrcArgs:              fcf.append({fcfWrapper: true}, a_options.args),
          args:                     {fcfId: fcfId},
          reqursion:                a_options.reqursion,
          loader:                   a_options.loader,
          update:                   a_options.update,
          processedArgs:            {},
          processedCounter:         0,
          currentArgType:           "system",
          lastArgType:              undefined,
          _fcfTransformation:       a_options.fcfTransformation,
          projections:              a_options.projections ? a_options.projections : fcf.application.getProjections().getProjections(),
          _start:                   true,
          _firstProcessValue:       true,
          _firstProcessTemplate:    true,
          inputArgs:                a_options.inputArgs,
          aliases:                  a_options.state.theme.getAliases(),
          state:                    a_options.state,
          page:                     a_options.state.page,
          currentTheme:             undefined,
          systemArgsIsProcessed:    false,
          importants:               {},
          _defaultFcfId:            fcfId,
          _processPreFcfChildsArgs: false,
          _processFcfChildsArgs:    false,
          _attach:                  {},
        });

        for(var k in a_options.args){
          if (!fcf.isArg(a_options.args[k]) || !a_options.args[k].important)
            continue;
          ti.importants[k] = a_options.args[k];
        }

        if (!fcf.empty(a_options.fcfTransformation)){
          ti.srcArgs.fcfTransformation     = a_options.fcfTransformation;
          ti.fullSrcArgs.fcfTransformation = a_options.fcfTransformation;
        }

        ti.actions = fcf.actions();
        ti.actions.catch((a_error)=>{
          a_options.onResult(a_error, undefined);
        })

        for (var key in a_options.inputArgs) {
          ti.srcArgs[key] = a_options.inputArgs[key];
          ti.fullSrcArgs[key] = a_options.inputArgs[key];
        }

        function isExistUrlArg(a_array, a_argName){
          for(let i = 0; i < a_array.length; ++i){
            let arr = fcf.parseObjectAddress(a_array[i]);
            if (arr[0] === "route" && arr[1] === "args" && arr[2] === a_argName)
              return true;
          }
          return false;
        }

        for(let key in ti.srcArgs){
          if (!fcf.isArg(ti.srcArgs[key]) || ti.srcArgs[key].type != "url")
            continue;
          let urlArgPath = "route.args[\"" + fcf.escapeQuotes(fcf.parseObjectAddress(ti.srcArgs[key].arg)[0]) + "\"]";
          if (ti.srcArgs[key].action == "reload") {
            if (Array.isArray(ti.srcArgs.fcfReload)){
              if (!isExistUrlArg(ti.srcArgs.fcfReload, ti.srcArgs[key].arg)){
                ti.srcArgs.fcfReload.push(urlArgPath);
                ti.fullSrcArgs.fcfReload.push(urlArgPath);
              }
            } if (fcf.isArg(ti.srcArgs.fcfReload) && Array.isArray(ti.srcArgs.fcfReload.value)) {
              if (!isExistUrlArg(ti.srcArgs.fcfReload.value, ti.srcArgs[key].arg)){
                ti.srcArgs.fcfReload.value.push(urlArgPath);
                ti.fullSrcArgs.fcfReload.value.push(urlArgPath);
              }
            } else {
              ti.srcArgs.fcfReload = [urlArgPath];
              ti.fullSrcArgs.fcfReload = fcf.append([], ti.srcArgs.fcfReload);
            }
          } else if (ti.srcArgs[key].action === undefined || ti.srcArgs[key].action == "update") {
            if (Array.isArray(ti.srcArgs.fcfUpdate)){
              if (!isExistUrlArg(ti.srcArgs.fcfUpdate, ti.srcArgs[key].arg)){
                ti.srcArgs.fcfUpdate.push(urlArgPath);
                ti.fullSrcArgs.fcfUpdate.push(urlArgPath);
              }
            } if (fcf.isArg(ti.srcArgs.fcfUpdate) && Array.isArray(ti.srcArgs.fcfUpdate.value)) {
              if (!isExistUrlArg(ti.srcArgs.fcfUpdate.value, ti.srcArgs[key].arg)){
                ti.srcArgs.fcfUpdate.value.push(urlArgPath);
                ti.fullSrcArgs.fcfUpdate.value.push(urlArgPath);
              }
            } else {
              ti.srcArgs.fcfUpdate = [urlArgPath];
              ti.fullSrcArgs.fcfUpdate = fcf.append([], ti.srcArgs.fcfUpdate);
            }
          }
          let escapeQuotesArgName = fcf.escapeQuotes(fcf.parseObjectAddress(key)[0])
          if (Array.isArray(ti.srcArgs.fcfRouteArgs)){
            if (!isExistUrlArg(ti.srcArgs.fcfRouteArgs, ti.srcArgs[key].arg)){
              ti.srcArgs.fcfRouteArgs.push(escapeQuotesArgName);
              ti.fullSrcArgs.fcfRouteArgs.push(escapeQuotesArgName);
            }
          } if (fcf.isArg(ti.srcArgs.fcfRouteArgs) && Array.isArray(ti.srcArgs.fcfRouteArgs.value)) {
            if (!isExistUrlArg(ti.srcArgs.fcfRouteArgs.value, ti.srcArgs[key].arg)){
              ti.srcArgs.fcfRouteArgs.value.push(escapeQuotesArgName);
              ti.fullSrcArgs.fcfRouteArgs.value.push(escapeQuotesArgName);
            }
          } else {
            ti.srcArgs.fcfRouteArgs = [escapeQuotesArgName];
            ti.fullSrcArgs.fcfRouteArgs = fcf.append([], ti.srcArgs.fcfRouteArgs);
          }
        }


        ti.state.args[fcfId] = ti.args;
        ti.state.sources[fcfId] = ti.fullSrcArgs;
        let originSourcesKeys = [];
        for (var key in a_options.args)
          originSourcesKeys.push(key);
        ti.state.originSourcesKeys[fcfId] = originSourcesKeys;

        for (var key in ti.srcArgs) {
          if (!fcf.isArg(ti.srcArgs[key]) || !("attach" in ti.srcArgs[key]))
            continue;
          let arg = ti.srcArgs[key].attach.arg;
          if (!(arg in ti.srcArgs))
            continue;
          if (!fcf.isArg(ti.srcArgs[arg]))
            ti.srcArgs[arg] = fcf.arg("value", {value: ti.srcArgs[arg]})
          if (!Array.isArray(ti.srcArgs[arg].dependencies))
            ti.srcArgs[arg].dependencies = [];
          ti.srcArgs[arg].dependencies.push(key);

          if (!ti._attach[arg])
            ti._attach[arg] = [];
          ti._attach[arg].push(key);
        }

        function clComplete() {
          if (fcf.isServer()) {
            if (ti.request && ti.args.fcfId) {
              if (Array.isArray(ti.args.fcfInclude)) {
                for(var i = 0; i < ti.args.fcfInclude.length; ++i){
                  let incl = ti.args.fcfInclude[i];
                  ti.state.include[incl] = incl;
                }
              }
              if ((ti.args.fcfWrapper || ti.args.fcfId == ti.state.root.id) && (ti.args.fcfStorage === undefined || ti.args.fcfStorage)) {
                ti.state.renderRestore[ti.args.fcfId] = {};
                delete ti.fullSrcArgs.fcfChildsArgs;
                fcf.append(ti.state.renderRestore[ti.args.fcfId], ti.fullSrcArgs);
                fcf.each(a_options.inputArgs, function(a_key, a_value){
                  if (a_key == "fcfChildsArgs")
                    return;
                  ti.state.renderRestore[ti.args.fcfId][a_key] = a_value;
                });

                var data = {fcfId: ti.args.fcfId};
                fcf.each(ti.fullSrcArgs, function(a_key, a_value){
                  if (a_key == "fcfChildsArgs")
                    return;
                  if (fcf.isArg(a_value) && a_value.type != "value" && a_value.type != "reference" && a_value.type != "programmable")
                    return;
                  var ptrData = fcf.resolveEx(data, a_key, true);
                  ptrData.object[ptrData.key] = fcf.resolve(ti.args, a_key);

                });
                ti.state.renderStorage[ti.args.fcfId] = data;
              }
            }
          }

          if (ti.request && ti.args.fcfId) {
            if (!fcf.empty(ti.args.fcfTheme)) {
              if (!ti.reqursion){
                let newTheme = fcf.application.getThemes().getTheme(ti.args.fcfTheme);
                if (newTheme)
                  ti.state.theme = newTheme;
              }
              ti.state.themes[ti.args.fcfTheme] = ti.args.fcfTheme;
            }
          }

          try {
            helper.callHook(
              ti.template,
              "hookAfterBuild",
              ti,
            );
          } catch(e) {
            ti.actions.error(e);
            return;
          }

          ti.actions.then(function(a_res, a_act){
            a_options.onResult(undefined, ti.args, ti.fullSrcArgs);
            for(let key in ti)
              delete ti[key];
            a_act.complete();
          })
        };


        try {
          helper.callHook(
            ti.template,
            "hookBeforeBuild",
            ti
          );
        } catch(e){
          ti.actions.error(e);
          return;
        };

        self._processedArgs(ti, clComplete);

      }


      this._callHookBeforeArgument = function(a_taskInfo, a_argPath){
        if (!a_taskInfo._processedHooksBeforeArgument)
          a_taskInfo._processedHooksBeforeArgument = {}

        if (a_argPath in a_taskInfo._processedHooksBeforeArgument)
          return;

        a_taskInfo._processedHooksBeforeArgument[a_argPath] = true;

        helper.callHookBeforeArgument(
          a_taskInfo.template,
          a_argPath,
          a_taskInfo,
        );
      }


      this._callProgrammableArgument = function(a_taskInfo, a_argPath){
        if (!a_taskInfo._processedHooksProgrammableArguments)
          a_taskInfo._processedHooksProgrammableArguments = {}

        if (a_argPath in a_taskInfo._processedHooksProgrammableArguments)
          return;

        a_taskInfo._processedHooksProgrammableArguments[a_argPath] = true;

        helper.callHookProgramableArgument(
          a_taskInfo.template,
          a_argPath,
          a_taskInfo,
        );
      }


      this._callHookAfterArgument = function(a_taskInfo, a_argPath){
        if (!a_taskInfo._processedHooksAfterArgument)
          a_taskInfo._processedHooksAfterArgument = {}

        if (a_argPath in a_taskInfo._processedHooksAfterArgument)
          return;

        a_taskInfo._processedHooksAfterArgument[a_argPath] = true;

        helper.callHookAfterArgument(
          a_taskInfo.template,
          a_argPath,
          a_taskInfo,
        );
      }

      this.getSystemArg = function(a_taskInfo){
        return this._getNextArg(a_taskInfo, "system");
      }

      this._getLevels = function(){
        return ["system", "url", "reference", "value", "template", "view", "programmable", "*"]
      }

      this.getArg = function(a_taskInfo){
        let types = this._getLevels();
        let typeIndex = fcf.find(types, a_taskInfo.currentArgType);

        while(true){
          let argPath = this._getNextArg(a_taskInfo, a_taskInfo.currentArgType);
          if (argPath) {
            a_taskInfo.lastArgType = a_taskInfo.currentArgType;
            return argPath;
          } else {
            if (a_taskInfo.lastArgType != a_taskInfo.currentArgType){
              ++typeIndex;
              if (typeIndex >= types.length)
                return;
              a_taskInfo.lastArgType = a_taskInfo.currentArgType;
              a_taskInfo.currentArgType = types[typeIndex];
            } else if (a_taskInfo.lastArgType == a_taskInfo.currentArgType && a_taskInfo.currentArgType == "system"){
              ++typeIndex;
              if (typeIndex >= types.length)
                return;
              a_taskInfo.lastArgType = a_taskInfo.currentArgType;
              a_taskInfo.currentArgType = types[typeIndex];
            } else {
              a_taskInfo.lastArgType = a_taskInfo.currentArgType;
              a_taskInfo.currentArgType = types[0];
            }
          }
        }
      }

      this._processedArgs = function(a_taskInfo, a_cbComplete) {
        function processArg(){
          if (++a_taskInfo.state.reqursionCounter < 300){
            processArgUnsafe();
          } else {
            a_taskInfo.state.reqursionCounter = 0;
            setTimeout(processArgUnsafe, 0);
          }
        }

        function processArgUnsafe() {
          let argPath = undefined;
          if (!a_taskInfo.systemArgsIsProcessed) {
            argPath = self.getSystemArg(a_taskInfo);

            if (!argPath) {
              if (!a_taskInfo.currentTheme && a_taskInfo.args.fcfTheme) {
                a_taskInfo.currentTheme = a_taskInfo.args.fcfTheme;
                let theme = fcf.application.getThemes().getTheme(a_taskInfo.currentTheme);
                a_taskInfo.state.theme = theme;
                a_taskInfo.aliases = theme.getAliases();
              }

              a_taskInfo.systemArgsIsProcessed = true;
              helper.callHook(
                a_taskInfo.template,
                "hookAfterSystemBuild",
                a_taskInfo,
              );
            }
          }

          if (a_taskInfo.systemArgsIsProcessed) {
            argPath = self.getArg(a_taskInfo);
          }

          if (argPath == "fcfChildsArgs") {
            a_taskInfo._processPreFcfChildsArgs = true;
          } else if (a_taskInfo._processPreFcfChildsArgs && !a_taskInfo._processFcfChildsArgs) {
            a_taskInfo._processFcfChildsArgs = true;
          }


          if (!argPath){
            a_taskInfo.actions.then(function(){
              a_cbComplete();
            });
            return;
          }

          try {
            self._callHookBeforeArgument(a_taskInfo, argPath);
          } catch(e) {
            a_act.error(e);
            return;
          }

          function processing(a_taskInfo, a_argContainer, a_argName, a_cb){
            a_taskInfo.actions.then(function(a_res, a_act){
              if (fcf.isArg(a_argContainer[a_argName]))
                a_argContainer[a_argName] = self._tokenizeArg(a_argContainer[a_argName], a_taskInfo.args, a_taskInfo);

              self._processedInnerArgs(argPath, a_argContainer[a_argName], a_taskInfo, function(a_error) {
                if (a_error) {
                  a_taskInfo.actions.error(a_error);
                  return;
                }

                self._processedArg(argPath, a_argContainer[a_argName], a_taskInfo, function(a_error, a_data) {
                  if (a_error) {
                    a_taskInfo.actions.error(a_error);
                  } else {
                    if (a_data !== undefined) {
                      let ptr = fcf.resolveEx(a_taskInfo.args, argPath, true);
                      ptr.object[ptr.key] = a_data;
                    }
                    a_cb();
                  }
                });
              });
              a_act.complete();
            });
          }

          function completeProcessing(a_taskInfo, a_argPath){
            delete a_taskInfo.srcArgs[a_argPath];
            a_taskInfo.processedArgs[a_argPath] = true;

            if (a_argPath == "fcfTransformation") {
              a_taskInfo._fcfTransformation = a_taskInfo.args.fcfTransformation;
              a_taskInfo.projections = fcf.application.getProjections().transformation(a_taskInfo.args.fcfTransformation).getProjections();
            } else if (a_argPath == "fcfId") {
              a_taskInfo.state.args[a_taskInfo.args.fcfId] = a_taskInfo.args;
              a_taskInfo.state.sources[a_taskInfo.args.fcfId] = a_taskInfo.fullSrcArgs;
            }

            if (a_argPath in a_taskInfo._attach){
              for(let i = 0; i < a_taskInfo._attach[a_argPath].length; ++i){
                let srcPath   = a_taskInfo._attach[a_argPath][i];
                let srcParams = a_taskInfo.fullSrcArgs[srcPath].attach;
                let srcData   = fcf.resolve(a_taskInfo.args, srcPath);
                let dstDataPtr = fcf.resolveEx(a_taskInfo.args, a_argPath);
                dstDataPtr.object[dstDataPtr.key] = fcf.merge(dstDataPtr.object[dstDataPtr.key], srcData, srcParams);
              }
            }


            try {
              self._callHookAfterArgument(a_taskInfo, a_argPath);
            } catch(e){
              a_taskInfo.actions.error(e);
              return;
            }

            a_taskInfo.actions.then(function(){
              processArg();
            });
          }

          let enableImportant = (fcf.isArg(a_taskInfo.importants[argPath]) &&  a_taskInfo.importants[argPath].important) && 
                                (!fcf.isArg(a_taskInfo.srcArgs[argPath]) || !a_taskInfo.srcArgs[argPath].important);
          processing(a_taskInfo, a_taskInfo.srcArgs, argPath, function(){
            if (enableImportant){
              processing(a_taskInfo, a_taskInfo.importants, argPath, function(){
                completeProcessing(a_taskInfo, argPath);
              });
            } else {
              completeProcessing(a_taskInfo, argPath);
            }
          });
        }

        processArg();
      }

      this._processedInnerArgs = function(a_argPath, a_srcArg, a_taskInfo, a_cb) {
        if (a_argPath == 'fcfChildsArgs'){
          a_cb();
          return;
        }

        if (!fcf.isArg(a_srcArg)){
          a_cb();
          return;
        }

        function clFindInnerArg(a_value, a_path, a_result){
          if (!a_result)
            a_result = [];
          fcf.each(a_value, function(a_key, a_v){
            let path = fcf.append([], a_path);
            path.push(a_key);

            if (fcf.isArg(a_v)){
              a_value[a_key] = self._tokenizeArg(a_v, a_taskInfo.args, a_taskInfo);
              let skip = fcf.isArg(a_srcArg) &&
                        ( a_srcArg.type == "template" ||
                          a_srcArg.type == "view" ) &&
                        a_v.type == "reference";
              if (!skip)
                a_result.push(path);

            } else if (typeof a_v == "object") {
              clFindInnerArg(a_v, path, a_result);
            }
          });

          return a_result;
        }
        function clNormalizePaths(a_arrPaths){
          fcf.each(a_arrPaths, function(a_key, a_value){
            let line = "";
            for(let i = 0; i < a_value.length; ++i)
              line += "[\""+fcf.escapeQuotes(a_value[i]) + "\"]"
            a_arrPaths[a_key] = line;
          })
          return a_arrPaths;
        }

        let innerSrcArgs = clNormalizePaths(clFindInnerArg(a_srcArg));
        fcf.actions()
        .catch((a_error)=>{
          a_cb(a_error);
        })
        .each(innerSrcArgs, function(a_key, a_value, a_res, a_act){
          let ptr = fcf.resolveEx(a_srcArg, a_value);
          let innerSrcArg = ptr.object[ptr.key];
          self._processedArg(a_argPath + "->" + a_key, innerSrcArg, a_taskInfo, function(a_error, a_data) {
            if (a_error){
              a_act.error(a_error);
              return;
            }

            let ptr = fcf.resolveEx(a_srcArg, a_value);
            ptr.object[ptr.key] = a_data;
            a_act.complete();
          });

        })
        .then(function(){
          a_cb();
        });
      }

      this._processedArg = function(a_argPath, a_srcArg, a_taskInfo, a_cb) {
        // already processed by the hook
        if (a_srcArg === undefined){
          a_cb(undefined);
          return;
        }

        a_taskInfo.context.currentTemplate = { id: a_taskInfo.args.fcfId };
        fcf.setContext(a_taskInfo.context);

        if (!(fcf.isArg(a_srcArg)) || a_srcArg.type == "value") {
          a_cb(undefined, self._processValue(a_srcArg, a_taskInfo));
        } else if (a_srcArg.type == "reference") {
          self._processReference(a_srcArg, a_taskInfo, a_cb);
        } else if (a_srcArg.type == "url") {
          self._processUrl(a_srcArg, a_taskInfo, a_cb);
        } else if (a_srcArg.type == "template") {
          self._processTemplate(a_argPath, a_srcArg, a_taskInfo, a_cb);
        } else if (a_srcArg.type == "view") {
          self._processView(a_argPath, a_srcArg, a_taskInfo, a_cb);
        } else if (a_srcArg.type == "programmable" && a_argPath) {
          try {
            self._callProgrammableArgument(a_taskInfo, a_argPath);
          }catch(error){
            a_cb(error);
            return;
          }
          a_taskInfo.actions.then(function(){
            a_cb(undefined);
          })
        } else {
          a_cb(undefined);
        }
      }

      this._processReference = function(a_srcArg, a_taskInfo, a_cb) {
        let data = undefined;

        if (a_srcArg.object.id == a_taskInfo.args.fcfId){
          data = fcf.resolve(a_taskInfo.args, a_srcArg.arg);
        } else {
          if (!fcf.isServer()){
            if (a_taskInfo.state.args[a_srcArg.object.id]) {
              data = fcf.resolve(a_taskInfo.state.args[a_srcArg.object.id], a_srcArg.arg);
            } else {
              let wrp = fcf.getWrapper(a_srcArg.object.id);
              if (wrp)
                data = wrp.getArg(a_srcArg.arg);
            }
          } else {
            data = fcf.resolve(a_taskInfo.state.args[a_srcArg.object.id], a_srcArg.arg);
          }
        }

        a_cb(undefined, data);
      }

      this._processUrl = function(a_srcArg, a_taskInfo, a_cb) {
        let data = undefined;
        let ptr  = fcf.resolveEx(a_taskInfo.route.args, a_srcArg.arg);
        if (!(ptr.key in ptr.object) && "default" in a_srcArg)
          data = a_srcArg.default;
        else 
          data = ptr.object[ptr.key];
        a_cb(undefined, data);
      }

      this._processTemplate = function(a_argPath, a_srcArg, a_taskInfo, a_cb) {
        this._processTemplateItem(
          0,
          a_argPath,
          a_srcArg.template,
          a_srcArg.args,
          a_taskInfo,
          function(a_error, a_content){
            if (a_error){
              a_cb(a_error);
              return;
            }
            a_cb(undefined, a_content);
          }
        );
      }



      this._processTemplateItem = function(a_callIndex, a_argPath, a_template, a_inputArgs, a_taskInfo, a_cb) {
        let rootTemplateFile = a_taskInfo.templatePath.split("+")[0];

        let templatePath;
        if (a_template.indexOf("+") == 0)
          templatePath = rootTemplateFile + "+" + a_template.substr(1);
        else
          templatePath = a_template;

        let templateFile = templatePath.split("+")[0];

        let fullPathRootTemplateFile = fcf.getPath(rootTemplateFile);
        let fullPathTemplateFile = fcf.getPath(templateFile);

        inputArgs = fcf.append({}, a_inputArgs);
        if (a_taskInfo.args.fcfWrapper || a_taskInfo.args.fcfId == a_taskInfo.state.root.id)
          inputArgs.fcfParent = a_taskInfo.args.fcfId;

        helper.appendChildInfo(inputArgs, {fcfCP: "tmpl:" + a_argPath + ":" + a_callIndex}, a_taskInfo.args.fcfChildsArgs);
        inputArgs.fcfCP = "tmpl:" + a_argPath + ":" + a_callIndex;

        a_taskInfo._render.render({
          template:     templatePath,
          theme:        a_taskInfo.state.theme.getName(),
          route:        a_taskInfo.route,
          context:      a_taskInfo.context,
          state:        a_taskInfo.state,
          request:      a_taskInfo.request,
          update:       a_taskInfo.update,
          reqursion:    true,
          projections:  a_taskInfo.projections,
          fcfTransformation: a_taskInfo._fcfTransformation,
          args: fcf.unescapeObject(inputArgs),
          onResult: function(a_error, a_template) {
            if (a_error) {
              a_cb(a_error);
              return;
            }
            a_cb(undefined, a_template.content);
          }
        });
      }


      this._processView = function(a_argPath, a_srcArg, a_taskInfo, a_cb) {
        let mode = a_srcArg.fcfMode       ? a_srcArg.fcfMode :
                  a_taskInfo.args.fcfMode ? a_taskInfo.args.fcfMode :
                                            "read";
        let template           = a_taskInfo.state.theme.getView(mode, a_srcArg.view).template;
            view               = fcf.buildView(a_srcArg.view, mode);
        let templateArgs       = fcf.append({}, view);
        fcf.append(templateArgs, a_srcArg.view.args);
        templateArgs.record    = a_srcArg.record;
        templateArgs.fcfParent = a_taskInfo.args.fcfWrapper || a_taskInfo.args.fcfId == a_taskInfo.state.root.id ? a_taskInfo.args.fcfId : undefined;
        if (a_taskInfo.args.fcfMode)
          templateArgs.fcfMode = a_taskInfo.args.fcfMode;
        templateArgs.fcfAlias  = view.alias;
        templateArgs.fcfKey    = a_taskInfo.args.fcfKey;
        if (!templateArgs.fcfClass)
          templateArgs.fcfClass  = a_taskInfo.args.fcfChildClass;
        templateArgs.fcfCP     = "view:" + a_argPath;
        templateArgs.value   =  fcf.isArg(view.value)
                                  ? fcf.unescapeObject(view.value) :
                                !fcf.empty(view.value)
                                  ? fcf.arg("value", {value: fcf.unescapeObject(view.value) }) :
                                fcf.isArg(a_srcArg.value)
                                  ? fcf.unescapeObject(a_srcArg.value) :
                                !fcf.empty(a_srcArg.value)
                                  ? fcf.arg("value", {value: fcf.unescapeObject(a_srcArg.value) }) :
                                fcf.isArg(view.default)
                                  ? fcf.unescapeObject(view.default) :
                                !fcf.empty(view.default)
                                  ? fcf.arg("value", {value: fcf.unescapeObject(view.default) }) :
                                    undefined;



        helper.appendChildInfo(templateArgs, templateArgs, a_taskInfo.args.fcfChildsArgs);

        a_taskInfo._render.render({
          template: template,
          state:    a_taskInfo.state,
          theme:    a_taskInfo.state.theme.getName(),
          route:    a_taskInfo.route,
          projections: a_taskInfo.projections,
          fcfTransformation: a_taskInfo._fcfTransformation,
          request:  a_taskInfo.request,
          args:     templateArgs,
          context:  a_taskInfo.context,
          update:        a_taskInfo.update,
          reqursion:     true,
          onResult: function(a_error, a_template) {
            if (a_error) {
              a_cb(a_error);
              return;
            }
            a_cb(undefined, a_srcArg.fullResult ? a_template : a_template.content);
          }
        });
      }


      this.isPreparedValue = function(a_value) {
        if (typeof a_value == "string") {
          let reg = new RegExp("([^\\\\][@#$]{{)|(^[@#$]{{)");
          let m = a_value.match(reg);
          return fcf.empty(m);
        } else if (typeof a_value == "object") {
          let res = true;
          fcf.foreach(a_value, function(k, v){ res &= this.isPreparedValue(v); });
          return res;
        }
        return true;
      }

      this._processValue = function(a_srcArg, a_taskInfo) {
        let isex = fcf.isArg(a_srcArg);
        let value = isex ? a_srcArg.value : a_srcArg;
        let needDefault = false;
        if (typeof value === "string") {
          needDefault = value.indexOf("${{") != -1 || value.indexOf("#{{") != -1 || value.indexOf("@{{") != -1;
        }

        if ((fcf.empty(value) || needDefault) && isex && "default" in a_srcArg) {
          value = a_srcArg.default;
          needDefault = false;
        }

        if ((fcf.empty(value) || needDefault) && isex && Array.isArray(a_srcArg.defaults)){
          let defaults = a_srcArg.defaults;
          for (let i = 0; i < defaults.length; ++i) {
            if (!fcf.empty(defaults[i]) && this.isPreparedValue(defaults[i])){
              value = defaults[i];
              break;
            }
          }
        }


        if (isex && typeof a_srcArg.merge == "object"){
          let merge = fcf.isEnumerable(a_srcArg.merge) ? a_srcArg.merge : [a_srcArg.merge];
          for(let i = 0; i < merge.length; ++i)
            value = fcf.merge(value, merge[i].value, merge[i].options);
        }

        return value;
      }

      this._tokenizeArg = function(a_srcArg, a_args, a_taskInfo) {
        let args = {args: a_args, source: a_srcArg, route: a_taskInfo.route, projections: fcf.application.getProjections().getProjections(), decor: a_taskInfo.state.theme.getInfo().decor };
        let result = fcf.tokenizeObject(a_srcArg, args, true);
        return result;
      }

      this._getNextArg = function(a_taskInfo, a_type) {
        for (let key in a_taskInfo.srcArgs) {
          if (a_type == "system") {
            if (a_taskInfo.srcArgs.fcfId)
              return "fcfId";
            if (a_taskInfo.srcArgs.fcfTheme)
              return "fcfTheme";
            if (key.indexOf("fcf") !== 0)
              continue;
          } else {
            let src = fcf.isArg(a_taskInfo.srcArgs[key]) ? a_taskInfo.srcArgs[key] : fcf.arg("value");
            let level = src.level ? src.level : src.type;
            let typeMatching = a_type == level;
            if (!typeMatching && a_type != "*")
              continue;
          }

          let important = fcf.isArg(a_taskInfo.importants[key]) ? a_taskInfo.importants[key] : fcf.arg("value");

          
          let deps = [];
          if (fcf.isArg(a_taskInfo.srcArgs[key])){
            deps = this._getTokenizeDependencies(a_taskInfo, a_taskInfo.srcArgs[key]);
            fcf.append(deps, a_taskInfo.srcArgs[key].dependencies);
          }
          if (fcf.isArg(a_taskInfo.importants[key])){
            fcf.append(deps, this._getTokenizeDependencies(a_taskInfo, a_taskInfo.importants[key]));
            fcf.append(deps, a_taskInfo.importants[key].dependencies);
          }
          if (a_taskInfo.template.hardDependencies[key])
            fcf.append(deps, a_taskInfo.template.hardDependencies[key]);

          let inwaiting = false;

          for (let i = 0; i < deps.length; ++i) {
            inwaiting |= this._checkWaiting(deps[i], a_taskInfo);
            if (inwaiting)
              break;
          }
          if (inwaiting)
            continue;

          if (a_type != "*") {
            if (key == "fcfChildsArgs" && a_taskInfo.fullSrcArgs.fcfCP){
              inwaiting |= this._checkWaiting("fcfCP", a_taskInfo);
              if (inwaiting)
                continue;
            }
          }

          return key;
        }

        if (a_type == "*") {
          for (let key in a_taskInfo.srcArgs) {
            return key;
          }
        }

        return;
      }

      this._checkWaiting = function(a_path, a_taskInfo) {
        if (a_path in a_taskInfo.processedArgs)
          return false;
        return true;
      }

      this._getTokenizeDependencies = function(a_taskInfo, a_srcArgs, _a_dst) {
        let isRoot = false;
        if (_a_dst === undefined) {
          _a_dst = {};
          isRoot = true;
        }

        for(let k in a_srcArgs) {
          if (typeof a_srcArgs[k] === "string") {
            let keys = fcf.getVariablesString(a_srcArgs[k]);
            for(let i = 0; i < keys.length; ++i){
              let addrArr = fcf.parseObjectAddress(keys[i]);
              if (addrArr[0] != "args")
                continue;
              _a_dst[addrArr[1]] = true;
            }
          } else if (typeof a_srcArgs[k] === "object") {
            this._getTokenizeDependencies(a_taskInfo, a_srcArgs[k], _a_dst);
          }
        }

        if (fcf.isArg(a_srcArgs) && a_srcArgs.type == "reference") {
          if (a_srcArgs.object.id == a_taskInfo.args.fcfId || a_srcArgs.object.id == "${{args.fcfId}}$" || a_srcArgs.object.id == "@{{args.fcfId}}@"){
            let arg = fcf.parseObjectAddress(a_srcArgs.arg)[0];
            if (arg !== undefined){
              _a_dst[arg] = true;
            }
          }
        }


        if (isRoot) {
          let result = [];
          for(let k in _a_dst)
            result.push(k);
          return result;
        }
      }

    }

    return NDetails.ArgsBuilder;
  }
});
