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

    var debugIncludeFiles = [
      "fcf:NRender/NDetails/Helper.js",
      "fcf:NRender/TaskInfo.js",
      "fcf:NFSQL/NFilter/Filter.js",
      "fcf:NServer/PackageHandler.js",
      "fcf:NServer/Package.js",
      "fcf:NTheme/Theme.js",
      "fcf:NTheme/Themes.js",
      "fcf:NRender/NDetails/Loader.js",
      "fcf:NRender/NDetails/TemplateRender.js",
      "fcf:NRender/NDetails/TemplateProcessor.js",
      "fcf:NRender/Template.js",
      
      "fcf:NRender/NDetails/ArgsBuilder.js",
      "fcf:NRender/Render.js",

      "fcf:NFSQL/NDetails/Errors.js",
      "fcf:NFSQL/NDetails/SingleBuilder.js",
      "fcf:NFSQL/Builder.js",

      "fcf:NEvent/Error.js",
      "fcf:NServer/Configuration.js",
      "fcf:NClient/LocalData.js",
      "fcf:NClient/Application.js",

      "fcf:NClient/Wrapper.js"
    ]

    var innerStorage = {};

    /*
    * @class fcf::NRender::NDetails::ArgOrderDetector
    * @brief Класс анализатора порядка элементов
    **/
    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._callMap     = {};

      this.write = function(a_content) {
        this.buffer += fcf.str(a_content);
      }

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

        var fcfCP = this._getCallPosition();
        var templateMarker = this.markerPrefix + fcf.uuid() + this.markerSuffix;
        if (!a_options.args)
          a_options.args = {};
        a_options.args = fcf.append({}, a_options.args, { fcfCP: fcfCP, fcfParent: a_initializeOptions.args.fcfWrapper ? a_initializeOptions.args.fcfId : undefined});
        this.templates[templateMarker] = fcf.append(true, {}, {type: "template", options: a_options});
        return templateMarker;
      }

      this.view = function(a_options) {
        var fcfCP = this._getCallPosition();
        var templateMarker = this.markerPrefix + fcf.uuid() + this.markerSuffix;
        if (!a_options.args)
          a_options.args = {};
        a_options.args = fcf.append({}, a_options.args, { fcfCP: fcfCP, fcfParent: a_initializeOptions.args.fcfWrapper ? a_initializeOptions.args.fcfId : undefined});

        if (Array.isArray(a_options.view.requireRecord) && fcf.isArg(a_options.record) && a_options.record.type == "reference"){
          let recordPattern = "";
          fcf.each(a_options.view.requireRecord, (a_key, a_field)=>{
            let argRef = fcf.arg("reference", {id: a_options.record.object.id, arg: a_options.record.arg + `["${a_field}"]` });
            a_options.args[`@record@${a_field}`] = argRef;
            recordPattern += `"${a_field}": args["@record@${a_field}"],`
          })
          a_options.args.record = fcf.argVal("@{{ {" + recordPattern + "} }}@");
        } else if (a_options.view.requireRecord){
          a_options.args.record = a_options.record;
        }

        this.templates[templateMarker] = fcf.append(true, {}, {type: "view", options: a_options});
        return templateMarker;
      }

      this.header = function() {
        this.templates[this.markerHeader] = {type: "header", options: {}};
        return this.markerHeader;
      }
      this._getCallPosition = function(){
        var stackArr = (new 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 = "block:"+callPostition;
        if (!(cp in this._callMap))
          this._callMap[cp] = 0;
        cp += "-" + (++this._callMap[cp]);
        return cp;
      }

      this._header = function() {
        var theme = fcf.application.getThemes().getTheme(a_initializeOptions.args.fcfTheme);
        if (!theme)
          theme = fcf.application.getThemes().getTheme();

        var result = "";
        var themeInfo = theme.getInfo();
        result += '    <style> .fcfwrapper { display: inline; }</style>\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 < debugIncludeFiles.length; ++i){
            let filePath = fcf.getPath(debugIncludeFiles[i], false);
            result += '    <script src="' + filePath + '"></script>\n';
          }
        }
        result += "    <script>\n";
          var defaultTheme = a_initializeOptions.state.defaultTheme ? a_initializeOptions.state.defaultTheme.getName() : undefined;
          if (!defaultTheme)
            defaultTheme = fcf.application.getThemes().getDefaultThemeName();

          let context      = fcf.getContext();
          let sendContext  = {};
          for(var k in context) {
            if (k != "safeEnv" && k != "currentTemplate")
              sendContext[k] = context[k];
          }
          var options = {
            context:             sendContext,
            clientRenderingMode: "server",
            defaultTheme:        defaultTheme,
            fileСaching:         fcf.application.getConfiguration().fileСaching,
            renderStorage:       a_initializeOptions.state.renderStorage,
            originSourcesKeys:   a_initializeOptions.state.originSourcesKeys,
            resets:              a_initializeOptions.state.resets,
          };

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

          result += "      fcf.application.setSettings('" + fcf.base64Encode(JSON.stringify(options)) + "');";

          var inlineRenderRestore = "{renderRestore:{";
          fcf.each(a_initializeOptions.state.sources, function(a_id, a_control){
            inlineRenderRestore += "\"" + fcf.escapeQuotes(a_id) + "\":{";
              fcf.each(a_control, function(a_key, a_value){
                inlineRenderRestore += "\"" + fcf.escapeQuotes(a_key) + "\": ";
                if (fcf.isArg(a_value)){
                  inlineRenderRestore += "fcf.arg(\"" + a_value.type + "\","
                  var value = fcf.arg({}, a_value);
                  delete value.type;
                  inlineRenderRestore += JSON.stringify(value);
                  inlineRenderRestore += ")"
                } else {
                  inlineRenderRestore += JSON.stringify(a_value);
                }
                inlineRenderRestore += ",";

              });
            inlineRenderRestore += "},";
          });
          inlineRenderRestore += "}}";
          result += "      fcf.application.setSettings('" + fcf.base64Encode(inlineRenderRestore) + "');\n";
        result += "    </script>\n";

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



        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.title))
          result += "    <title>" + a_initializeOptions.state.page.header.title + "</title>\n";
        else if (!fcf.empty(fcf.getContext().route.title))
          result += "    <title>" + fcf.getContext().route.title + "</title>\n";
        if (!fcf.empty(a_initializeOptions.state.page.header.keywords))
          result += "    <meta name=\"Keywords\" content=\"" + fcf.encodeHtml(a_initializeOptions.state.page.header.keywords.join(" ")) + "\">\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";
          })
        }

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

        return result;
      }
    }

    return NDetails.TemplateRender;
  }
});
