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

    class HeaderMarker {
    };

    /*
    * @class fcf::NRender::NDetails::ArgOrderDetector
    * @brief Класс анализатора порядка элементов
    * @param object a_initializeOptions
    *         - object loader
    *         - object args
    *         - object state
    *         - string template
    *         - object rawTemplate
    **/
    NDetails.TemplateRender = function(a_initializeOptions) {
      var self = this;
      this._buffer = [];
      this.templates = {};
      this.markerPrefix = "<marker c9193820-251d-11ea-bc52-d7f06800af03 ";
      this.markerSuffix = "></marker>";
      this.markerHeader = this.markerPrefix + "header" + this.markerSuffix;;
      this.page = a_initializeOptions.state.page;

      this.getTheme = function(){
        return fcf.application.getTheme();
      }

      this.build = async function(){
        let result = "";
        let fcfstate = fcf.getState();
        var currentTemplateInfo = fcf.getContext().currentTemplate;
        let headerIndex = undefined;

        for(let i = 0; i < this._buffer.length; ++i){
          let itm = this._buffer[i];
          if (itm instanceof Promise || itm instanceof fcf.Actions) {
            if (itm instanceof fcf.Actions && itm._deferred)
              itm.startup();
            this._buffer[i] = await itm;
            fcf.setState(fcfstate);
            fcf.getContext().currentTemplate = currentTemplateInfo;
          } else if (itm instanceof HeaderMarker) {
            headerIndex = i;
          }
        }

        if (headerIndex !== undefined){
          this._buffer[headerIndex] = await this._header();
        }

        for(let i = 0; i < this._buffer.length; ++i){
          result += fcf.str(this._buffer[i]);
        }

        return result;
      }

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

      this.writeRuntime = function(a_content, a_rid, a_variables, a_rtype) {
        let fcfRuntime = a_initializeOptions.args.fcfRuntime;
        if (!fcfRuntime) {
          fcfRuntime = {};
          a_initializeOptions.args.fcfRuntime = fcfRuntime;
          a_initializeOptions.state.args[a_initializeOptions.args.fcfId].fcfRuntime = a_initializeOptions.args.fcfRuntime;
          a_initializeOptions.state.sources[a_initializeOptions.args.fcfId].fcfRuntime = a_initializeOptions.args.fcfRuntime;
        }
        let rrid = a_rtype == 3 ? this._getLastRRID() : this._getRRID();
        if (a_rtype != 0) {
          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();
        }
      }

      this.template = function(a_options, a_args, a_callPosition, a_deferred){
        let currentTemplate = fcf.getContext().currentTemplate;
        let template    = undefined;
        let args        = undefined;
        let inheritArgs = {};

        if (typeof a_options === "string"){
          template = a_options;
          if (typeof a_args === "number"){
            args = {};
            a_callPosition = a_args;
          } else {
            args = a_args;
          }
        } else {
          template = a_options.template;
          args = a_options.args;
          if (typeof a_args === "number")
            a_callPosition = a_args;
        }
        if (typeof args !== "object")
          args = {};

        let themeName = a_initializeOptions.state.theme ? a_initializeOptions.state.theme : undefined;
        template = helper.normalizeTemplatePath(template, themeName);

        let fcfCP = this._getCallPosition(template, a_args, a_callPosition);
        let parentId = fcf.NRender.getParentId(a_initializeOptions.args.fcfId, a_initializeOptions.state);
        args = fcf.append({}, args, { fcfCP: fcfCP, fcfParent: parentId});
        fcf.append(args, inheritArgs);
        if (a_initializeOptions.args.fcfInitialStorageOfChildren)
          args.fcfInitialStorageOfChildren = true;

        if (template.charAt(0) == "+")
          template = a_initializeOptions.template.split("+")[0] + template;

        return fcf.actions({deferred: a_deferred})
        .then(()=>{
          if (fcf.getPath(a_initializeOptions.template) == fcf.getPath(template))
            throw new fcf.Exception("ERROR_REDNER_LOOP", {template: template});
          return fcf.application.getRender().getLoader().loadInfo(template)
        })
        .then((a_renderedTemplate)=>{
          let block = template.split("+")[1];
          if (!block)
            block = "";
          if (!a_renderedTemplate.templates[block])
            throw new fcf.Exception("ERROR_TEMPLATE_NOT_FOUND", {template: template});
          let existsWrapper = !("wrapper" in a_renderedTemplate.templates[block].options) || !!a_renderedTemplate.templates[block].options.wrapper;
          helper.appendChildInfo(args, args, a_initializeOptions.args.fcfChildsArgs, existsWrapper);

          if (!a_initializeOptions.rawTemplate.hooks.hookSubtemplateRender)
            return;
          fcf.getContext().currentTemplate = currentTemplate;
          return a_initializeOptions.rawTemplate.hooks.hookSubtemplateRender({
            args:            a_initializeOptions.args,
            renderedArgs:    args,
            renderedOptions: a_renderedTemplate.options,
            renderedPath:    template,
          });
        })
        .then(()=>{
          return fcf.application.render({
            template:   template,
            route:      a_initializeOptions.route,
            state:      a_initializeOptions.state,
            args:       args,
            reqursion:  true
          });
        })
        .then((a_template)=>{
          return a_template.content;
        })
      }

      this.view = function(a_options, a_callPosition, a_deferred) {
        let currentTemplate = fcf.getContext().currentTemplate;
        let fcfCP;
        let parentId = fcf.NRender.getParentId(a_initializeOptions.args.fcfId, a_initializeOptions.state);
        let args = fcf.append({}, a_options.args, {fcfParent: parentId});
        let inheritArgs = {};

        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, a_initializeOptions.state.theme);

        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);
        fcf.append(args, inheritArgs);

        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 (a_initializeOptions.args.fcfInitialStorageOfChildren)
          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(a_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 template  = view.template;
        let themeName = a_initializeOptions.state.theme ? a_initializeOptions.state.theme : undefined;
        template = helper.normalizeTemplatePath(template, themeName);
        fcfCP = this._getCallPosition(template, args, a_callPosition);
        args.fcfCP = fcfCP;

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

        return fcf.actions({deferred: a_deferred})
        .then(()=>{
          if (fcf.getPath(a_initializeOptions.template) == fcf.getPath(template))
            throw new fcf.Exception("ERROR_REDNER_LOOP", {template: template});
          return fcf.application.getRender().getLoader().loadInfo(template)
        })
        .then((a_renderedTemplate)=>{
          fcf.getContext().currentTemplate = currentTemplate;

          if (!a_renderedTemplate)
            return a_renderedTemplate;
          let block = template.split("+")[1];
          if (!block)
            block = "";

          if (!a_renderedTemplate.templates[block])
            throw new fcf.Exception("ERROR_TEMPLATE_NOT_FOUND", {template: template});

          let existsWrapper = !("wrapper" in a_renderedTemplate.templates[block].options) || !!a_renderedTemplate.templates[block].options.wrapper;
          helper.appendChildInfo(args, args, a_initializeOptions.args.fcfChildsArgs, existsWrapper);

          if (!a_renderedTemplate.templates[block] || !a_renderedTemplate.templates[block].hooks.hookBeforeView)
            return a_renderedTemplate;
          let hookOptions = {
            record: a_options.record,
            view:   a_options.view,
            mode:   mode,
            args:   args,
          }
          if (fcf.isArg(hookOptions.record)){
            if (hookOptions.record.type === "reference") {
              hookOptions.record = fcf.resolve(a_initializeOptions.state.args[hookOptions.record.object.id], hookOptions.record.arg);
            } else if (hookOptions.record.type === "value") {
              hookOptions.record = fcf.tokenizeObject(hookOptions.record.value, {}, true);
            }
          }
          return a_renderedTemplate.templates[block].hooks.hookBeforeView(hookOptions)
          .then(()=>{
            return a_renderedTemplate;
          })
        })
        .then((a_renderedTemplate)=>{
          fcf.getContext().currentTemplate = currentTemplate;

          if (!a_initializeOptions.rawTemplate.hooks.hookSubtemplateRender)
            return;

          return a_initializeOptions.rawTemplate.hooks.hookSubtemplateRender({
            args:            a_initializeOptions.args,
            renderedArgs:    args,
            renderedOptions: a_renderedTemplate.options,
            renderedPath:    template,
          });
        })
        .then(()=>{
          return fcf.application.render({
            template:   template,
            route:      a_initializeOptions.route,
            state:      a_initializeOptions.state,
            args:       args,
            reqursion: true,
          });
        })
        .then((a_template)=>{
          return a_template.content;
        })
      }

      this.header = function() {
        return new HeaderMarker();
      }

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

      this._getLastRRID = function(){
        return this._lrrid;
      }

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

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

      this._header = async function() {
        var theme = fcf.application.getTheme(a_initializeOptions.args.fcfTheme, true);
        var result = "";
        var themeInfo = theme.getInfo();

        if (!fcf.empty(a_initializeOptions.state.page.header.title))
          result += "    <title>" + a_initializeOptions.state.page.header.title + "</title>\n";

        if (!fcf.empty(a_initializeOptions.state.page.header.keywords))
          result += `    <meta name="Keywords" content=${JSON.stringify(a_initializeOptions.state.page.header.keywords.join(" "))}>\n`;

        if (!fcf.empty(a_initializeOptions.state.page.header.description))
          result += `    <meta name="description" content=${JSON.stringify(a_initializeOptions.state.page.header.description)}/>\n`;

        result += '    <style> .fcfwrapper { display: inline; }</style>\n';

        if (fcf.getContext().needBabel){
          result += '    <script src="' + fcf.getPath("fcf:NClient/regeneratorRuntime.js", false) + '"></script>\n';
          result += '    <script src="' + fcf.getPath("fcf:NClient/es6-promise.auto.js", false) + '"></script>\n';
        }

        result += '    <script src="' + fcf.getPath("fcf:fcf.js", false) + '"></script>\n';
        if (!fcf.getContext().get("debug")) {
          result += '    <script src="/fcfpackages/fcf/include.js"></script>\n';
        } else {
          for(var i = 0; i < fcf.NClient.mainClientInclude.length; ++i){
            let filePath = fcf.getPath(fcf.NClient.mainClientInclude[i], false);
            result += '    <script src="' + filePath + '"></script>\n';
          }
        }
        var defaultTheme = a_initializeOptions.state.defaultTheme ? a_initializeOptions.state.defaultTheme.getName() : undefined;
        if (!defaultTheme)
          defaultTheme = fcf.application.getConfiguration().defaultTheme;

        let context      = fcf.getContext();
        let sendContext  = {};
        for(var k in context) {
          if (k != "safeEnv" && k != "currentTemplate")
            sendContext[k] = context[k];
        }

        let sargs = a_initializeOptions.state.args;
        let ssources = a_initializeOptions.state.sources;
        let soriginSourcesKeys = a_initializeOptions.state.originSourcesKeys;
        let args = {};
        let sources = {};
        let originSourcesKeys = {};
        for(let id in sargs) {
          if ("fcfWrapper" in sargs[id] && !sargs[id].fcfWrapper){
            if (sargs[id].fcfBR) {
              let objArgs    = {fcfId: sargs[id].fcfId, fcfParent: sargs[id].fcfParent, fcfBR: sargs[id].fcfBR, fcfWrapper: false};
              let objSources = {fcfId: sargs[id].fcfId, fcfParent: sargs[id].fcfParent, fcfBR: sargs[id].fcfBR, fcfWrapper: false};
              for(let arg in objArgs.fcfBR) {
                objArgs[arg]    = a_initializeOptions.state.args[id][arg];
                objSources[arg] = a_initializeOptions.state.sources[id][arg];
              }
              let objOSK = [];
              for(let arg in objArgs) {
                if (fcf.find(a_initializeOptions.state.originSourcesKeys[id], arg) !== undefined)
                  objOSK.push(arg);
              }
              originSourcesKeys[id] = objOSK;
              sources[id] = objSources;
              args[id] = objArgs;
              continue;
            } else {
              continue;
            }
          }
          originSourcesKeys[id] = soriginSourcesKeys[id];
          sources[id] = ssources[id];
          args[id] = {};
          for(let key in sargs[id]){
            if (key == "fcfChildsArgs")
              continue;
            if (fcf.isArg(ssources[id][key]) && (ssources[id][key].type === "reference" || ssources[id][key].type === "template" || ssources[id][key].ignore))
              continue;
            args[id][key] = sargs[id][key];
          }
        }

        var options = {
          context:             sendContext,
          maxReqursionRender:  fcf.application.getConfiguration().maxReqursionRender,
          clientRenderingMode: "server",
          defaultTheme:        defaultTheme,
          fileСaching:         fcf.application.getConfiguration().fileСaching,
          renderStorage:       args,
          renderRestore:       sources,
          originSourcesKeys:   originSourcesKeys,
        };

        if (a_initializeOptions.state.root) {
          options.root =  {
                            id: a_initializeOptions.state.root.id,
                            template: fcf.getPath(a_initializeOptions.state.root.template, false)
                          };
        }

        result += "    <script>";
        result += `fcf.application.setSettings("${encodeURIComponent(JSON.stringify(options))}");`;
        result += "</script>\n";

        result += '    <script src="/fcfpackages/fcf/settings.js"></script>\n';

        result += "    <script>fcf.application.initialize()</script>\n";

        let avalibleMirrors = fcf.application.getConfiguration().avalibleMirrors;


        await fcf.application.getEventChannel().send("fcf_page_after", {route: a_initializeOptions.route, page: a_initializeOptions.state.page});

        var includeFilesMap = {};
        var includeFiles    = [];
        fcf.each(fcf.application.getConfiguration().html.include, function(a_key, a_path){
          var path = fcf.getPath(a_path, false)
          if (!includeFilesMap[path]){
            includeFilesMap[path] = true;
            includeFiles.push(path);
          }
        });
        fcf.each(themeInfo.html.include, function(a_key, a_path){
          var path = fcf.getPath(a_path, false)
          if (!includeFilesMap[path]){
            includeFilesMap[path] = true;
            includeFiles.push(path);
          }
        });
        for(var path in a_initializeOptions.state.include){
          path = fcf.getPath(path, false);
          if (!includeFilesMap[path]){
            includeFilesMap[path] = true;
            includeFiles.push(path);
          }
        }
        for(let i = 0; i < a_initializeOptions.state.page.header.include.length; ++i){
          let path = a_initializeOptions.state.page.header.include[i];
          path = fcf.getPath(path, false);
          if (!includeFilesMap[path]){
            includeFilesMap[path] = true;
            includeFiles.push(path);
          }
        }

        fcf.each(includeFiles, function(a_key, a_path){
          var ext = fcf.getExtension(a_path);
          if (ext == "css") {
            result += '    <link rel="stylesheet" type="text/css" href="' + a_path + '">\n';
          } else if (ext == "js") {
            result += '    <script src="' + a_path + '"></script>\n';
          }
        })

        if (!fcf.empty(a_initializeOptions.state.page.header.header)){
          fcf.each(a_initializeOptions.state.page.header.header, (k, v)=>{
            result += "    ";
            result += v;
            result += "\n";
          })
        }

        if (avalibleMirrors !== true && avalibleMirrors !== undefined){
          let strValue = Array.isArray(avalibleMirrors)
                                ? JSON.stringify(avalibleMirrors)
                                : "undefined";
          result += `    <script>`
          result += `       let currentServer = fcf.parseUrl(window.location.href).server;`
          result += `       let avalibleServers = fcf.append([], ${strValue}, [fcf.parseUrl("${fcf.application.getConfiguration().site}").server]);`
          result += `       var check = ${JSON.stringify(fcf.uuid())};`;
          result += `       let found = false;`
          result += `       fcf.each(avalibleServers, function (a_key, a_server){`
          result += `         let pos = currentServer.lastIndexOf(a_server);`;
          result += `         if (pos == -1)`;
          result += `           return;`;
          result += `         if (pos != currentServer.length - a_server.length)`;
          result += `           return;`;
          result += `         if (pos != 0 && currentServer[pos-1] != ".")`;
          result += `           return;`;
          result += `         found = true;`;
          result += `         var check = ${JSON.stringify(fcf.uuid())};`;
          result += `         return false;`;
          result += `       });`;
          result += `    if (!found) { `;
          result += `      var check = ${JSON.stringify(fcf.uuid())};`;
          result += `      if(window.stop)`;
          result += `        window.stop();`;
          result += `      else if (document.execCommand)`;
          result += `        document.execCommand("Stop", false);`;
          result += `    }`;
          result += `    </script>\n`;
        }

        result += fcf.str(fcf.application.getSystemVariable("fcf:htmlHeader"));
        result += "\n";


        result += '<script>';
        result += 'window.addEventListener("load", function(){';
        result += 'fcf.liven().finally(function(){ fcf.application._isRun = true;});';
        result += '});'
        result += '</script>\n';

        return result;
      }
    }

    return NDetails.TemplateRender;
  }
});
