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

    /**
    * @class fcf::NRender::TaskInfo
    * @brief Класс задачи рендеринга шаблона
    **/
    NRender.TaskInfo = function(a_options) {
      var self = this;
      fcf.append(this, a_options);

      this._callMap = {};


      /**
      * @var object args;
      *lng_en @brief The resulting template arguments
      *lng_ru @brief Результирующие аргументы шаблона
      **/

      /**
      * @var fcf::NRender::Render render
      *lng_en @brief Render object
      *lng_ru @brief Объект рендера
      **/


      /**
      * @var object srcArgs
      *lng_en @brief Description of template arguments waiting to be build
      *lng_ru @brief Описание аргументов шаблона ожидающие сборки
      **/

      /**
      * @var fcf::Actions actions
      *lng_ru @brief Объект отложенных вызовов функций
      **/

      /**
      * @var fcf::RouteInfo route
      *lng_en @brief The object of the description of the current route
      *lng_ru @brief Объект описания текущего маршрута
      **/

      /**
      * @var string templatePath
      *lng_en @brief Template address
      *lng_ru @brief Адрес шаблона
      **/

      /**
      * @fn void setArg(string a_path, mixed a_value)
      *
      *lng_en @brief To set a variable of the template used in the hooks.
      *lng_en @brief After calling this method, the a_path argument will not be automatically assembled
      *lng_en @param string a_path The path/filename argument
      *lng_en @param mixed a_value New value
      *
      *lng_ru @brief Установить переменную шаблона, используется в хуках.
      *lng_ru @brief После вызова данного метода автоматическая сборка аргумента a_path выполняться не будет
      *lng_ru @param string a_path Путь/имя аргумента
      *lng_ru @param mixed a_value Устанавливаемое значение
      **/
      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.object.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];
        }

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


      /**
      * void render(object a_options)
      *lng_en @brief performs rendering of the template
      *lng_en   - string template Path to the template
      *lng_en   - string args     Template arguments
      *lng_en   - function onResult(a_error, a_template)
      *
      *lng_ru @brief выполняет рендер шаблона
      *lng_ru   - string template Путь к шаблону
      *lng_ru   - string args     Аргументы шаблона
      *lng_ru   - function onResult(a_error, a_template)
      **/
      this.render = function(a_options){
        let self = this;
        var fcfCP = this._getCallPosition();
        var inheritArgs = {};

        helper.appendChildInfo(inheritArgs, {fcfCP: fcfCP}, this.args.fcfChildsArgs);
        var args = fcf.append({}, a_options.args, inheritArgs, { fcfParent: this.args.fcfId, fcfCP: fcfCP});

        return fcf.actions()
        .then(()=>{
          return fcf.application.getRender().getLoader().loadInfo(a_options.template)
        })
        .then((a_template)=>{
          let block = a_options.template.split("+")[1];
          if (!block)
            block = "";
          if (!a_template.templates[block])
            return;
          if (!a_template.templates[block].hooks.hookRender)
            return;
          return a_template.templates[block].hooks.hookRender({
              args:            self.args,
              renderedOptions: a_template.options,
              renderedArgs:    args,
              renderedPath:    a_options.template,
            });
        })
        .then(()=>{
          return fcf.application.getRender().render({
            theme:      this._details.state.theme,
            template:   a_options.template,
            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); }
          })
        })
        .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){
        fcf.setContext(this.context);
        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.setContext(this.context);
        }
        if (template.hooks.hooksBeforeArgument && typeof template.hooks.hooksBeforeArgument["*"] == "function"){
          await template.hooks.hooksBeforeArgument["*"].call(template.hooks, a_taskInfo, a_path);
          fcf.setContext(this.context);
        }
      }

      this._callHookAfterArgument = async function(a_path){
        fcf.setContext(this.context);
        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.setContext(this.context);
        }
        if (template.hooks.hooksAfterArgument && typeof template.hooks.hooksAfterArgument["*"] == "function"){
          await template.hooks.hooksAfterArgument["*"].call(template.hooks, a_taskInfo, a_path);
          fcf.setContext(this.context);
        }

      }

      this._getCallPosition = function(){
        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 callPostition = "";
        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;
            }
          }
        }

        var cp = "hook:"+callPostition;
        if (!(cp in this._callMap))
          this._callMap[cp] = 0;
        cp += "-" + (++this._callMap[cp]);
        return cp;
      }

    }

    return NRender.TaskInfo;
  }
});
