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

    function resolveWrapperPath(a_template) {
      a_template = helper.normalizeTemplatePath(a_template);
      let arr = a_template.split("+");
      arr[0] = arr[0].substr(arr[0].length - ".tmpl".length, ".tmpl".length) == ".tmpl" ? arr[0].substr(0, arr[0].length - ".tmpl".length) : arr[0];
      return arr[0] + (arr[1] ? "+" + arr[1] : "") + ".wrapper.js"
    }

    async function getTemplateInfo(a_template){
      let template = helper.normalizeTemplatePath(a_template);
      if (fcf.NDetails.renderInstructions[template])
        return fcf.NDetails.renderInstructions[template];
      let wrapper = resolveWrapperPath(a_template);
      await fcf.require([wrapper]);
      return fcf.NDetails.renderInstructions[template];
    }

    function appendChildInfo(a_dstArgs, a_cp, a_chlidInfo, a_existsWrapper){
      if (!a_chlidInfo || !a_chlidInfo.childs)
        return;
      if (!a_cp)
        return;
      if (a_existsWrapper) {
        if (a_chlidInfo.childs[a_cp]) {
          fcf.append(a_dstArgs, a_chlidInfo.childs[a_cp].args);
          a_dstArgs.fcfChildsArgs = a_chlidInfo.childs[a_cp];
        }
      } else {
        a_dstArgs.fcfChildsArgs = a_chlidInfo;
      }
    };

    class TemplateRender{
      constructor(a_initializeOptions){
        this._buffer = [];
        this._initializeOptions = a_initializeOptions;
        this.page = this._initializeOptions.state.page;
      }



      getTheme(){
        return fcf.application.getTheme();
      }

      write(a_content){
        this._buffer.push(a_content);
      }

      writeRuntime(a_content, a_rid, a_variables, a_rtype){
        let fcfRuntime = this._initializeOptions.args.fcfRuntime;
        if (!fcfRuntime) {
          fcfRuntime = {};
          this._initializeOptions.args.fcfRuntime = fcfRuntime;
          this._initializeOptions.state.args[this._initializeOptions.args.fcfId].fcfRuntime = this._initializeOptions.args.fcfRuntime;
          this._initializeOptions.state.sources[this._initializeOptions.args.fcfId].fcfRuntime = this._initializeOptions.args.fcfRuntime;
        }
        let rrid = a_rtype == 3 ? this._getLastRRID() : this._getRRID();
        if (!(a_rid in fcfRuntime))
          fcfRuntime[a_rid] = {};
        if (!(rrid in fcfRuntime[a_rid]))
          fcfRuntime[a_rid][rrid] = [];
        fcfRuntime[a_rid][rrid].push({
          t: a_rtype,
          v: a_variables,
          c: a_rtype > 1 ? a_content : undefined,
        });

        if (a_rtype == 0) {
          this._buffer.push(" rrid=\"" + rrid + "\" ");
        } if (a_rtype == 1) {
          this._buffer.push("<span rrid=\"" + rrid + "\" >");
          this._buffer.push(a_content);
          this._buffer.push("</span>");
        } else {
          this._buffer.push(a_content);
        }

        if (a_rtype <= 1)
          this._nextRRID();
      }

      async build(){
        let self = this;
        let result = "";
        let map = {};
        for(let i = 0; i < this._buffer.length; ++i){
          if (this._buffer[i] instanceof Promise || this._buffer[i] instanceof fcf.Actions){
            if (this._buffer[i] instanceof fcf.Actions && this._buffer[i]._deferred)
              this._buffer[i].startup();
            result += fcf.str(await this._buffer[i]);
          } else {
            result += fcf.str(this._buffer[i]);
          }
        }
        return result;
      }

      template(a_options, a_args, a_callPosition, a_deferred){
        let self = this;
        if (typeof a_options === "string"){
          a_options = {
            template: a_options,
            args: a_args,
          };
        }
        if (typeof a_args === "number")
          a_callPosition = a_args;

        let externalArgs = {};
        this._fillExternalArgs(externalArgs, this.id, a_options.args);

        let themeName = this._initializeOptions.state && this._initializeOptions.state.theme ? this._initializeOptions.state.theme : undefined;
        a_options.template = helper.normalizeTemplatePath(a_options.template, themeName);

        return fcf.actions({deferred: a_deferred}).then(async ()=>{
          let templatePath = a_options.template;
          if (templatePath[0] == "+"){
            let fcftemplate = helper.normalizeTemplatePath(self._initializeOptions.template).split("+")[0];
            templatePath = fcftemplate + templatePath;
          }

          if (fcf.getPath(self._initializeOptions.template) == fcf.getPath(templatePath))
            throw new fcf.Exception("ERROR_REDNER_LOOP", {template: templatePath});

          let originPath = (await getTemplateInfo(this._initializeOptions.template)).originPath;
          let templateInfo = await getTemplateInfo(templatePath);
          let cp      = this._getCallPosition(originPath, a_args, a_options.template, a_callPosition);
          a_options.args.fcfCP = cp;
          a_options.args.fcfParent = fcf.NRender.getParentId(self._initializeOptions.args.fcfId, self._initializeOptions.state);

          if (self._initializeOptions.args.fcfInitialStorageOfChildren)
            a_options.args.fcfInitialStorageOfChildren = true;

          let ri = fcf.NDetails.renderInstructions[self._initializeOptions.template]
          if (ri && ri.hooks && ri.hooks.hookSubtemplateRender){
            await ri.hooks.hookSubtemplateRender({
                args:            self._initializeOptions.args,
                renderedOptions: templateInfo.options,
                renderedArgs:    a_options.args,
                renderedPath:    templatePath,
              });
          }

          let existsWrapper = !("wrapper" in templateInfo.options) || !!templateInfo.options.wrapper;
          appendChildInfo(a_options.args, cp, self._initializeOptions.args.fcfChildsArgs, existsWrapper);

          let template = await fcf.application.render({
            template:     templatePath,
            parent:       self._initializeOptions.fcfId,
            args:         a_options.args,
            externalArgs: externalArgs,
            secondary:    true,
            state:        self._initializeOptions.state,
          });

          if (!templateInfo.options.clientRendering){
            fcf.append(self._initializeOptions.state.args, template.state.args)
            fcf.append(self._initializeOptions.state.include, template.state.include)
            fcf.append(self._initializeOptions.state.originSourcesKeys, template.state.originSourcesKeys)
            fcf.append(self._initializeOptions.state.header, template.state.header);
            fcf.append(self._initializeOptions.state.include, template.state.include);
            fcf.append(self._initializeOptions.state.keywords, template.state.keywords);
            if (template.state.title)
              self._initializeOptions.state.title = template.state.title;
            fcf.append(self._initializeOptions.state.sources, template.state.sources);
          }
          return template.content;
        });
      }
      view(a_options, a_callPosition, a_deferred){
        let self = this;
        let cp;

        return fcf.actions({deferred: a_deferred}).then(async ()=>{
          let parentId = fcf.NRender.getParentId(self._initializeOptions.args.fcfId, self._initializeOptions.state);
          let args = fcf.append({}, a_options.args, { fcfParent: parentId });

          let mode = a_options.mode ?                         a_options.mode :
                     a_options.view && a_options.view.mode ?  a_options.view.mode :
                                                              "read";
          let view = fcf.buildView(a_options.view, mode, fcf.application.getTheme());

          for(let key in view)
            if (key != "args" && key != "template")
              args[key] = view[key];

          fcf.append(args, view.args);
          fcf.append(args, a_options.args);

          if (view.alias)
            args.fcfAlias = view.alias;

          if ("value" in a_options){
            args.value = a_options.value;
          } else if ("value" in a_options.view) {
            args.value = a_options.view.value;
          } else if (a_options.args && "value" in a_options.args) {
            args.value = a_options.args.value;
          } else if (!fcf.isArg(a_options.record) && args.fcfAlias in a_options.record) {
            args.value = a_options.record[args.fcfAlias];
          } else if (fcf.isArg(a_options.record) && a_options.record.type == "reference" && args.fcfAlias) {
            args.value = fcf.clone(a_options.record);
            args.value.arg += `[${JSON.stringify(args.fcfAlias)}]`;
          }

          if (!args.fcfId)
            args.fcfId = fcf.id();

          if (self._initializeOptions.args.fcfInitialStorageOfChildren)
            a_options.args.fcfInitialStorageOfChildren = true;

          if (mode.split(".")[0] == "add" && view.default !== undefined) {
            if (args.value == undefined){
              args.value = view.default;
              args.defaultFlag = true;
            } else if (fcf.isArg(args.value) && args.value.type == "reference"){
              let id    = args.value.object.id;
              let arg   = args.value.arg;
              let ptr   = fcf.resolveEx(self._initializeOptions.state.args[id], arg, true);
              if (ptr.object[ptr.key] === undefined) {
                ptr.object[ptr.key] = view.default;
                ptr.object[ptr.key].defaultFlag = true;
              }
            }
          }

          let originPath = (await getTemplateInfo(this._initializeOptions.template)).originPath;
          let templatePath = view.template;
          let themeName = this._initializeOptions.state && this._initializeOptions.state.theme ? this._initializeOptions.state.theme : undefined;
          templatePath = helper.normalizeTemplatePath(templatePath, themeName);

          if (!templatePath)
            throw new fcf.Exception("ERROR_NOT_SET_TEMPLATE_IN_VIEW", {type: view.type});
          if (fcf.getPath(self._initializeOptions.template) == fcf.getPath(templatePath))
            throw new fcf.Exception("ERROR_REDNER_LOOP", {template: templatePath});

          cp = this._getCallPosition(originPath, args, templatePath, a_callPosition)
          args.fcfCP = cp;

          if (!args.mode)
            args.mode = mode;

          for(let key in args){
            if (fcf.isArg(args[key]) && args[key].type == "recordreference"){
              if (fcf.isArg(a_options.record) && a_options.record.type == "reference") {
                args[key].type   = "reference";
                args[key].object = a_options.record.object;
                args[key].arg    = a_options.record.arg + `[${JSON.stringify(args[key].arg)}]`;
              }
            }
          }

          let externalArgs = {};

          let templateInfo = await getTemplateInfo(templatePath);
          self._fillExternalArgs(externalArgs, self._initializeOptions.args.fcfId, args);

          let existsWrapper = !("wrapper" in templateInfo.options) || !!templateInfo.options.wrapper;
          appendChildInfo(args, cp, self._initializeOptions.args.fcfChildsArgs, existsWrapper);

          let ri = fcf.NDetails.renderInstructions[self._initializeOptions.template]
          if (ri && ri.hooks && ri.hooks.hookSubtemplateRender){
            await ri.hooks.hookSubtemplateRender({
                args:            self._initializeOptions,
                renderedOptions: templateInfo.options,
                renderedArgs:    args,
                renderedPath:    templatePath,
              });
          }

          let template = await fcf.application.render({
            template:     templatePath,
            args:         args,
            externalArgs: externalArgs,
            state:        self._initializeOptions.state,
            secondary:    true,
          });

          fcf.append(self._initializeOptions.state.include, fcf.map(templateInfo.options.clientInclude, (k,v)=>{ return [v,v]}));
          fcf.append(self._initializeOptions.state.include, fcf.map(templateInfo.options.include, (k,v)=>{ return [v,v]}));

          if (!templateInfo.options.clientRendering){
            fcf.append(self._initializeOptions.state.args, template.state.args)
            fcf.append(self._initializeOptions.state.include, template.state.include)
            fcf.append(self._initializeOptions.state.originSourcesKeys, template.state.originSourcesKeys)
            fcf.append(self._initializeOptions.state.header, template.state.header);
            fcf.append(self._initializeOptions.state.include, template.state.include);
            fcf.append(self._initializeOptions.state.keywords, template.state.keywords);
            if (template.state.title)
              self._initializeOptions.state.title = template.state.title;
            fcf.append(self._initializeOptions.state.sources, template.state.sources);
          }

          return template.content;
        });
      }

      _getRRID(){
        if (!this._rrid)
          this._rrid = fcf.id();
        return this._rrid;
      }

      _getLastRRID(){
        return this._lrrid;
      }

      _nextRRID(){
        this._lrrid = this._rrid;
        this._rrid = fcf.id();
        return this._rrid;
      }


      _getCallPosition(a_originPath, a_args, a_template, a_callPosition){
        let sid = fcf.NRender.getIdFromStorage(this._initializeOptions.args.fcfId, this._initializeOptions.args, this._initializeOptions.state);
        var cp = "render:" + a_originPath + ":" + a_template + ":" + sid + ":" + a_callPosition;
        if (!(cp in this._initializeOptions.state.cpMap))
          this._initializeOptions.state.cpMap[cp] = 0;
        let fcfAlias = a_args.fcfAlias
                        ? helper.resolveArg(a_args.fcfAlias, a_args, this._initializeOptions.state)
                        : undefined;
        if (fcfAlias) {
          cp += "-a:" + fcfAlias;
        } else {
          cp += "-" + (++this._initializeOptions.state.cpMap[cp]);
        }

        return cp;
      }

      _fillExternalArgs(a_dst, a_ownerId, a_args){
        if (typeof a_args != "object")
          return;
        if (fcf.isArg(a_args) && a_args.type=="reference") {
          let tokenizeId  = a_args.object.id.indexOf("{{") != -1;
          let tokenizeArg = a_args.arg.indexOf("{{") != -1;
          let refId = a_args.object.id;
          let refArg = a_args.arg;
          if (tokenizeId || tokenizeArg) {
            let parentArgs  = a_args.fcfParent && this._initializeOptions.state.args[a_args.fcfParent] ? a_initializeOptions.state.args[a_args.fcfParent] :
                              a_args.fcfParent && fcf.getWrapper(a_args.fcfParent)                 ? fcf.getWrapper(a_args.fcfParent).getArgs() :
                                                                                                     {};
            refId   = tokenizeId ? fcf.tokenize(a_args.object.id, {args: this._initializeOptions.args, parent: parentArgs}) : a_args.object.id;
            refArg  = tokenizeArg ? fcf.tokenize(a_args.arg, {args: this._initializeOptions.args, parent: parentArgs}) : a_args.arg;
          }
          if (!a_dst[refId])
            a_dst[refId] = {};
          let refDst = fcf.resolveEx(a_dst[refId], refArg, true);
          let refArgs = this._initializeOptions.state.args[refId] ? this._initializeOptions.state.args[refId] :
                        fcf.getWrapper(refId)                 ? fcf.getWrapper(refId).getArgs() :
                                                                undefined;
          if (refArgs !== undefined)
            refDst.object[refDst.key] = fcf.resolve(refArgs, refArg);
        } else if (Array.isArray(a_args)) {
          for(let i = 0; i < a_args.length; ++i)
            this._fillExternalArgs(a_dst, a_ownerId, a_args[i]);
        } else {
          for(let k in a_args)
            this._fillExternalArgs(a_dst, a_ownerId, a_args[k]);
        }
      }

    }

    class Render {
      constructor(){
        this._argBuilder = new ArgsBuilder();
      }

      async getTemplateInfo(a_template){
        return await getTemplateInfo(a_template);
      }

      render(a_options){
        let self = this;
        let fcfcontext = fcf.getContext();
        let fcfstate   = fcf.getState();
        if (a_options.context)
          fcf.setContext(a_options.context);
        let parent = a_options.parent;
        let owner = a_options.owner;
        if (parent === undefined && owner === undefined) {
          owner = false;
        } else if (typeof owner === "object" && typeof owner._id == "string" && fcf.getWrapper(owner._id)) {
          parent = owner._id;
          owner  = owner.getDomElement();
        } else if (typeof owner === "string" && fcf.getWrapper(owner)) {
          parent = fcf.getWrapper(owner).getId();
          owner  = fcf.getWrapper(owner).getDomElement();
        } else if (typeof parent === "object" && typeof parent._id == "string" && fcf.getWrapper(parent._id)) {
          if (!owner)
            owner  = parent.getDomElement();
          parent = parent._id;
        } else if (typeof parent === "string" && fcf.getWrapper(parent)) {
          if (!owner)
            owner  = fcf.getWrapper(parent).getDomElement();
        }

        let themeName     = a_options.theme                          ? a_options.theme :
                            a_options.state && a_options.state.theme ? a_options.state.theme :
                                                                       undefined;
        a_options.template = helper.normalizeTemplatePath(a_options.template, themeName);
        return fcf.actions()
        .then(async function(a_res, a_act){

          function clCompleteRender(a_error, a_template, a_clientRendering){
            if (a_error) {
              fcf.log.err("FCF", a_error.message, a_error);
              if (a_options.onResult)
                a_options.onResult(a_error, a_template);
              a_act.error(a_error);
              return;
            }


            if (!a_options.secondary) {
              for(var id in a_template.state.args){
                let args    = a_template.state.args[id];
                let sources    = a_template.state.sources[id];
                let originSourcesKeys = a_template.state.originSourcesKeys[id];
                delete args.fcfChildsArgs;
                delete sources.fcfChildsArgs;

                if ("fcfWrapper" in args && !args.fcfWrapper){
                  let parentId = args.fcfParent;
                  let foundParent = false;
                  while(!!parentId) {
                    if (a_template.state.args[parentId]){
                      if (!("fcfWrapper" in a_template.state.args[parentId]) || a_template.state.args[parentId].fcfWrapper){
                        foundParent = true;
                        break;
                      } else {
                        parentId = a_template.state.args[parentId].fcfParent;
                      }
                    } else {
                      let obj = fcf.application.getLocalData().getOriginObject(parentId);
                      if (obj && (!("fcfWrapper" in obj) || obj.fcfWrapper)){
                        foundParent = true;
                        break;
                      }
                    }
                  };
                  if (foundParent) {
                    if (!fcf.NDetails._noWrapperChilds[parentId])
                      fcf.NDetails._noWrapperChilds[parentId] = {};
                    fcf.NDetails._noWrapperChilds[parentId][id] = fcf.NDetails._attachWrapperCounter[parentId] ? fcf.NDetails._attachWrapperCounter[parentId] : 0;
                  } else {
                    fcf.log.wrn("FCF:RENDER", "Memory leak! Using no wrapper template without parent")
                  }
                }
                for(let key in sources) {
                  if (fcf.isArg(sources[key]) && sources[key].type === "reference")
                    delete args[key];
                }
                let originObject = fcf.application.getLocalData().getOriginObject(id);
                let fcfCP = originObject ? originObject.fcfCP : undefined;
                if (fcfCP && fcfCP != args.fcfCP) {
                  let wrapper       = fcf.getWrapper(id);
                  let parentWrapper = wrapper ? wrapper.getParent() : undefined;
                  if (parentWrapper){
                    fcf.each(parentWrapper.getChilds(), (a_key, a_child)=>{
                      let childId = a_child.getId();
                      let childOriginObject = fcf.application.getLocalData().getOriginObject(childId);
                      if (childOriginObject.fcfCP == fcfCP) {
                        originObject.fcfId = childId;
                        childOriginObject.fcfId = id;
                        fcf.application.getLocalData().setOriginObject(id, childOriginObject);
                        fcf.application.getLocalData().setOriginObject(childId, originObject);
                        return false;
                      }
                    });
                  }
                }
                fcf.application.getLocalData().setObject(id, args);
                if (a_options.updateOriginState)
                  fcf.application.getLocalData().setOriginObject(id, args);
                fcf.application.getLocalData().setSourceObject(id, sources);
                fcf.application.getLocalData().setOriginSourcesKeys(id, originSourcesKeys);
              }

              function clCreate() {
                if (owner !== false) {
                  owner = typeof owner == "string"    ? fcf.select(owner)[0] :
                                 owner === undefined  ? document.body :
                                                        owner;
                  let div = document.createElement("div");
                  if ("fcfWrapper" in a_template.args && !a_template.args.fcfWrapper) {
                    div.innerHTML = a_template.content;
                    a_template.domElements = [];
                    for(var i = 0; i < div.children.length; ++i) {
                      var el = div.children[i];
                      a_template.domElements.push(el);
                      if (owner)
                        owner.appendChild(el);
                    }
                  } else {
                    div.innerHTML = a_template.content;
                    a_template.domElement = div.firstChild;
                    a_template.domElements = [a_template.domElement];
                    if (owner)
                      owner.appendChild(a_template.domElement);
                  }
                }

                var enableWrapper = a_options.wrapper !== undefined ? a_options.wrapper : true;

                if (enableWrapper && (!("fcfWrapper" in a_template.args) || a_template.args.fcfWrapper) && a_template.domElement) {
                  fcf.liven(a_template.domElement, function() {
                    a_template.wrapper = fcf.NDetails._wrappers[a_template.id];
                    if (a_options.onResult)
                      a_options.onResult(a_error, a_template);
                    a_act.complete(a_template);
                  });
                } else {
                  if (a_options.onResult)
                    a_options.onResult(a_error, a_template);
                  a_act.complete(a_template);
                }
              }
              if (!fcf.empty(a_template.state.include)){
                fcf.include(fcf.array(a_template.state.include, (k)=>{return k}), ()=>{
                  if (!fcf.empty(a_template.state.page.include)){
                    fcf.include(fcf.array(a_template.state.page.include, (k)=>{return k}), clCreate);
                  } else {
                    clCreate();
                  }
                });
              } else {
                clCreate();
              }
            } else {
              if (!a_clientRendering && a_options.state){
                fcf.append(a_options.state.args, a_template.state.args);
                fcf.append(a_options.state.include, a_template.state.include);
                fcf.append(a_options.state.originSourcesKeys, a_template.state.originSourcesKeys);
                fcf.append(a_options.state.page.header, a_template.state.page.header);
                fcf.append(a_options.state.page.include, a_template.state.page.include);
                fcf.append(a_options.state.sources, a_template.state.sources);
              }
              if (a_options.onResult)
                a_options.onResult(a_error, a_template);
              a_act.complete(a_template);
            }
          }

          let renderInscrtutions = fcf.NDetails.renderInstructions[a_options.template];
          if (!renderInscrtutions){
            let template = a_options.template[0] == "@" ? self.getTheme().getAliases()[a_options.template.substr(1)] : a_options.template;
            let templArr = template.split("+");
            let block    = templArr[1];
            let templateShort = templArr[0];
            let wrapper  = templateShort.substr(0, templateShort.length - ".tmpl".length) + (block ? "+" + block : "") + ".wrapper.js";
            try {
              await fcf.require([wrapper]);
              renderInscrtutions = fcf.NDetails.renderInstructions[template];
            } catch(e){
            }
          }
          if (renderInscrtutions && renderInscrtutions.renderFunction && !renderInscrtutions.hooks){
            let template = a_options.template[0] == "@" ? self.getTheme().getAliases()[a_options.template.substr(1)] : a_options.template;
            let templArr = template.split("+");
            let block    = templArr[1];
            let templateShort = templArr[0];
            let wrapper  = templateShort.substr(0, templateShort.length - ".tmpl".length) + (block ? "+" + block : "") + ".wrapper.js";
            try {
              await fcf.require([wrapper+"---cri.js"]);
            }catch(e){
              fcf.log.err(`Can't get hooks info for rendering. Template: ${a_options.template}`)
            }
          }

          let clientRendering = renderInscrtutions && renderInscrtutions.options && "clientRendering" in renderInscrtutions.options ? renderInscrtutions.options.clientRendering : undefined;
          if (clientRendering) {
            if (a_options.args && "fcfClientRendering" in a_options.args)
              clientRendering = a_options.args.fcfClientRendering;
            else if (renderInscrtutions.arguments && "fcfClientRendering" in renderInscrtutions.arguments)
              clientRendering = renderInscrtutions.arguments.fcfClientRendering;
          }
          let updateMode = a_options.args && a_options.args.fcfId && !!fcf.getWrapper(a_options.args.fcfId);

          if (clientRendering === true || clientRendering === "all" || (updateMode && !a_options.updateOriginState && (clientRendering === "update" || clientRendering === "update_np"))) {
            let template = a_options.template.split("+")[0][0] == "@" ? self.getTheme().getAliases()[a_options.template.substr(1)] : a_options.template;
            let args = fcf.append({}, a_options.args);
            if (parent)
              args.fcfParent = parent;
            args.fcfId = args.fcfId                                       ? args.fcfId :
                        !args.fcfId && renderInscrtutions.arguments.fcfId ? renderInscrtutions.arguments.fcfId :
                                                                            fcf.id();
            for(let key in args)
              if (fcf.isArg(args[key]) && args[key].type == "reference" && !args[key].object.id)
                args[key].object.id = args.fcfId;

            self._render({
              template:     a_options.template,
              templateOptions: renderInscrtutions && renderInscrtutions.options ? renderInscrtutions.options : {},
              args:         args,
              state:        a_options.state,
              url:          a_options.url || window.location.href,
              secondary:    a_options.secondary,
              onResult:     function(a_error, a_result) {
                clCompleteRender(a_error, a_result, true);
              }
            });
          } else {
            let args = {};
            if (parent)
              args.fcfParent = parent;
            fcf.append(args, a_options.args);


            let externalArgs = typeof a_options.externalArgs == "object" ? a_options.externalArgs : {};
            function fillExternalArgs(a_object) {
              if (fcf.isArg(a_object) && a_object.type == "reference"){
                let wrp = fcf.getWrapper(a_object.object.id);
                if (wrp){
                  let value = a_options.state && a_options.state.args[a_object.object.id] && a_object.arg in a_options.state.args[a_object.object.id]
                                ? a_options.state.args[a_object.object.id][a_object.arg]
                                : wrp.getArg(a_object.arg);
                  if (!(a_object.object.id in externalArgs))
                    externalArgs[a_object.object.id] = {};
                  let arg = a_object.arg;
                  if (arg.indexOf("{{") != -1){
                    let wrpArgs = wrp.getArgs();
                    let prtArgs = wrp.getParent() ? wrp.getParent().getArgs() : {};
                    arg = fcf.tokenize(arg, {args: wrpArgs, parent: prtArgs});
                  }
                  let ptr = fcf.resolveEx(externalArgs[a_object.object.id], arg, true);
                  ptr.object[ptr.key] = value;
                }
              } else if (typeof a_object === "object"){
                fcf.each(a_object, (a_key, a_item)=>{
                  fillExternalArgs(a_item);
                });
              }
            }
            for(let key in args)
              if (fcf.isArg(args[key]))
                fillExternalArgs(args[key]);

            fcf.loadObject({
              path:  "@url:fcfRender",
              post: { template: a_options.template, args: args, url: a_options.url || window.location.href, externalArgs: externalArgs },
              onResult: function(a_error, a_response) {
                clCompleteRender(a_error, a_response, false);
              }
            });
          }
        })
        .then((a_template)=>{
          if (a_options.secondary || a_options.update || !a_template.domElement)
            return a_template;
          return fcf.application.getEventChannel().send("fcf_render", {element: a_template.domElement})
          .then(()=>{
            return a_template;
          })
        })
        .catch((a_error)=>{
          fcf.application.getEventChannel().send("fcf_error", {error: a_error});
        })
        .finally(()=>{
          fcf.setContext(fcfcontext);
          fcf.setState(fcfstate);
        })
      }

      _render(a_options){
        let self = this;
        let isRootCall       = !a_options.state;
        let state;
        let renderInscrtutions = fcf.NDetails.renderInstructions[a_options.template]
        let fcfId;
        let resultArgs;
        let resultSources;
        let resultContent;
        let taskInfo;

        return fcf.actions()
        .then(async ()=>{
          if (!renderInscrtutions){
            let template = a_options.template[0] == "@" ? fcf.application.getTheme().getAliases()[a_options.template.substr(1)] : a_options.template;
            let templArr = template.split("+");
            let block    = templArr[1];
            let templateShort = templArr[0];
            let wrapper  = templateShort.substr(0, templateShort.length - ".tmpl".length) + (block ? "+" + block : "") + ".wrapper.js";
            await fcf.require([wrapper]);
            renderInscrtutions = fcf.NDetails.renderInstructions[template];
          }

          let args = a_options.args ? a_options.args : {};
          fcfId = args.fcfId                                       ? args.fcfId :
                 !args.fcfId && renderInscrtutions.arguments.fcfId ? renderInscrtutions.arguments.fcfId :
                                                                     fcf.id();

          state  = a_options.state
                  ? a_options.state
                  : {
                      id:                 fcfId,
                      root:               {id: fcf.application.getRootWrapper().getId()},
                      theme:              fcf.application.getTheme(),
                      defaultTheme:       undefined,
                      merge:              false,
                      include:            {},
                      reqursionCounter:   0,
                      reqursionRender:    0,
                      args:               {},
                      sources:            {},
                      originSourcesKeys:  {},
                      cpMap:              {},
                      page:               {
                                            header: {
                                              include:      [],
                                              description:  "",
                                              title:        "",
                                              keywords:     [],
                                              header:       []
                                            }
                                          }
                   };
          ++state.reqursionRender;
          if (state.reqursionRender > fcf.application.getConfiguration().maxReqursionRender)
            throw new fcf.Exception("ERROR_MAX_REQURSION_RENDER", {template: a_options.template});

          if (renderInscrtutions.options && renderInscrtutions.options.include){
            let include = Array.isArray(renderInscrtutions.options.include) ? renderInscrtutions.options.include : [renderInscrtutions.options.include];
            for(let i = 0; i < include.length; ++i)
              state.include[include[i]] = include[i];
          }
          if (renderInscrtutions.options && renderInscrtutions.options.clientInclude){
            let include = Array.isArray(renderInscrtutions.options.clientInclude) ? renderInscrtutions.options.clientInclude : [renderInscrtutions.options.clientInclude];
            for(let i = 0; i < include.length; ++i)
              state.include[include[i]] = include[i];
          }
          if (renderInscrtutions.options.merge || renderInscrtutions.options.static){
            state.merge = true;
          }
        })
        .then((a_res, a_act)=>{
          self._argBuilder.build({
            id:           fcfId,
            state:        state,
            reqursion:    !!a_options.reqursion,
            templatePath: a_options.template,
            route:        a_options.route ? a_options.route : fcf.getContext().route,
            args:         renderInscrtutions.arguments,
            template:     renderInscrtutions,
            inputArgs:    a_options.args,
            onResult:     function (a_error, a_args, a_sources, a_taskInfo) {
              if (a_error) {
                a_act.error(a_error);
                return;
              }
              resultArgs = a_args;
              resultSources = a_sources;
              taskInfo = a_taskInfo;
              a_act.complete();
            }
          });
        })
        .then(async ()=>{
          let templateRender = new TemplateRender({template: a_options.template, state: state, args: resultArgs});
          let lct = fcf.getContext().currentTemplate;
          fcf.getContext().currentTemplate = {id: fcfId, template: a_options.template};
          let libFCF = {};
          libFCF.__proto__ = fcf;
          libFCF.argRef = function(){
            if (arguments.length == 1)
              return fcf.argRef(fcfId, arguments[0]);
            else
              return fcf.argRef(arguments[0], arguments[1]);
          }
          await renderInscrtutions.renderFunction({
            decor:    fcf.application.getTheme().getDecor(),
            route:    a_options.route ? a_options.route : fcf.getContext().route,
            args:     resultArgs,
            render:   templateRender,
            fcf:      libFCF,
            taskInfo: taskInfo,
          });
          fcf.getContext().currentTemplate = lct;
          resultContent = await templateRender.build()
        })
        .then(()=>{
          let content;
          if ("fcfWrapper" in resultArgs && !resultArgs.fcfWrapper){
            content = resultContent;
          } else {
            content = "<div ";
            content += "fcftemplate=\"" + state.theme.resolveAlias(a_options.template) + "\" ";
            content += " id='" + resultArgs.fcfId + "' ";
            if (a_options.templateOptions.static)
              content += " fcfstatic ";
            content += " class=' fcfwrapper " + fcf.str(resultArgs.fcfClassInner) + " " + fcf.str(resultArgs.fcfClass) + "' "
            content += resultArgs.fcfEvntid ? " fcfevntid='" + resultArgs.fcfEvntid + "'" : "";

            if (resultArgs.fcfStyle || resultArgs.fcfStyleInner)
              content += " style='" + fcf.str(resultArgs.fcfStyleInner) + ";" + fcf.str(resultArgs.fcfStyle) + "' ";
            if (typeof resultArgs.fcfAttributes == "object"){
              for(var k in resultArgs.fcfAttributes)
                content += " " + k + "=\"" + resultArgs.fcfAttributes[k] + "\" ";
            }
            if (resultArgs.fcfParent)
              content += "fcfparent=\"" + resultArgs.fcfParent + "\" ";
            if (resultArgs.fcfAlias)
              content += "fcfalias=\"" + resultArgs.fcfAlias + "\" ";
            content +=">" + resultContent + "</div>";
          }
          return content;
        })
        .then(async (a_content)=>{
          if (renderInscrtutions.hooks.hookAfterRender)
            await renderInscrtutions.hooks.hookAfterRender(taskInfo);

          --state.reqursionRender;

          var resultTemplate = new Template({
                                    content: a_content,
                                    id:      resultArgs.fcfId,
                                    args:    resultArgs,
                                    state: {
                                      args:              state.args,
                                      sources:           state.sources,
                                      originSourcesKeys: state.originSourcesKeys,
                                      include:           state.include,
                                      page:              state.page,
                                      merge:             state.merge,
                                    }
                                  });

          if (!resultTemplate.args.fcfError){
            a_options.onResult(undefined, resultTemplate);
          } else {
            a_options.onResult(resultTemplate.args.fcfError, resultTemplate);
            throw resultTemplate.args.fcfError;
          }

          return resultTemplate;
        })
        .catch(function(a_error){
          fcf.log.err("FCF:RENDER", a_error);
          a_options.onResult(a_error, new Template({}));
        });


      }
    };

    Namespace.Render = Render;

    return Namespace.Render;
  }
});
