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

    NDetails.TemplateProcessor = function(a_settings) {
      /**
      * @fn constructor(a_settings)
      * @param fcf::NRender::Render           render рендер шаблона
      * @param fcf::NRender::NDetails::Loader loader загрузчик информации о шаблоне
      * @param object                         args   объект аргументов шаблона
      **/

      var self = this;

      /**
      * @fn void build(a_options)
      * @param object a_options
      *         - string                          template      адрес шаблона
      *         - object                          rawTemplate   объект информации о шаблоне
      *         - object                          request       Объекта запроса от клиента
      *         - object                          route         объект информации о маршруте
      *         - object                          context       объект информации о маршруте
      *         - fcf::NRender::NDetails::Loader  loader        объект загрузчика данных о шаблоне
      *         - object                          args
      *         - object                          sources       объект с исходными данными аргументов шаблона
      *         - object                          aliases       Алиасы путей шаблонов
      *         - object                          state         состояние рекурсивного рендеринга
      *         - object                          projections   объект-карта проекций
      *         - bool                            update [optional] флаг обновления повторной отрисовки на клиенте
      *         - function  onResult                            Обратный вызов результата \n
      *                  Сигнатура: void onResult(fcf::Exception a_error, a_content)
      **/
      this.build = function(a_options) {
        var currentTemplate = fcf.getContext().currentTemplate;
        var template = a_options.template.split("+")[0];
        var templateRender = new TemplateRender({request: a_options.request, args: a_options.args, state:  a_options.state});
        var renderingBlocks = self._getRenderingBlocks(a_options.template, a_options.rawTemplate, a_options.aliases ? a_options.aliases : {});
        var projections = a_options.projections ? a_options.projections : fcf.application.getProjections().getProjections();

      
        var decor = typeof a_options.state.theme.getInfo().decor == "object" ?  a_options.state.theme.getInfo().decor : {};
        var aliases = typeof a_options.state.theme.getInfo().aliases == "object" ?  a_options.state.theme.getInfo().aliases : {};
        fcf.setContext(a_options.context);
        try {
          self._renderBlock(
            a_options.rawTemplate.template.template,
            {
              render: templateRender,
              args:a_options.args,
              sources: a_options.sources,
              route: a_options.route,
              decor: decor,
              aliases: aliases,
              template: function(a_template, a_args) {
                return render.template({template: a_template, args: a_args });
              },
              path:         a_options.rawTemplate.template.path,
              stringNumber: a_options.rawTemplate.template.stringNumber,
            },
            template);
        } catch (e){
          a_options.onResult(e);
          return;
        }
      
        let result = templateRender.buffer;
        let isRoot = a_options.args.fcfId && a_options.state.root && a_options.state.root.id === a_options.args.fcfId;

        if (a_options.args.fcfWrapper && !isRoot) {
          var content = "<span ";
          content += "fcftemplate=\"" + a_options.state.theme.resolveAlias(a_options.template) + "\" ";
          content += " id='" + a_options.args.fcfId + "' ";
          content += " class=' " + fcf.str(a_options.args.fcfClassInner) + " " + fcf.str(a_options.args.fcfClass) + "' "
          content += a_options.args.fcfEvntid ? " fcfevntid='" + a_options.args.fcfEvntid + "'" : "";
          
          if (a_options.args.fcfStyle || a_options.args.fcfStyleInner)
            content += " style='" + fcf.str(a_options.args.fcfStyleInner) + ";" + fcf.str(a_options.args.fcfStyle) + "' ";
          if (typeof a_options.args.fcfAttributes == "object"){
            for(var k in a_options.args.fcfAttributes)
              content += " " + k + "=\"" + a_options.args.fcfAttributes[k] + "\" ";
          }
          if (a_options.args.fcfParent)
            content += "fcfparent=\"" + a_options.args.fcfParent + "\" ";
          if (a_options.args.fcfAlias)
            content += "fcfalias=\"" + a_options.args.fcfAlias + "\" ";
          content +=">" + result + "</span>";
          result = content;
        }

        var inlineTemplates = {};
        (function(inlineTemplates){

          fcf.actions()
          .catch((a_error)=>{
            a_options.onResult(a_error); 
          })
          .each(templateRender.templates, function(a_key, a_templateInfo, a_res, a_act){
            var inheritArgs = {};
            helper.appendChildInfo(inheritArgs, a_templateInfo.options.args, a_options.args.fcfChildsArgs);

            if (a_key != templateRender.markerHeader) {
              if (a_templateInfo.type === "template") {
                var args = fcf.append({}, a_templateInfo.options.args, inheritArgs);
                var options = fcf.append(
                  {},
                  a_templateInfo.options,
                  {
                    request: a_options.request,
                    route:  a_options.route,
                    state: a_options.state,
                    update: a_options.update,
                    context: a_options.context,
                    args: args,
                    reqursion: true
                  }
                );

                if (options.template.charAt(0) == "+"){
                  options.template = a_options.template.split("+")[0] + options.template;
                }

                a_options.loader.loadInfo(options.template)
                .then((a_renderedTemplate)=>{
                  if (!a_options.rawTemplate.hooks.hookRender)
                    return;
                  fcf.getContext().currentTemplate = currentTemplate;
                  return a_options.rawTemplate.hooks.hookRender({
                    args:            a_options.args,
                    renderedArgs:    options.args,
                    renderedOptions: a_renderedTemplate.options,
                    renderedPath:    options.template,
                  });
                })
                .then(()=>{
                  return a_options.render.render(options);
                })
                .then((a_template)=>{
                  inlineTemplates[a_key] = a_template.content;
                  a_act.complete();
                })
                .catch((a_error)=>{
                  fcf.log.err("Render", a_error);
                  a_act.error(a_error);
                })
              } else if (a_templateInfo.type === "view") {
                let fcfId   = fcf.genId();
                let options = {args: {}};
                let mode = a_templateInfo.options.mode ?                                      a_templateInfo.options.mode : 
                           a_templateInfo.options.view && a_templateInfo.options.view.mode ?  a_templateInfo.options.view.mode : 
                                                                                              "read";
                let view = fcf.buildView(a_templateInfo.options.view, mode);

                for(let key in view)
                  if (key != "args")
                    options.args[key] = view[key];
                fcf.append(options.args, view.args);
                fcf.append(options.args, a_templateInfo.options.args);
                fcf.append(options.args, inheritArgs);
                if ("value" in a_templateInfo.options)
                  options.args.value = a_templateInfo.options.value;
                else if ("value" in a_templateInfo.options.view)
                  options.args.value = a_templateInfo.options.view.value;
                else if ("value" in a_templateInfo.options.args)
                  options.args.value = a_templateInfo.options.args.value;
                if (!options.args.fcfAlias && view.alias)
                  options.args.fcfAlias = view.alias;
                if (!options.args.fcfId)
                  options.args.fcfId = fcfId;
                else
                  fcfId = options.args.fcfId;

                if (mode.split(".")[0] == "add" && view.default !== undefined){
                  if (options.args.value == undefined){
                    options.args.value = view.default;
                    options.args.defaultFlag = true;
                  } else if (fcf.isArg(options.args.value) && options.args.value.type == "reference"){
                    let id    = options.args.value.object.id;
                    let arg   = options.args.value.arg;
                    let ptr   = fcf.resolveEx(a_options.state.args[id], arg, true);
                    if (ptr.object[ptr.key] === undefined){
                      ptr.object[ptr.key] = view.default;
                      ptr.object[ptr.key].defaultFlag = true;
                      if (!a_options.state.resets[fcfId])
                        a_options.state.resets[fcfId] = {};
                      a_options.state.resets[fcfId].value = 1;
                    }
                  }
                }
                  
                    mode      = a_templateInfo.options.mode ? a_templateInfo.options.mode : "read";
                let template  = a_options.state.theme.getView(mode, a_templateInfo.options.view).template;
                if (!options.args.mode)
                  options.args.mode = mode;

                a_options.loader.loadInfo(template, (a_error, a_template)=>{
                  if (!a_template)
                    return;
                  let block = template.split("+")[1];
                  if (!block)
                    block = "";
                  if (!a_template.templates[block])
                    return;
                  if (!a_template.templates[block].hooks.hookBeforeView)
                    return;
                  let hookOptions = {
                    record: a_templateInfo.options.record,
                    view:   a_templateInfo.options.view,
                    mode:   a_templateInfo.options.mode,
                    args:   options.args,
                  }
                  if (fcf.isArg(hookOptions.record)){
                    if (hookOptions.record.type === "reference") {
                      hookOptions.record = fcf.resolve(a_options.state.args[hookOptions.record.object.id], hookOptions.record.arg);
                    } else if (hookOptions.record.type === "value") {
                      hookOptions.record = fcf.tokenizeObject(hookOptions.record.value, {}, true);
                    }
                  }
                  fcf.getContext().currentTemplate = currentTemplate;
                  return a_template.templates[block].hooks.hookBeforeView(hookOptions)
                  .then(()=>{
                    return a_template;
                  })
                })
                .then((a_renderedTemplate)=>{
                  if (!a_options.rawTemplate.hooks.hookRender)
                    return;
                  fcf.getContext().currentTemplate = currentTemplate;
                  return a_options.rawTemplate.hooks.hookRender({
                    args:            a_options.args,
                    renderedArgs:    options.args,
                    renderedOptions: a_renderedTemplate.options,
                    renderedPath:    template,
                  });
                })
                .then(()=>{
                  options = fcf.append(options,
                    {
                      template: template,
                      request: a_options.request,
                      route:  a_options.route,
                      state: a_options.state,
                      update: a_options.update,
                      reqursion: true,
                    }
                  );
                  return a_options.render.render(options);
                })
                .then((a_template)=>{
                  inlineTemplates[a_key] = a_template.content;
                  a_act.complete();
                })
                .catch((a_error)=>{
                  fcf.log.err("Render", a_error);
                  a_act.error(a_error);
                })
              } else {
                a_act.complete();
              }
            } else {
              a_act.complete();
            }
          })
          .then(function(){
            if (templateRender.templates[templateRender.markerHeader])
              inlineTemplates[templateRender.markerHeader] = templateRender._header();
          })
          .then(function(){
            if (!fcf.empty(inlineTemplates)){
              var newResult = "";
              var curPos = 0;
              var endPos = 0;
              var lstPos = 0;
              while(true){
                curPos = result.indexOf(templateRender.markerPrefix, lstPos);
                if (curPos != -1){
                  endPos = result.indexOf(templateRender.markerSuffix, curPos);
                  var key = result.substr(curPos, endPos - curPos + templateRender.markerSuffix.length);
                  newResult += result.substr(lstPos, curPos - lstPos);
                  newResult += inlineTemplates[key];
                } else {
                  newResult += result.substr(lstPos);
                  break;
                }
                lstPos = endPos + templateRender.markerSuffix.length;
              }
              result = newResult;
            }

            a_options.onResult(undefined, result);
          });
        })(inlineTemplates);
      }

      this._renderBlock = function(a_code, a_options, a_template) {
        fcf.getContext().currentTemplate = {id: a_options.args.fcfId};
        let ov = fcf.NDetails.currentTemplate;
        fcf.NDetails.currentTemplate = a_template;
        fcf.scriptExecutor.execute(a_code, a_options, a_options.path, a_options.stringNumber);
        fcf.NDetails.currentTemplate = ov;
      }

      this._getRenderingBlocks = function(a_templatePath, a_templateInfo, a_aliases) {
        a_templatePath = fcf.getPath(a_templatePath, a_aliases, true);
        var octothorpePos = a_templatePath.indexOf("+");
        if (octothorpePos !== -1) {
          return [a_templatePath.substr(octothorpePos+1, a_templatePath.length - octothorpePos - 1)];
        } else {
          return a_templateInfo.defaultBlocks;
        }
      }

    }

    return NDetails.TemplateProcessor;
  }
});
