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

    async function getTemplateInfo(a_template){
      if (fcf.isServer()) {
        let block = a_template.split("+")[1];
        if (!block)
          block = "";
        let ti = await fcf.application.getRender().getLoader().loadInfo(a_template);
        return ti.templates[block];
      } else {
        return await fcf.application.getRender().getTemplateInfo(a_template);
      }
    }

    NDetails.ArgsBuilder = function() {
      var self = this;

      this.build = function(a_options) {
        var fcfId = a_options.id;
        var ti = new TaskInfo({
          route:                    a_options.route,
          template:                 a_options.templatePath,
          args:                     {fcfId: fcfId},
          srcArgs:                  fcf.append({}, a_options.args),
          fullSrcArgs:              fcf.append({}, a_options.args),
          processedArgs:            {},
          page:                     a_options.state.page,
          reqursion:                a_options.reqursion,
          currentTheme:             undefined,
          _details: {
            owner:                  this,
            importants:             {},
            attach:                 {},
            systemArgsIsProcessed:  false,
            state:                  a_options.state,
            template:               a_options.template,
            lastArgType:            undefined,
            currentArgType:         "system",
            dependencies:           {},
          },
        });

        for(var k in a_options.args){
          if (fcf.isArg(a_options.args[k]) && Array.isArray(a_options.args[k].validate)) {
            let fcfValidate = ti.srcArgs.fcfValidate;
            if (!fcfValidate){
              fcfValidate = {};
              ti.srcArgs.fcfValidate = fcfValidate;
              ti.fullSrcArgs.fcfValidate = fcfValidate;
            };

            let validate = a_options.args[k].validate;
            for(let i = 0; i < validate.length; ++i){
              if (!("value" in validate[i])) {
                validate[i].value = a_options.args[k].value;
              }
            }

            fcfValidate[k] = [validate];
          }
          if (!fcf.isArg(a_options.args[k]) || !a_options.args[k].important)
            continue;
          ti._details.importants[k] = a_options.args[k];
        }

        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];
          if (fcf.isArg(a_options.inputArgs[key]) && Array.isArray(a_options.inputArgs[key])) {
            let fcfValidate = ti.srcArgs.fcfValidate;
            if (!fcfValidate){
              fcfValidate = {};
              ti.srcArgs.fcfValidate = fcfValidate;
              ti.fullSrcArgs.fcfValidate = fcfValidate;
            };

            let validate = a_options.inputArgs[key].validate;
            for(let i = 0; i < validate.length; ++i){
              if (!("value" in validate[i])) {
                validate[i].value = a_options.inputArgs[key].value;
              }
            }

            if (!Array.isArray(fcfValidate[key]))
              fcfValidate[key] = [];

            fcfValidate[key].push(a_options.inputArgs[key].validate)
          }
        }

        if (ti._details.template.options.initialStorageOfChildren){
          ti.srcArgs.fcfInitialStorageOfChildren = true;
          ti.srcArgs.fcfRootInitialStorageOfChildren = true;
          ti.fullSrcArgs.fcfInitialStorageOfChildren = true;
          ti.fullSrcArgs.fcfRootInitialStorageOfChildren = true;
        }

        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._details.state.args[fcfId] = ti.args;
        ti._details.state.sources[fcfId] = ti.fullSrcArgs;
        let originSourcesKeys = [];
        for (var key in a_options.args)
          originSourcesKeys.push(key);
        ti._details.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.argVal(ti.srcArgs[arg]);
          if (!Array.isArray(ti.srcArgs[arg].dependencies))
            ti.srcArgs[arg].dependencies = [];
          ti.srcArgs[arg].dependencies.push(key);

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

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

          try {
            helper.callHook(
              ti._details.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, ti);
            a_act.complete();
          })
        };

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

        ti.actions.then(()=>{
          // processing simple arguments
          for(let key in ti.srcArgs){
            let srcArg          = ti.srcArgs[key];
            let importantSrcArg = ti._details.importants[key];
            let processed       = false;
            let value           = undefined;

            if (ti._details.template.hooks.hooksBeforeArgument && key in ti._details.template.hooks.hooksBeforeArgument)
              continue;
            if (ti._details.template.hooks.hooksAfterArgument && key in ti._details.template.hooks.hooksAfterArgument)
              continue;

            let skipErrorValidation = false;

            if (!fcf.isArg(srcArg) && !importantSrcArg) {
              value = srcArg && typeof srcArg === "object" ? fcf.clone(srcArg) : srcArg;
              processed = true;
            } else if (
              fcf.isArg(srcArg) &&
              srcArg.type == "reference" &&
              !importantSrcArg &&
              srcArg.object.id.indexOf("{{") == -1 &&
              srcArg.arg.indexOf("{{") == -1 &&
              (!(key in  ti._details.template.hardDependencies)) &&
              (!Array.isArray(srcArg.dependencies) || !srcArg.dependencies.length)
            )
            {
                let ptr;
                if (srcArg.object.id == ti.args.fcfId && srcArg.arg in ti.processedArgs){
                  ptr = fcf.resolveEx(ti.args, srcArg.arg);
                  value = ptr.object ? ptr.object[ptr.key] : undefined;
                  skipErrorValidation = !ptr.object || !(ptr.key in ptr.object);
                  skipErrorValidation =
                  processed = true;
                } else if (srcArg.object.id != ti.args.fcfId){
                  if (fcf.isServer()) {
                    ptr = fcf.resolveEx(ti._details.state.args[srcArg.object.id], srcArg.arg);
                    value = ptr.object ? ptr.object[ptr.key] : undefined;
                    skipErrorValidation = !ptr.object || !(ptr.key in ptr.object);
                    processed = true;
                  } else {
                    if (ti._details.state.args[srcArg.object.id]){
                      ptr = fcf.resolveEx(ti._details.state.args[srcArg.object.id], srcArg.arg);
                      value = ptr.object ? ptr.object[ptr.key] : undefined;
                      skipErrorValidation = !ptr.object || !(ptr.key in ptr.object);
                    } else {
                      let info = {};
                      value = fcf.application.getLocalData().getItem(srcArg.object.id, srcArg.arg, false, info);
                      if (info.nodata){
                        skipErrorValidation = true;
                      }
                    }
                    processed = true;
                  }
                }
            }

            if (processed) {
              delete ti.srcArgs[key];
              ti.processedArgs[key] = true;
              if (ti.fullSrcArgs.fcfValidate && key in ti.fullSrcArgs.fcfValidate){
                value = NDetails.ArgsBuilder.validate(value, ti.fullSrcArgs.fcfValidate[key], skipErrorValidation);
              }

              ti.args[key] = value;

              if (key == "fcfId") {
                ti._details.state.args[ti.args.fcfId] = ti.args;
                ti._details.state.sources[ti.args.fcfId] = ti.fullSrcArgs;
              }

              if (fcf.isArg(srcArg) && srcArg.type === "reference") {
                if (ti._details.state.args[srcArg.object.id]) {
                  let refArgs = ti._details.state.args[srcArg.object.id];
                  let refSources = ti._details.state.sources[srcArg.object.id];
                  if ("fcfWrapper" in refArgs && !refArgs.fcfWrapper) {
                    if (!refArgs.fcfBR){
                      refArgs.fcfBR = {};
                      refSources.fcfBR = {};
                    }
                    let argArr = fcf.parseObjectAddress(srcArg.arg);
                    refArgs.fcfBR[argArr[0]] = 1;
                    refSources.fcfBR[argArr[0]] = 1;
                  }
                }
              }

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

        self._processedArgs(ti, clComplete);
      }

      this.buildArg = function(a_srcArg, a_taskInfo) {
        let self = this;
        let argPath = fcf.uuid();
        return fcf.actions()
        .then((a_res, a_act)=>{
          if (fcf.isArg(a_srcArg))
            a_srcArg = self._tokenizeArg(a_srcArg, a_taskInfo.args, a_taskInfo);
          self._processedInnerArgs(argPath, a_srcArg, a_taskInfo, function(a_error) {
            if (a_error)
              a_act.error(a_error);
            else
              a_act.complete();
          });
        })
        .then((a_res, a_act)=>{
          if (a_srcArg === undefined){
            a_act.complete();
            return;
          }

          fcf.getContext().currentTemplate = { id: a_taskInfo.args.fcfId, template: a_taskInfo.template };

          if (!(fcf.isArg(a_srcArg)) || a_srcArg.type == "value") {
            a_act.complete(self._processValue(undefined, a_srcArg, a_taskInfo));
          } else if (a_srcArg.type == "reference") {
            self._processReference(undefined, a_srcArg, a_taskInfo, (a_error, a_value)=>{ if (a_error) { a_act.error(a_error) } else { a_act.complete(a_value); } } );
          } else if (a_srcArg.type == "url") {
            self._processUrl(undefined, a_srcArg, a_taskInfo, (a_error, a_value)=>{ if (a_error) { a_act.error(a_error) }  else { a_act.complete(a_value); } });
          } else if (a_srcArg.type == "template") {
            self._processTemplate(argPath, a_srcArg, a_taskInfo, (a_error, a_value)=>{ if (a_error) { a_act.error(a_error) } else { a_act.complete(a_value); } });
          } else if (a_srcArg.type == "view") {
            self._processView(argPath, a_srcArg, a_taskInfo, (a_error, a_value)=>{ if (a_error) { a_act.error(a_error) } else { a_act.complete(a_value); } });
          } else {
            a_act.complete(a_srcArg);
          }
        })
      }


      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._details.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._details.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._details.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._details.currentArgType);

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

      this._processedArgs = function(a_taskInfo, a_cbComplete) {
        function processArg(){
          let currentTemplateInfo = fcf.getContext().currentTemplate;
          if (++a_taskInfo._details.state.reqursionCounter < 300){
            processArgUnsafe();
          } else {
            a_taskInfo._details.state.reqursionCounter = 0;
            setTimeout(()=>{
              fcf.getContext().currentTemplate = currentTemplateInfo;
              processArgUnsafe();
            }, 0);
          }
        }

        function processArgUnsafe() {
          let argPath = undefined;
          if (!a_taskInfo._details.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.getTheme(a_taskInfo.currentTheme);
                a_taskInfo._details.state.theme = theme;
              }

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

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

          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 == "fcfId") {
              a_taskInfo._details.state.args[a_taskInfo.args.fcfId] = a_taskInfo.args;
              a_taskInfo._details.state.sources[a_taskInfo.args.fcfId] = a_taskInfo.fullSrcArgs;
            }

            if (a_argPath in a_taskInfo._details.attach){
              for(let i = 0; i < a_taskInfo._details.attach[a_argPath].length; ++i){
                let srcPath   = a_taskInfo._details.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._details.importants[argPath]) &&  a_taskInfo._details.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._details.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_srcArg.type == "reference" || ( a_srcArg.type == "value" && typeof a_srcArg.value != "object")){
          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_v.type !== "recordreference") {
              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;
        }

        fcf.getContext().currentTemplate = { id: a_taskInfo.args.fcfId, template: a_taskInfo.template };

        if (!(fcf.isArg(a_srcArg)) || a_srcArg.type == "value") {
          a_cb(undefined, self._processValue(a_argPath, a_srcArg, a_taskInfo));
        } else if (a_srcArg.type == "reference") {
          self._processReference(a_argPath, a_srcArg, a_taskInfo, a_cb);
        } else if (a_srcArg.type == "url") {
          self._processUrl(a_argPath, 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, a_srcArg);
        }
      }

      this._processReference = function(a_argName, a_srcArg, a_taskInfo, a_cb) {
        let data = undefined;
        let skipErrorValidation = false;
        if (a_srcArg.object.id == a_taskInfo.args.fcfId){
          let ptr = fcf.resolveEx(a_taskInfo.args, a_srcArg.arg);
          if (ptr.object && ptr.key in ptr.object){
            data = ptr.object[ptr.key];
          } else {
            data = undefined;
            skipErrorValidation = true;
          }
        } else {
          if (fcf.isServer()) {
            let ptr = fcf.resolveEx(a_taskInfo._details.state.args[a_srcArg.object.id], a_srcArg.arg);
            if (ptr.object && ptr.key in ptr.object){
              data = ptr.object[ptr.key];
            } else {
              data = undefined;
              skipErrorValidation = true;
            }
          } else {
            if (a_taskInfo._details.state.args[a_srcArg.object.id]) {
              let ptr = fcf.resolveEx(a_taskInfo._details.state.args[a_srcArg.object.id], a_srcArg.arg);
              if (ptr.object && ptr.key in ptr.object){
                data = ptr.object[ptr.key];
              } else {
                data = undefined;
                skipErrorValidation = true;
              }
            } else {
              let info = {};
              data = fcf.application.getLocalData().getItem(a_srcArg.object.id, a_srcArg.arg, false, info);
              if (info.nodata){
                skipErrorValidation = true;
              }

            }
          }

          if (a_taskInfo._details.state.args[a_srcArg.object.id]) {
            let refArgs = a_taskInfo._details.state.args[a_srcArg.object.id];
            let refSources = a_taskInfo._details.state.sources[a_srcArg.object.id];
            if ("fcfWrapper" in refArgs && !refArgs.fcfWrapper) {
              if (!refArgs.fcfBR){
                refArgs.fcfBR = {};
                refSources.fcfBR = {};
              }
              let argArr = fcf.parseObjectAddress(a_srcArg.arg);
              refArgs.fcfBR[argArr[0]] = 1;
              refSources.fcfBR[argArr[0]] = 1;
            }
          }
        }

        if (a_argName !== undefined && a_taskInfo.fullSrcArgs.fcfValidate && a_argName in a_taskInfo.fullSrcArgs.fcfValidate) {
          data = NDetails.ArgsBuilder.validate(data, a_taskInfo.fullSrcArgs.fcfValidate[a_argName], skipErrorValidation)
        } else if (fcf.isArg(a_srcArg) && Array.isArray(a_srcArg.validate)) {
          data = NDetails.ArgsBuilder.validate(data, [a_srcArg.validate], skipErrorValidation);
        }

        a_cb(undefined, data);
      }

      this._processUrl = function(a_argName, 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];


        if (a_argName !== undefined && a_taskInfo.fullSrcArgs.fcfValidate && a_argName in a_taskInfo.fullSrcArgs.fcfValidate) {
          data = NDetails.ArgsBuilder.validate(data, a_taskInfo.fullSrcArgs.fcfValidate[a_argName])
        } else if (fcf.isArg(a_srcArg) && Array.isArray(a_srcArg.validate)) {
          data = NDetails.ArgsBuilder.validate(data, [a_srcArg.validate]);
        }

        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 = async function(a_callIndex, a_argPath, a_template, a_inputArgs, a_taskInfo, a_cb) {
        try {
          let rootTemplateFile = a_taskInfo.template.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 block = a_template.split("+")[1];
          if (!block)
            block = "";


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

          inputArgs = fcf.append({}, a_inputArgs);
          inputArgs.fcfParent = fcf.NRender.getParentId(a_taskInfo.args.fcfId, a_taskInfo._details.state);

          let templateInfo = await getTemplateInfo(a_template);
          if (!templateInfo)
            throw new fcf.Exception("ERROR_TEMPLATE_NOT_FOUND", {template: a_template});
          let existsWrapper = !("wrapper" in templateInfo.options) || !!templateInfo.options.wrapper;
          let cp = `tmpl:${a_template}:${a_taskInfo.args.fcfId}:${a_argPath}:${a_callIndex}`;
          helper.appendChildInfo(inputArgs, {fcfCP: cp}, a_taskInfo.args.fcfChildsArgs, existsWrapper);
          inputArgs.fcfCP = cp;

          if (a_taskInfo.args.fcfInitialStorageOfChildren)
            inputArgs.fcfInitialStorageOfChildren = true;

          fcf.application.render({
            template:     templatePath,
            theme:        a_taskInfo._details.state.theme.getName(),
            route:        a_taskInfo.route,
            state:        a_taskInfo._details.state,
            reqursion:    true,
            args:         inputArgs,
            secondary:    true,
            onResult: function(a_error, a_template) {
              if (a_error) {
                a_cb(a_error);
                return;
              }
              a_cb(undefined, a_template.content);
            }
          });
        } catch (e){
          a_cb(e);
        }
      }


      this._processView = async function(a_argPath, a_srcArg, a_taskInfo, a_cb) {
        try {
          let mode = a_srcArg.fcfMode       ? a_srcArg.fcfMode :
                    a_taskInfo.args.fcfMode ? a_taskInfo.args.fcfMode :
                                              "read";
          let view               = fcf.buildView(a_srcArg.view, mode, a_taskInfo._details.state.theme);
          let template           = view.template;
          let templateArgs       = fcf.append({}, view);
          fcf.append(templateArgs, a_srcArg.view.args);
          templateArgs.record    = a_srcArg.record;
          templateArgs.fcfParent = fcf.NRender.getParentId(a_taskInfo.args.fcfId, a_taskInfo._details.state);
          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)
                                    ? view.value :
                                  !fcf.empty(view.value)
                                    ? fcf.argVal(view.value) :
                                  fcf.isArg(a_srcArg.value)
                                    ? a_srcArg.value :
                                  !fcf.empty(a_srcArg.value)
                                    ? fcf.argVal(a_srcArg.value) :
                                  fcf.isArg(view.default)
                                    ? view.default :
                                  !fcf.empty(view.default)
                                    ? fcf.argVal(view.default) :
                                      undefined;
          if (a_taskInfo.args.fcfInitialStorageOfChildren)
            templateArgs.fcfInitialStorageOfChildren = true;


          let templateInfo = await getTemplateInfo(template);
          if (!templateInfo)
            throw new fcf.Exception("ERROR_TEMPLATE_NOT_FOUND", {template: a_template});
          let block = template.split("+")[1];
          if (!block)
            block = "";
          let existsWrapper = !("wrapper" in templateInfo.options) || !!templateInfo.options.wrapper;
          helper.appendChildInfo(templateArgs, templateArgs, a_taskInfo.args.fcfChildsArgs, existsWrapper);

          fcf.application.render({
            template:   template,
            state:      a_taskInfo._details.state,
            theme:      a_taskInfo._details.state.theme.getName(),
            route:      a_taskInfo.route,
            args:       templateArgs,
            reqursion:  true,
            secondary:  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);
            }
          });
        } catch(e){
          a_cb(e);
        }
      }


      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.each(a_value, function(k, v){ res &= this.isPreparedValue(v); });
          return res;
        }
        return true;
      }

      this._processValue = function(a_argName, a_srcArg, a_taskInfo) {
        let isex = fcf.isArg(a_srcArg);

        if (isex && typeof a_srcArg.level === "number" && a_srcArg.level > 1){
          let value = fcf.clone(a_srcArg);
          --value.level;
          return value;
        }

        let value = isex ? a_srcArg.value : a_srcArg;
        let needDefault = false;
        if (isex && 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);
        }

        if (value && typeof value === "object")
          value = fcf.clone(value);

        if (a_argName !== undefined && a_taskInfo.fullSrcArgs.fcfValidate && a_argName in a_taskInfo.fullSrcArgs.fcfValidate) {
          value = NDetails.ArgsBuilder.validate(data, a_taskInfo.fullSrcArgs.fcfValidate[a_argName])
        } else if (fcf.isArg(a_srcArg) && Array.isArray(a_srcArg.validate)) {
          for (let k = 0; k < a_srcArg.validate.length; ++i) {
            if (!("error" in a_srcArg.validate[i]) && !("value" in a_srcArg.validate[i])){
              a_srcArg.validate[i].value = a_srcArg.value;
            }
          }
          value = NDetails.ArgsBuilder.validate(data, [a_srcArg.validate]);
        }

        return value;
      }

      this._tokenizeArg = function(a_srcArg, a_args, a_taskInfo) {
        let args = {args: a_args, source: a_srcArg, route: a_taskInfo.route, decor: a_taskInfo._details.state.theme.getDecor() };
        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.argVal();
            let level = src.level ? src.level : src.type;
            let typeMatching = a_type == level;
            if (!typeMatching && a_type != "*")
              continue;
          }

          let important = fcf.isArg(a_taskInfo._details.importants[key]) ? a_taskInfo._details.importants[key] : fcf.argVal();

          let deps = [];
          if (a_taskInfo._details.dependencies[key]){
            deps = a_taskInfo._details.dependencies[key];
          } else {
            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._details.importants[key])){
              fcf.append(deps, this._getTokenizeDependencies(a_taskInfo, a_taskInfo._details.importants[key]));
              fcf.append(deps, a_taskInfo._details.importants[key].dependencies);
            }
            if (a_taskInfo._details.template.hardDependencies[key])
              fcf.append(deps, a_taskInfo._details.template.hardDependencies[key]);
            a_taskInfo._details.dependencies[key] = deps;
          }

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

    }

    NDetails.ArgsBuilder.validate = (a_value, a_validateArrays, a_skipError) => {
      if (!Array.isArray(a_validateArrays) || !a_validateArrays.length){
        return a_value;
      }
      let valueType     = typeof a_value;
      let valueIsArray  = Array.isArray(a_value);
      let valueIsNaN    = typeof a_value == "number" && isNaN(a_value);
      let valueIsNull   = a_value === null;
      let replace       = false;

      for(let i = 0; i < a_validateArrays.length; ++i) {
        if (!Array.isArray(a_validateArrays[i]))
          a_validateArrays[i] = [a_validateArrays[i]];

        for(let j = 0; j < a_validateArrays[i].length; ++j) {
          let allow = true;
          if (replace && !("error" in a_validateArrays[i][j]))
            continue;

          if (Array.isArray(a_validateArrays[i][j].denyType)){
            let found = false;
            for(let k = 0; k < a_validateArrays[i][j].denyType.length; ++k) {
              let type = a_validateArrays[i][j].denyType[k];
              if (type == "array" && valueIsArray) {
                found = true;
                break;
              } else if (type == "nan" && valueIsNaN) {
                found = true;
                break;
              } else if (type == "null" && valueIsNull) {
                found = true;
                break;
              } else if (type == valueType){
                found = true;
                break;
              }
            }
            if (found)
              allow = false;
          }

          if (allow && Array.isArray(a_validateArrays[i][j].allowType)){
            let found = false;
            for(let k = 0; k < a_validateArrays[i][j].allowType.length; ++k) {
              let type = a_validateArrays[i][j].allowType[k];
              if (type == "array" && valueIsArray) {
                found = true;
                break;
              } else if (type == "nan" && valueIsNaN) {
                found = true;
                break;
              } else if (type == "null" && valueIsNull) {
                found = true;
                break;
              } else if (type == valueType){
                found = true;
                break;
              }
            }
            if (!found)
              allow = false;
          }

          if (allow && Array.isArray(a_validateArrays[i][j].denyValue)){
            let found = false;
            for(let k = 0; k < a_validateArrays[i][j].denyValue.length; ++k) {
              let value = a_validateArrays[i][j].denyValue[k];
              if (value === a_value){
                found = true;
                break;
              }
            }
            if (found)
              allow = false;
          }

          if (allow && Array.isArray(a_validateArrays[i][j].allowValue)){
            let found = false;
            for(let k = 0; k < a_validateArrays[i][j].allowValue.length; ++k) {
              let value = a_validateArrays[i][j].allowValue[k];
              if (value === a_value){
                found = true;
                break;
              }
            }
            if (!found)
              allow = false;
          }

          if (!allow) {
            if (!a_skipError && "error" in a_validateArrays[i][j]){
              if (fcf.empty(a_validateArrays[i][j].error) || a_validateArrays[i][j].error === true){
                throw new fcf.Exception("ERROR", {error: "Incorrect data type. Template argument has a validation constraint."});
              } else {
                throw new fcf.Exception("ERROR", {error: a_validateArrays[i][j].error});
              }
            } else {
              replace = true;
              a_value = fcf.clone(a_validateArrays[i][j].value);
            }
          }
        }
      }

      return a_value;
    }

    return NDetails.ArgsBuilder;
  }
});
