if (fcf.isServer()){
  var libFS = require('fs');
  var libUtil = require('util');
}

fcf.module({
  name: "fcf:NRender/NDetails/Loader.js",
  dependencies: ["fcf:NRender/NDetails/Helper.js"],
  module: function(helper) {
    var NDetails = fcf.prepareObject(fcf, "NRender.NDetails");
    fcf.addException("ERROR_RENDER_SERVER_ONLY_TEMPLATE",   "The '${{template}}$' template can only be rendered on the server");
    fcf.addException("ERROR_RENDER_TEMPLATE_NOT_FOUND",     "Element '${{template}}$' template not found");

    var innerTemplateStorage = {};
    var innerDependencies = {};


    NDetails.Loader = class Loader {

      constructor(a_options) {
        this._options = fcf.append({}, a_options);
      }

      setSettings(a_options) {
        fcf.append(this._options, a_options);
      }

      load(a_path, a_state, a_id, a_cb) {
        let currentTemplateInfo = fcf.getContext().currentTemplate;
        let date = new Date();
        if (typeof a_state === "function" || arguments.length == 1){
          a_cb = a_state;
          a_state = {
            theme: fcf.application.getTheme(),
            include: {},
          }
        }
        a_path = a_path.split("+")[0];
        a_path = a_state.theme.resolveAlias(a_path);

        return fcf.actions()
        .then(async ()=>{
          let template = await this._loadTemplateData(a_path, a_state, a_id, a_path);

          fcf.getContext().currentTemplate = currentTemplateInfo;
          let lsttmpl = fcf.NDetails.currentTemplate;
          fcf.NDetails.currentTemplate = a_path;

          template = this._resolveTemplate(template, a_path);

          fcf.NDetails.currentTemplate = lsttmpl;
          fcf.getContext().currentTemplate = currentTemplateInfo;

          if (!fcf.empty(template.options.access) && !fcf.access(template.options.access))
            throw new fcf.Exception("ERROR_ACCESS", {resource: a_path, responseCode: 403});

          if (a_cb)
            a_cb(undefined, template);
          return template;
        })
        .catch((a_error)=>{
          if (a_cb)
            a_cb(a_error);
        });
      }

      loadInfo(a_path, a_state, a_id, a_cb) {
        let currentTemplateInfo = fcf.getContext().currentTemplate;
        let date = new Date();
        if (typeof a_state === "function" || arguments.length == 1){
          a_cb = a_state;
          a_state = {
            theme: fcf.application.getTheme(),
            include: {},
          }
        }
        a_path = a_path.split("+")[0];
        a_path = a_state.theme.resolveAlias(a_path);

        return fcf.actions()
        .then(async ()=>{
          let template = await this._loadTemplateData(a_path, a_state, a_id, a_path);

          fcf.getContext().currentTemplate = currentTemplateInfo;

          if (!fcf.empty(template.options.access) && !fcf.access(template.options.access))
            throw new fcf.Exception("ERROR_ACCESS", {resource: a_path, responseCode: 403});

          if (a_cb)
            a_cb(undefined, template);
          return template;
        })
        .catch((a_error)=>{
          if (a_cb)
            a_cb(a_error);
        });
      }


      _resolveTemplate(a_template, a_path){
        let result = fcf.append({}, a_template);
        result.templates = fcf.append({}, result.templates);
        for(let block in result.templates) {
          result.templates[block] = fcf.append({}, result.templates[block]);

          let args = {};
          for(let i = 0; i < result.templates[block].rawArguments.length; ++i){
            let currentArgs = fcf.scriptExecutor.parse(result.templates[block].rawArguments[i].data, {}, result.templates[block].rawArguments[i].path, result.templates[block].rawArguments[i].startLine);
            fcf.append(args, currentArgs);
          }

          result.templates[block].arguments = args;
          result.templates[block].hardDependencies = {};
          for(let key in result.templates[block].arguments){
            if (!fcf.isArg(result.templates[block].arguments[key]))
              continue;
            if (!result.templates[block].arguments[key].hardDependencies)
              continue;
            if (!(key in result.templates[block].hardDependencies))
              result.templates[block].hardDependencies[key] = [];
            fcf.append(result.templates[block].hardDependencies[key], result.templates[block].arguments[key].hardDependencies);
          }

          if ("wrapper" in result.templates[block].options && !result.templates[block].options.wrapper)
            result.templates[block].arguments.fcfWrapper = false;
        }

        return result;
      }

      async _loadTemplateData(a_path, a_state, a_id, a_mainTemplate){
        let self = this;
        let originPath = a_path;
        a_path = fcf.getPath(a_path);

        if (innerTemplateStorage[a_path])
          return innerTemplateStorage[a_path];

        let data = await fcf.load({path: a_path, aliases: a_state.theme.getAliases()})
        let stat = await libUtil.promisify(libFS.stat)(a_path);
        let lastModifyTime = stat.mtime;

        //Loading template data
        let lstct = fcf.NDetails.currentTemplate;
        fcf.NDetails.currentTemplate = a_mainTemplate;
        let template = this._parseTemplate(data, a_path, originPath, a_state, a_id, fcf.getContext());
        template.lastModifyTime = lastModifyTime;
        fcf.NDetails.currentTemplate = lstct;

        //Filling in the need for a wrapper file
        await fcf.each(template.templates, async (a_key, a_subTemplate)=>{
          let dotPos = a_path.lastIndexOf(".");
          if (dotPos == -1)
            return;
          let path = a_path.substring(0, dotPos);
          if (a_key)
            path += "+" + a_key;
          path += ".wrapper.js";
          if (a_subTemplate.options && a_subTemplate.options.clientRendering)
            a_subTemplate.wrapperExists = true;
          else
            a_subTemplate.wrapperExists = await libUtil.promisify(libFS.exists)(fcf.getPath(path));
        });


        //Loading template hooks
        let error = undefined;
        fcf.each(template.templates, (a_key, a_subTemplate)=>{
          let hookFileArr = a_path.split(".");
          hookFileArr.pop();
          if (a_key != "")
            hookFileArr[hookFileArr.length-1] += "+" + a_key;
          hookFileArr.push("hooks");
          hookFileArr.push("js");
          let hookFilePath = hookFileArr.join(".");
          fcf.requireEx([hookFilePath], {showError: false}, (error, hooks)=>{
            if (error && error.toString().indexOf("SyntaxError") !== -1){
              error = error;
              return false;
            }
            try {
              let stat = libFS.statSync(fcf.getPath(hookFilePath));
              template.lastModifyTime = fcf.max(template.lastModifyTime, stat.mtime);
            } catch(e){
            }

            if (hooks){
              fcf.append(a_subTemplate.hooksFiles, [hookFilePath]);
              fcf.append(a_subTemplate.hooksDependenciesArgs, fcf.NDetails.modules[hookFilePath].dependenciesArgs)
              fcf.append(a_subTemplate.hooksDependencies,     fcf.NDetails.modules[hookFilePath].dependencies)
              if (typeof hooks.hooksProgramableArgument !== "object")
                hooks.hooksProgramableArgument = {};
              if (typeof hooks.hooksBeforeArgument !== "object")
                hooks.hooksBeforeArgument = {};
              if (typeof hooks.hooksAfterArgument !== "object")
                hooks.hooksAfterArgument = {};
              fcf.append(a_subTemplate.hooks, hooks);
            }


          })
        });

        if (error)
          throw error;

        //Apply the inherited functional
        if (template.options.extends){
          let extendsPath = fcf.getPath(template.options.extends);
          if (!innerDependencies[extendsPath])
            innerDependencies[extendsPath] = {};
          innerDependencies[extendsPath][a_path] = a_path;

          let etemplate = await this._loadTemplateData(template.options.extends, a_state, a_id, a_mainTemplate);

          template = this._inheritance(etemplate, template.options.extends, template);
        }

        //Save into local buffer
        let initWatcher = !innerTemplateStorage[a_path];
        innerTemplateStorage[a_path] = template;

        //Set watch file
        if (initWatcher) {
          function clClearStorage(a_path){
            let obj = innerDependencies[a_path];
            delete innerDependencies[a_path];
            fcf.scriptExecutor.clear(a_path);
            delete innerTemplateStorage[a_path];
            fcf.each(obj, (a_depPath) => {
              clClearStorage(a_depPath);
            })
          }

          libFS.watchFile(a_path, {interval: 1000}, (a_eventType, a_filename)=>{
            clClearStorage(a_path);
          });
        }

        return template;
      }

      _inheritance(a_baseTemplate, a_baseTemplatePath, a_template){
        let self = this;
        let result = {
          options: {},
          templates: {},
          inheritance: [],
        };
        fcf.append(true, result.options, a_baseTemplate.options);
        fcf.append(true, result.options, a_template.options);
        fcf.append(result.inheritance, a_baseTemplate.inheritance, a_template.inheritance);
        result.inheritance.push(a_baseTemplatePath);

        let names = {};
        fcf.each(a_template.templates, function(k){names[k] = true;});
        fcf.each(a_baseTemplate.templates, function(k){names[k] = true;});
        for(let tname in names){
          if (!result.templates[tname])
            result.templates[tname] = {
              arguments:            {},
              options:              {},
              rawArguments:         [],
              template:             { items: [], template: "" },
              hooks:                {},
              hooksFiles:           [],
              hooksDependencies:    [],
              hooksDependenciesArgs:[],
              hardDependencies:     {},
              wrapperExists:        false,
            };

          let rawArgs;

          //build hooksFiles
          rawArgs = a_baseTemplate.templates[tname] && a_baseTemplate.templates[tname].hooksFiles ? a_baseTemplate.templates[tname].hooksFiles : [];
          fcf.append(result.templates[tname].hooksFiles, rawArgs);
          rawArgs = a_template.templates[tname] && a_template.templates[tname].hooksFiles ? a_template.templates[tname].hooksFiles : [];
          fcf.append(result.templates[tname].hooksFiles, rawArgs);


          //build hooksDependencies
          rawArgs = a_baseTemplate.templates[tname] && a_baseTemplate.templates[tname].hooksDependencies ? a_baseTemplate.templates[tname].hooksDependencies : [];
          fcf.append(result.templates[tname].hooksDependencies, rawArgs);
          rawArgs = a_template.templates[tname] && a_template.templates[tname].hooksDependencies ? a_template.templates[tname].hooksDependencies : [];
          fcf.append(result.templates[tname].hooksDependencies, rawArgs);

          //build hooksDependenciesArgs
          rawArgs = a_baseTemplate.templates[tname] && a_baseTemplate.templates[tname].hooksDependenciesArgs ? a_baseTemplate.templates[tname].hooksDependenciesArgs : [];
          fcf.append(result.templates[tname].hooksDependenciesArgs, rawArgs);
          rawArgs = a_template.templates[tname] && a_template.templates[tname].hooksDependenciesArgs ? a_template.templates[tname].hooksDependenciesArgs : [];
          fcf.append(result.templates[tname].hooksDependenciesArgs, rawArgs);

          // build raw arguments
          rawArgs = a_baseTemplate.templates[tname] && a_baseTemplate.templates[tname].rawArguments ? a_baseTemplate.templates[tname].rawArguments : [];
          fcf.append(result.templates[tname].rawArguments, rawArgs);
          rawArgs = a_template.templates[tname] && a_template.templates[tname].rawArguments ? a_template.templates[tname].rawArguments : [];
          fcf.append(result.templates[tname].rawArguments, rawArgs);

          //build options
          if (a_baseTemplate.templates[tname] && !fcf.empty(a_baseTemplate.templates[tname].options)){
            if (typeof a_baseTemplate.templates[tname].options == "object") {
              for(let property in a_baseTemplate.templates[tname].options){
                if (property == "include"){
                  if (!result.templates[tname].options.include)
                    result.templates[tname].options.include = [];
                  fcf.append(result.templates[tname].options.include, a_baseTemplate.templates[tname].options.include);
                }
                else if (property == "clientInclude"){
                  if (!result.templates[tname].options.clientInclude)
                    result.templates[tname].options.clientInclude = [];
                  fcf.append(result.templates[tname].options.clientInclude, a_baseTemplate.templates[tname].options.clientInclude);
                } else {
                  result.templates[tname].options[property] = a_baseTemplate.templates[tname].options[property];
                }
              }
            }
          }
          if (a_template.templates[tname] && !fcf.empty(a_template.templates[tname].options)){
            if (typeof a_template.templates[tname].options == "object") {
              for(let property in a_template.templates[tname].options){
                if (property == "include"){
                  if (!result.templates[tname].options.include)
                    result.templates[tname].options.include = [];
                  fcf.append(result.templates[tname].options.include, a_template.templates[tname].options.include);
                }
                else if (property == "clientInclude"){
                  if (!result.templates[tname].options.clientInclude)
                    result.templates[tname].options.clientInclude = [];
                  fcf.append(result.templates[tname].options.clientInclude, a_template.templates[tname].options.clientInclude);
                } else {
                  result.templates[tname].options[property] = a_template.templates[tname].options[property];
                }
              }
            }
          }

          //build wrapperExists
          if ((a_baseTemplate.templates[tname] && a_baseTemplate.templates[tname].wrapperExists) ||
              (a_template.templates[tname] && a_template.templates[tname].wrapperExists) ||
              (result.templates[tname].options.clientRendering))
            result.templates[tname].wrapperExists = true;

          // build hardDependencies
          let baseHardDependencies = a_baseTemplate.templates[tname] && a_baseTemplate.templates[tname].hardDependencies ? a_baseTemplate.templates[tname].hardDependencies : undefined;
          let hardDependencies     = a_template.templates[tname] && a_template.templates[tname].hardDependencies ? a_template.templates[tname].hardDependencies : undefined;
          fcf.append(result.templates[tname].hardDependencies, baseHardDependencies, hardDependencies);


          // build templates
          if (a_baseTemplate.templates[tname] && !fcf.empty(a_baseTemplate.templates[tname].template))
            result.templates[tname].template = a_baseTemplate.templates[tname].template;
          if (a_template.templates[tname] && !fcf.empty(a_template.templates[tname].template))
            result.templates[tname].template = a_template.templates[tname].template;

          // build hooks
          if (a_baseTemplate.templates[tname] && !fcf.empty(a_baseTemplate.templates[tname].hooks)){
            fcf.append(result.templates[tname].hooks, a_baseTemplate.templates[tname].hooks);
            if (!result.templates[tname].hooks.prototype)
              result.templates[tname].hooks.prototype = {};
            result.templates[tname].hooks.prototype = a_baseTemplate.templates[tname].hooks;
          }
          if (a_template.templates[tname] && !fcf.empty(a_template.templates[tname].hooks)){
            fcf.append(result.templates[tname].hooks, a_template.templates[tname].hooks);
            let dstHooks = result.templates[tname].hooks;
            let srcHooks = dstHooks.prototype;
            if (srcHooks) {
              if (!fcf.empty(srcHooks.hooksProgramableArgument)){
                for(let key in srcHooks.hooksProgramableArgument){
                  if (!(key in dstHooks.hooksProgramableArgument)){
                    dstHooks.hooksProgramableArgument[key] = srcHooks.hooksProgramableArgument[key];
                  }
                }
              }
              if (!fcf.empty(srcHooks.hooksBeforeArgument)){
                for(let key in srcHooks.hooksBeforeArgument)
                  if (!(key in dstHooks.hooksBeforeArgument))
                    dstHooks.hooksBeforeArgument[key] = srcHooks.hooksBeforeArgument[key];
              }
              if (!fcf.empty(srcHooks.hooksAfterArgument)){
                for(let key in srcHooks.hooksAfterArgument)
                  if (!(key in dstHooks.hooksAfterArgument))
                    dstHooks.hooksAfterArgument[key] = srcHooks.hooksAfterArgument[key];
              }
            }
          }
        }
        result.lastModifyTime = fcf.max(a_baseTemplate.lastModifyTime, a_template.lastModifyTime);

        return result;
      }

      _parseTemplate(a_ctxt, a_path, a_originPath, a_state, a_id, a_context) {
        let currentTemplate = fcf.NDetails.currentTemplate;
        var result = {
          options:   {},
          templates: {},
        };

        var blocksPositions = [];
        var lstpos = 0;
        do {
          var blockPositions = this._getBlockPositions(a_ctxt, lstpos);
          lstpos = blockPositions.dataEnd;
          if (lstpos) {
            blocksPositions.push(blockPositions);
          }
        } while (lstpos);

        for (var i = 0; i < blocksPositions.length; ++i) {
          this._parseBlockMainOptions(result, a_ctxt, blocksPositions[i], a_path);
        }

        fcf.NDetails.currentTemplate = currentTemplate;
        for (var i = 0; i < blocksPositions.length; ++i) {
          this._parseBlock(result, a_ctxt, blocksPositions[i], a_path, a_originPath);
        }

        fcf.each(result.templates, (a_key, a_subtemplateInfo)=>{
          if (!("clientRendering" in a_subtemplateInfo.options) && "clientRendering" in result.options)
            a_subtemplateInfo.options.clientRendering = result.options.clientRendering;
          if (!("wrapper" in a_subtemplateInfo.options) && "wrapper" in result.options)
            a_subtemplateInfo.options.wrapper = result.options.wrapper;

          let include = Array.isArray(a_subtemplateInfo.options.include) ? a_subtemplateInfo.options.include   :
                        !fcf.empty(a_subtemplateInfo.options.include)    ? [a_subtemplateInfo.options.include] :
                                                                           [];
          let jsInclude = [];
          for(let i = 0; i < include.length; ++i) {
            let ext = fcf.getExtension(include[i]).toLowerCase();
            let filePath = include[i];
            if (filePath[0] != "/" && filePath.indexOf(":") == -1){
              filePath = fcf.getDirectory(a_originPath) + "/" + filePath;
              if (filePath[0] != "/" && filePath.indexOf(":") == -1)
                filePath = ":" + filePath;
            }
            include[i] = filePath;
            if (ext == "js"){
              jsInclude.push(include[i]);
            }
          }

          a_subtemplateInfo.options.include = include;

          if (!fcf.empty(jsInclude)){
            fcf.require(jsInclude);
          }

          include = Array.isArray(a_subtemplateInfo.options.clientInclude) ? a_subtemplateInfo.options.clientInclude :
                    !fcf.empty(a_subtemplateInfo.options.clientInclude)    ? [a_subtemplateInfo.options.clientInclude] :
                                                                          [];
          for(let i = 0; i < include.length; ++i) {
            let ext = fcf.getExtension(include[i]).toLowerCase();
            let filePath = include[i];

            if (filePath[0] != "/" && filePath.indexOf(":") == -1){
              filePath = fcf.getDirectory(a_originPath) + "/" + filePath;
              if (filePath[0] != "/" && filePath.indexOf(":") == -1)
                filePath = ":" + filePath;
            }
            include[i] = filePath;
          }
          a_subtemplateInfo.options.clientInclude = include;
        });

        return result;
      }

      _parseBlockMainOptions(a_dst, a_ctxt, a_blockPositions, a_path) {
        let headerLine = a_ctxt.substr(a_blockPositions.headerBeg+3, a_blockPositions.headerEnd - a_blockPositions.headerBeg-3);
        let arrHeaderLine = fcf.splitSpace(headerLine);
        let type = arrHeaderLine[0] ? arrHeaderLine[0].toUpperCase() : "";
        if (type == "OPTIONS" && arrHeaderLine.length == 1) {
          let data = a_ctxt.substr(a_blockPositions.dataBeg, a_blockPositions.dataEnd - a_blockPositions.dataBeg);
          let stringNumber = this._getStringNumber(a_ctxt, a_blockPositions.dataBeg);
          let options = fcf.scriptExecutor.parse(data, {}, a_path, stringNumber);
          fcf.append(a_dst.options, options);
        }
      }

      _parseBlock(a_dst, a_ctxt, a_blockPositions, a_path, a_originPath) {
        let headerLine = a_ctxt.substr(a_blockPositions.headerBeg+3, a_blockPositions.headerEnd - a_blockPositions.headerBeg-3);
        let arrHeaderLine = fcf.splitSpace(headerLine);
        let type = arrHeaderLine[0] ? arrHeaderLine[0].toUpperCase() : "";
        let name = arrHeaderLine[1] ? arrHeaderLine[1] : "";
        if (!a_dst.templates[name]){
          a_dst.templates[name] = {
            options:              {},
            arguments:            {},
            rawArguments:         [],
            template:             {},
            hooks:                {},
            hooksFiles:           [],
            hooksDependencies:    [],
            hooksDependenciesArgs:[],
            hardDependencies:     {},
          };
        }
        a_originPath = a_originPath.split("+")[0];
        if (type == "OPTIONS") {
          let data = a_ctxt.substr(a_blockPositions.dataBeg, a_blockPositions.dataEnd - a_blockPositions.dataBeg);
          let stringNumber = this._getStringNumber(a_ctxt, a_blockPositions.dataBeg);
          fcf.append(a_dst.templates[name].options, fcf.scriptExecutor.parse(data, {}, a_path, stringNumber));
        } else if (type == "ARGUMENTS") {
          let data = a_ctxt.substr(a_blockPositions.dataBeg, a_blockPositions.dataEnd - a_blockPositions.dataBeg);
          let stringNumber = this._getStringNumber(a_ctxt, a_blockPositions.dataBeg);
          a_dst.templates[name].rawArguments.push({data: data, path: a_path, startLine: stringNumber});
        } else if (type == "TEMPLATE") {
          let content = a_ctxt.substr(a_blockPositions.dataBeg, a_blockPositions.dataEnd - a_blockPositions.dataBeg);
          a_dst.templates[name].template.originPath     = !name ? a_originPath : a_originPath + "+" + name;
          a_dst.templates[name].template.template       = this._contentToJS(this._removeLastLF(content));
          a_dst.templates[name].template.path           = a_path;
          a_dst.templates[name].template.stringNumber   = this._getStringNumber(a_ctxt, a_blockPositions.dataBeg);
        }
      }

      _getStringNumber(a_txt, a_position){
        if (a_position === undefined)
          a_position = a_txt.length;
        let pos = undefined;
        let lastpos = 0;
        let n   = 0;
        while(true) {
          pos = a_txt.indexOf("\n", pos !== undefined ? pos+1 : 0);
          if (pos == -1 || pos > a_position)
            return n;
          ++n;
          lastpos = pos;
        }
      }

      _getBlockPositions(a_ctxt, a_start) {
        var result = {
          headerBeg: undefined,
          headerEnd: undefined,
          dataBeg: undefined,
          dataEnd: undefined,
        };
        var pos;
        while(true) {
          pos = a_ctxt.indexOf("//~", a_start);
          if (pos != -1 && pos != 0 && a_ctxt[pos-1] != "\n") {
            a_start = pos;
            continue;
          }
          if (pos == -1)
            return result;
          var dpos = pos+3;
          if (dpos >= a_ctxt.length)
            return result;
          var epos = dpos;
          while (a_ctxt.charCodeAt(epos) > 32 && epos < a_ctxt.length)
            ++epos;
          if (epos >= a_ctxt.length)
            return result;
          var type = a_ctxt.substring(dpos, epos).toLowerCase();
          var validBlocks = {"template":true, "options":true, "arguments":true};
          if (!(type in validBlocks)){
            a_start = epos;
            continue;
          }
          break;
        }

        result.headerBeg = pos;
        if (result.headerBeg == -1){
          result.headerBeg = undefined;
          return result;
        }

        result.headerEnd = a_ctxt.indexOf("\n", pos);
        if (result.headerEnd == -1){
          result.headerEnd = a_ctxt.length;
        }

        result.dataBeg = (result.headerEnd + 1) > a_ctxt.length ? result.headerEnd
                                                                : result.headerEnd + 1;
        var lstpos = result.dataBeg;
        while (true) {
          pos = a_ctxt.indexOf("//~", lstpos);
          if (pos != -1 && a_ctxt[pos-1] != "\n") {
            lstpos = pos;
            continue;
          }
          break;
        }
        result.dataEnd = pos != -1 ? pos : a_ctxt.length;

        return result;
      }

      _removeLastLF(a_content){
        if (a_content.length >= 2 && a_content.charAt(a_content.length-2) == "\r" && a_content.charAt(a_content.length-1) == "\n")
          return a_content.substr(0, a_content.length-2);
        if (a_content.length >= 1 && a_content.charAt(a_content.length-1) == "\n")
          return a_content.substr(0, a_content.length-1);
        return a_content;
      }

      _contentToJS(a_content) {
        var items = [];
        var pos = 0;
        var startPos = 0;
        var type = "content";
        while(pos != -1) {
          var pos = a_content.indexOf("{{", pos);

          if (pos !== -1 && pos !== 0 && a_content[pos-1] == "$") {
            if (pos - startPos - 1)
              items.push({type: "content", data: a_content.substr(startPos, pos - startPos - 1)});
            var endPos = a_content.indexOf("}}$", pos);
            if (endPos != -1) {
              items.push({type: "variable", data: fcf.trim(a_content.substr(pos+2, endPos - pos - 2))});
              pos = endPos+3;
              startPos = pos;
            } else {
              pos = -1;
            }
          } else if (pos !== -1 && pos !== 0 && a_content[pos-1] == "#") {
            if (pos - startPos - 1)
              items.push({type: "content", data: a_content.substr(startPos, pos - startPos - 1)});
            var endPos = a_content.indexOf("}}#", pos);
            if (endPos != -1) {
              items.push({type: "variable", data: fcf.trim(a_content.substr(pos+2, endPos - pos - 2))});
              pos = endPos+3;
              startPos = pos;
            } else {
              pos = -1;
            }
          } else if (pos !== -1 && pos !== 0 && a_content[pos-1] == "@") {
            if (pos - startPos - 1)
              items.push({type: "content", data: a_content.substr(startPos, pos - startPos - 1)});
            let endPos = undefined;
            let fpos = pos;
            let counter = 0;
            while(true){
              let startNextBlock = a_content.indexOf("@{{", fpos);
              endPos = a_content.indexOf("}}@", fpos);
              if (!counter && (startNextBlock > endPos || startNextBlock == -1 || endPos == -1) )
                break;
              if (startNextBlock > endPos || (startNextBlock === -1 && endPos !== -1)){
                fpos = endPos+3;
                ++counter;
              } else if (endPos == -1){
                break;
              } else {
                fpos = startNextBlock+3;
                --counter;
              }
            }
            if (endPos != -1) {
              let code         = helper.insertCallPositions(a_content, "template", "", pos+2, endPos);
              items.push({type: "calculation", data: fcf.trim(fcf.trim(code), [";"])});
              pos = endPos+3;
              startPos = pos;
            } else {
              pos = -1;
            }
          } else if (pos !== -1 && pos !== 0 && a_content[pos-1] == "!") {
            if (pos - startPos - 1)
              items.push({type: "content", data: a_content.substr(startPos, pos - startPos - 1)});
            var endPos = a_content.indexOf("}}!", pos);
            if (endPos != -1) {
              items.push({type: "translate", data: fcf.trim(a_content.substr(pos+2, endPos - pos - 2))});
              pos = endPos+3;
              startPos = pos;
            } else {
              pos = -1;
            }
          } else if (pos !== -1 && pos !== 0 && a_content[pos-1] == "%") {
            if (pos - startPos - 1)
              items.push({type: "content", data: a_content.substr(startPos, pos - startPos - 1)});
            var endPos = a_content.indexOf("}}%", pos);
            if (endPos != -1) {
              let code         = helper.insertCallPositions(a_content, "template", "", pos+2, endPos);
              items.push({type: "code", data: code});
              pos = endPos+3;
              startPos = pos;
            } else {
              pos = -1;
            }
          } else if (pos == -1) {
            if (a_content.length - startPos)
              items.push({type: "content", data: a_content.substr(startPos, a_content.length - startPos)});
            pos = -1;
          } else {
            pos = pos+2;
          }
        }

        var result = "var _2318115_block_env={args: args, route: route, decor: decor};";
        for (var i = 0; i < items.length; ++i) {
          if (items[i].type == "code"){
            result += items[i].data;
            result += ";";
          } else if (items[i].type == "variable") {
            result += "render.write(fcf.resolve(_2318115_block_env, " + JSON.stringify(items[i].data) + ") );";
          } else if (items[i].type == "calculation") {
            result += "render.write(" + items[i].data + ");";
          } else if (items[i].type == "translate") {
            result +="(()=>{";
            result +="  let content947339 = fcf.t(" + JSON.stringify(items[i].data) + ");";
            result +="  let contentIndex947339 = 0;";
            result +="  while(contentIndex947339 < content947339.length && contentIndex947339 != -1) {";
            result +="    let posStart947339 = content947339.indexOf('@{{', contentIndex947339);";
            result +="    render.write(";
            result +="      content947339.substr(contentIndex947339, posStart947339 != -1 ? (posStart947339 - contentIndex947339) : (content947339.length - contentIndex947339))";
            result +="    );";
            result +="    let posEnd947339 = content947339.indexOf('}}@', posStart947339);";
            result +="    if (posStart947339 == -1 || posEnd947339 == -1)";
            result +="      break;";
            result +="    render.write(eval(fcf.trim(content947339.substr(posStart947339 + 3, (posEnd947339 - posStart947339) - 3), ';')));";
            result +="    contentIndex947339 = posEnd947339 + 3;";
            result +="  }";
            result +="})();";
          } else if (items[i].type == "content") {
            result += "render.write(" + JSON.stringify(items[i].data) + ");";
            let lines  = this._getStringNumber(items[i].data);
            for(let i = 0; i < lines; ++i)
              result += "\n";
          }
        }



        return result;
      }

    }

    return NDetails.Loader;
  }
});
