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

    NRender.getParentId = (a_parentId, a_state) => {
      while(a_state.args[a_parentId] && "fcfWrapper" in a_state.args[a_parentId] && !a_state.args[a_parentId].fcfWrapper){
        a_parentId = a_state.args[a_parentId].fcfParent;
      }
      return a_parentId;
    }

    NRender.getIdFromStorage = (a_id, a_args, a_state) => {
      if ("fcfWrapper" in a_args && !a_args.fcfWrapper){
        a_id = a_args.fcfParent;
      } else {
        return a_id;
      }
      while(a_state.args[a_id] && "fcfWrapper" in a_state.args[a_id] && !a_state.args[a_id].fcfWrapper){
        a_id = a_state.args[a_id].fcfParent;
      }
      return a_id;
    }

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

    NRender.TaskInfo = function(a_options) {
      var self = this;
      fcf.append(this, a_options);

      this.setArg = async function(a_path, a_value){
        await this._callHookBeforeArgument(a_path);

        if (fcf.isArg(a_value)){
          this.srcArgs[a_path] = a_value;
          this.fullSrcArgs[a_path] = a_value;
          this.processedArgs[a_path] = false;
          a_value = await this._details.owner.buildArg(a_path, this);
        }

        this.args[a_path] = a_value;
        delete this.srcArgs[a_path];
        if (!(a_path in this.fullSrcArgs))
          this.fullSrcArgs[a_path] = a_value;
        this.processedArgs[a_path] = true;

        await this._callHookAfterArgument(a_path);
      }


      this.setValue = async function(a_path, a_value) {
        await this._callHookBeforeArgument(a_path);

        let curArgs   = this.args;
        let curSrcArg = this.fullSrcArgs[a_path];
        let curValue  = a_value;
        while(fcf.isArg(curSrcArg) && curSrcArg.type === "reference") {
          let id      = fcf.tokenize(curSrcArg.object.id, {args: curArgs});
          let refPath = fcf.tokenize(curSrcArg.arg, {args: curArgs});
          curArgs = this._details.state.args[id];
          if (!curArgs)
            break;
          let ref = fcf.resolveEx(curArgs, refPath, true);
          ref.object[ref.key] = curValue;
          let argName = fcf.parseObjectAddress(refPath)[0];
          if (!argName || !this._details.state.sources[id] || !this._details.state.sources[id][argName])
            break;
          curSrcArg = this._details.state.sources[id][argName];
          curValue  = curArgs[argName];
        }

        this.args[a_path] = a_value;
        delete this.srcArgs[a_path];
        if (!(a_path in this.fullSrcArgs))
          this.fullSrcArgs[a_path] = a_value;
        this.processedArgs[a_path] = true;

        await this._callHookAfterArgument(a_path);
      }

      this.render = function(a_options, a_args, a_callPosition){
        if (typeof a_options === "string"){
          let options = {
            template: a_options,
            args: a_args,
          }
          a_options = options;
        }

        if (typeof a_args === "string"){
          a_callPosition = a_args;
        }

        if (!a_options.template.split("+")[0])
          a_options.template = this.template.split("+")[0] + a_options.template;

        let self = this;
        let fcfCP = this._getCallPosition(a_options.template, a_callPosition);
        let inheritArgs = {};
        let parentId = fcf.NRender.getParentId(this.args.fcfId, this._details.state);
        let args = fcf.append({}, a_options.args, inheritArgs, { fcfParent: parentId, fcfCP: fcfCP});
        let templateInfo = undefined;

        return fcf.actions()
        .then(()=>{
          if (fcf.getPath(self.template) == fcf.getPath(a_options.template))
            throw new fcf.Exception("ERROR_REDNER_LOOP", {template: a_options.template});
          return loadTemplateInfo(a_options.template);
        })
        .then((a_templateInfo)=>{
          templateInfo = a_templateInfo;
          let block = a_options.template.split("+")[1];
          if (!block)
            block = "";
          if (!templateInfo)
            return;

          let existsWrapper = !("wrapper" in templateInfo.options) || !!templateInfo.options.wrapper;
          helper.appendChildInfo(args, {fcfCP: fcfCP}, self.args.fcfChildsArgs, existsWrapper);

          if (!templateInfo.hooks || !templateInfo.hooks.hookSubtemplateRender)
            return;
          return templateInfo.hooks.hookSubtemplateRender({
              args:            self.args,
              renderedOptions: templateInfo.options,
              renderedArgs:    args,
              renderedPath:    a_options.template,
            });
        })
        .then(()=>{
          if (fcf.isServer() || templateInfo.options.clientRendering){
            return fcf.application.render({
              theme:      this._details.state.theme,
              template:   a_options.template,
              route:      this.route,
              args:       args,
              state:      this._details.state,
              request:    this.request,
              reqursion:  true,
              onResult:   (a_error, a_template)=>{ if (a_options.onResult) a_options.onResult(a_error, a_template.content); }
            })
          } else {
            return fcf.application.render({
              parent:     self.args.fcfId,
              template:   a_options.template,
              url:        this.route.url,
              args:       args,
              secondary:  true,
              state:      this._details.state,
              onResult:   (a_error, a_template)=>{ if (a_options.onResult) a_options.onResult(a_error, a_template.content); }
            });
          }
        })
        .then((a_template)=>{
          return a_template.content;
        });
      }

      this.buildArg = function(a_srcArg){
        return this._details.owner.buildArg(a_srcArg, this);
      }

      this._callHookBeforeArgument = async function(a_path){
        let fcfstate = fcf.getState();
        if (!this._processedHooksBeforeArgument)
          this._processedHooksBeforeArgument = {}
        if (a_path in this._processedHooksBeforeArgument)
          return;
        this._processedHooksBeforeArgument[a_path] = true;
        let template = this._details.template;
        if (template.hooks.hooksBeforeArgument && typeof template.hooks.hooksBeforeArgument[a_path] == "function") {
          await template.hooks.hooksBeforeArgument[a_path].call(template.hooks, a_taskInfo, a_path);
          fcf.setState(fcfstate);
        }
        if (template.hooks.hooksBeforeArgument && typeof template.hooks.hooksBeforeArgument["*"] == "function"){
          await template.hooks.hooksBeforeArgument["*"].call(template.hooks, a_taskInfo, a_path);
          fcf.setState(fcfstate);
        }
      }

      this._callHookAfterArgument = async function(a_path){
        let fcfstate = fcf.getState();
        if (!this._processedHooksAfterArgument)
        this._processedHooksAfterArgument = {}
        if (a_path in this._processedHooksAfterArgument)
          return;
        this._processedHooksAfterArgument[a_path] = true;

        let template = this._details.template;
        if (template.hooks.hooksAfterArgument && typeof template.hooks.hooksAfterArgument[a_path] == "function"){
          await template.hooks.hooksAfterArgument[a_path].call(template.hooks, a_taskInfo, a_path);
          fcf.setState(fcfstate);
        }
        if (template.hooks.hooksAfterArgument && typeof template.hooks.hooksAfterArgument["*"] == "function"){
          await template.hooks.hooksAfterArgument["*"].call(template.hooks, a_taskInfo, a_path);
          fcf.setState(fcfstate);
        }

      }

      this._getCallPosition = function(a_template, a_cp){
        var callPostition = a_cp ? a_cp : "";

        if (!callPostition) {
          let error = undefined;
          try { throw new Error()} catch(e) { error = e };
          var stackArr = error.stack.split("\n");
          var strInfo  = fcf.trim(stackArr[0].indexOf(":") == -1 ? stackArr[3] : stackArr[2], [" ", ")"]);
          var dc = 0;
          if (strInfo.length) {
            var charCode0 = "0".charCodeAt(0);
            var charCode9 = "9".charCodeAt(0);
            for (var i = strInfo.length-1; i >= 0; --i) {
              var charCode = strInfo.charCodeAt(i);
              if (charCode >= charCode0 && charCode <= charCode9)
                continue;
              ++dc;
              if (dc >= 2){
                callPostition = strInfo.substr(i+1);
                break;
              }
            }
          }
        }

        let sid = fcf.NRender.getIdFromStorage(this.args.fcfId, this.args, this._details.state);
        var cp = "hook:" + this._details.template.template.originPath + ":" + a_template + ":" + sid + ":" + callPostition;
        if (!(cp in this._details.state.cpMap))
          this._details.state.cpMap[cp] = 0;
          let fcfAlias = this.args.fcfAlias
                            ? this.args.fcfAlias :
                         this.fullSrcArgs.fcfAlias
                            ? helper.resolveArg(this.fullSrcArgs.fcfAlias, this.fullSrcArgs, this._details.state) :
                              undefined;
          if (fcfAlias) {
            cp += "-a:" + fcfAlias;
          } else {
            cp += "-" + (++this._details.state.cpMap[cp]);
          }


        return cp;
      }

    }

    return NRender.TaskInfo;
  }
});
