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

fcf.module({
  name: "fcf:NRender/NDetails/Loader.js",
  dependencies: [],
  module: function() {
    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;
          let event = await fcf.application.getEventChannel().send("loading_template", {path: a_path, template: template})
          fcf.getContext().currentTemplate = currentTemplateInfo;
          if (a_cb)
            a_cb(undefined, event.template);
          return event.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 (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);
          }
        }

        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.asyncEach(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);
        }

        fcf.each(result.templates, (a_key, a_subtemplateInfo)=>{
          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) {
        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:     {},
          };
        }
        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.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         = this._insertCallPositions(a_content.substr(pos+2, endPos - pos - 2), pos+2);
              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         = this._insertCallPositions(a_content.substr(pos+2, endPos - pos - 2), pos+2,);
              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;
      }

      _insertCallPositions(a_content, a_position){
        function isWN(a_char){
          if (!a_char)
            return false;
          let chatCode = a_char.charCodeAt(0);
          return (chatCode >= '0'.charCodeAt(0) && chatCode <= '9'.charCodeAt(0)) ||
                 (chatCode >= 'a'.charCodeAt(0) && chatCode <= 'z'.charCodeAt(0)) ||
                 (chatCode >= 'A'.charCodeAt(0) && chatCode <= 'Z'.charCodeAt(0)) ||
                 a_char[0] == "_";
        }
        function skipSpaces(a_pos){
          while(a_pos < a_content.length){
            if (a_content.charCodeAt(a_pos) > 32)
              return a_pos;
            ++a_pos;
          }
          return -1;
        }
        function findMethod(a_object, a_methods, a_pos){
          let pos = a_content.indexOf(a_object, a_pos);
          if (pos == -1)
            return;
          if (isWN(a_content[pos-1])) {
            pos += 1;
            return;
          }
          pos += a_object.length;
          pos = skipSpaces(pos);
          if (pos == -1)
            return;
          if (a_content[pos] != ".")
            return;
          pos += 1;
          pos = skipSpaces(pos);
          if (pos == -1)
            return;
          let found = false;
          for(let i = 0; i < a_methods.length; ++i){
            if (a_content.substr(pos, a_methods[i].length) == a_methods[i]){
              found = true;
              pos += a_methods[i].length;
              break;
            }
          }
          if (!found)
            return;
          pos = skipSpaces(pos);
          if (pos == -1)
            return;
          if (a_content[pos] != "(")
            return;
          return pos+1;
        }
        function isRegEx(a_pos){
          if (a_content[a_pos] == "/"){
            let p = a_pos-1;
            while(p >= 0){
              if (a_content.charCodeAt(p) <= 32){
                --p;
                continue;  
              } else if (a_content[p] == "(" || a_content[p] == ","  || a_content[p] == "="){
                return true;
              } else {
                return false;
              }
            }
          }
          return false;
        }
        function findCloseArgs(a_pos){
          let bracketCounter = 0;
          let slashCounter = 0;
          let quote = false;
          let regex = false;
          let regexInBracket = false;
          while(a_pos < a_content.length){
            let c = a_content[a_pos];
            if (!quote && !regex){
              if(isRegEx(a_pos)) {
                regex = true;
                regexInBracket = false;
                slashCounter = 0;
              } else if (c == "\"" || c == "'" || c == "`") {
                quote = c;
                slashCounter = 0;
              } else if (c == "(") {
                ++bracketCounter;
              } else if (c == ")") {
                if (!bracketCounter)
                  return a_pos;
                --bracketCounter;
              }
              ++a_pos;
            } else if (quote) {
              if (c == "\\") {
                ++slashCounter;
              } else if (c == quote && slashCounter%2 == 0) {
                quote = false;
              } else {
                slashCounter = 0;
              }
              ++a_pos;
            } else {
              if (c == "\\") {
                ++slashCounter;
              } else if (c == "/" && !regexInBracket && slashCounter%2 == 0) {
                regex = false;
              } else if (c == "[" && !regexInBracket && slashCounter%2 == 0) {
                regexInBracket = true;
                slashCounter = 0;                
              } else if (c == "]" && regexInBracket && slashCounter%2 == 0) {
                regexInBracket = false;
                slashCounter = 0;                
              } else {
                slashCounter = 0;
              }
              ++a_pos;
            }
          }
          return -1;
        }

        let result = "";
        let pos    = 0;
        let lpos   = 0;
        while(pos != -1) {
          let nextPos = findMethod("render", ["view", "template"], pos);
          if (nextPos === undefined) {
            pos = -1;
            continue;
          } else {
            let cp = nextPos;
            while(a_content[cp] != "\n" && cp>=0)
              --cp;
            ++cp;
            let slashCounter = 0;
            let quote = false;
            let regex = false;
            let regexInBracket = false;
            while(cp < nextPos){
              let c = a_content[cp];
              if (!quote && !regex){
                if(isRegEx(cp)) {
                  regex = true;
                  regexInBracket = false;
                  slashCounter = 0;
                } else if (c == "\"" || c == "'" || c == "`") {
                  quote = c;
                  slashCounter = 0;
                }
                ++cp;
              } else if (quote) {
                if (c == "\\") {
                  ++slashCounter;
                } else if (c == quote && slashCounter%2 == 0) {
                  quote = false;
                } else {
                  slashCounter = 0;
                }
                ++cp;
              } else {
                if (c == "\\") {
                  ++slashCounter;
                } else if (c == "/" && !regexInBracket && slashCounter%2 == 0) {
                  regex = false;
                } else if (c == "[" && !regexInBracket && slashCounter%2 == 0) {
                  regexInBracket = true;
                  slashCounter = 0;                
                } else if (c == "]" && regexInBracket && slashCounter%2 == 0) {
                  regexInBracket = false;
                  slashCounter = 0;                
                } else {
                  slashCounter = 0;
                }
                ++cp;
              }
            }
            pos = nextPos;
            if (quote || regex)
              continue;
          }
          pos = findCloseArgs(pos);
          if (pos == -1)
            continue;
          result += a_content.substring(lpos, nextPos);
          result += this._insertCallPositions(a_content.substring(nextPos, pos), nextPos + a_position);
          result += `, ${nextPos + a_position - 1}`;
          lpos = pos;
        }
        result += a_content.substr(lpos);
        return result;
      }

    }

    return NDetails.Loader;
  }
});
