fcf.module({
  name: "fcf:NRender/NDetails/Helper.js",
  module: function() {

    function setArg(a_taskInfo, a_argName, a_value){
        var normPath = fcf.normalizeObjectAddress(a_argName);
        var ptr = fcf.resolveEx(a_taskInfo.args, normPath, true);
        ptr.object[ptr.key] = a_value;
        delete a_taskInfo.srcArgs[normPath];
        a_taskInfo.processedArgs[normPath] = true;
    }

    let helper = {
      getLineCount(a_string, a_start, a_end){
        let i = a_start;
        let n = 0;
        while(true){
          let j = a_string.indexOf("\n", i);
          if (j == -1 || j >= a_end || j >= a_string.length) {
            return { line: n, character: a_end - i };
          }
          ++n;
          i = j+1;
        }
      },
      insertCallPositions(a_content, a_type, a_preffix, a_position, a_end, a_deferred){
        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_end){
            if (a_content.charCodeAt(a_pos) > 32)
              return a_pos;
            ++a_pos;
          }
          return -1;
        }
        function findMethodInHooks(a_pos){
          let re = /[a-zA-Z_][a-zA-Z0-9_]*\s*[\.]\s*render\s*\(/g;
          re.lastIndex = a_pos;
          let m = re.exec(a_content);
          if (!m || m.index + m[0].length >= a_end)
            return;
          return m.index + m[0].length;
        }
        function findMethodInRender(a_object, a_methods, a_pos){
          let pos = a_content.indexOf(a_object, a_pos);
          if (pos == -1 || pos >= a_end)
            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 (pos + a_methods[i].length < a_end && 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;
          if (pos+1 >= a_end)
            return;
          return pos+1;
        }
        function isRegEx(a_pos){
          if (a_content[a_pos] == "/"){
            let p = a_pos-1;
            while(p >= a_position){
              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_end){
            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;
        }

        if (a_end === undefined)
          a_end = a_content.length;

        let result = "";
        let pos    = a_position;
        let lpos   = a_position;
        while(pos != -1) {
          let nextPos1 = a_type === "hooks" ? findMethodInHooks(pos)
                                            : findMethodInRender("render", ["view", "template"], pos);
          let nextPos = nextPos1;
          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 += helper.insertCallPositions(a_content, a_type, a_preffix, nextPos, pos);
          let curpos = nextPos;
          let linePos = helper.getLineCount(a_content, 0, curpos);
          while(true) {
            let c = a_content[curpos];
            if (c == "\n"){
              --linePos.line;
            } else if (c === "."){
              ++linePos.character;
              break;
            } else {
              --linePos.character;
            }
            --curpos;
          }
          result += `, "${a_preffix}${linePos.line+1}:${linePos.character+1}"`;
          if (a_deferred)
            result += ", true";
          lpos = pos;
        }
        result += a_content.substring(lpos, a_end);
        return result;
      },
      appendChildInfo: function(a_dstArgs, a_srcArgs, a_chlidInfo, a_existsWrapper){
        if (!a_chlidInfo || !a_chlidInfo.childs)
          return;
        if (!a_srcArgs)
          return;
        if (a_existsWrapper) {
          if (a_chlidInfo.childs[a_srcArgs.fcfCP]) {
            fcf.append(a_dstArgs, a_chlidInfo.childs[a_srcArgs.fcfCP].args);
            a_dstArgs.fcfChildsArgs = a_chlidInfo.childs[a_srcArgs.fcfCP];
          }
        } else {
          a_dstArgs.fcfChildsArgs = a_chlidInfo;
        }
      },

      callHook: function(a_template, a_name, a_taskInfo) {
        if (!a_template.hooks || !a_template.hooks[a_name])
          return;
        fcf.setContext(a_taskInfo.context);
        if (typeof a_template.hooks[a_name] == "function"){
          var result = a_template.hooks[a_name].call(a_template.hooks, a_taskInfo);
          if ((typeof Promise !== "undefined" && result instanceof Promise) || result instanceof fcf.Actions) {
            a_taskInfo.actions.then(function(a_res, a_act){
              result
              .then(function(result){
                a_act.complete();
              })
              .catch(function(a_error){
                a_act.error(a_error);
              });
            });
          }
        }
      },
      callHookBeforeArgument: function(a_template, a_argName, a_taskInfo) {
        if (!a_template.hooks || !a_template.hooks.hooksBeforeArgument || !a_template.hooks.hooksBeforeArgument[a_argName])
          return;
        fcf.setContext(a_taskInfo.context);
        if (typeof a_template.hooks.hooksBeforeArgument[a_argName] == "function") {
          var result = a_template.hooks.hooksBeforeArgument[a_argName].call(a_template.hooks, a_taskInfo, a_argName);
          if ((typeof Promise !== "undefined" && result instanceof Promise) || result instanceof fcf.Actions) {
            a_taskInfo.actions.then(function(a_res, a_act){
              result
              .then(function(result){
                a_act.complete();
              })
              .catch(function(a_error){
                a_act.error(a_error);
              });
            });
          }
        }
        if (typeof a_template.hooks.hooksBeforeArgument["*"] == "function") {
          var result = a_template.hooks.hooksBeforeArgument["*"].call(a_template.hooks, a_taskInfo, a_argName);
          if ((typeof Promise !== "undefined" && result instanceof Promise) || result instanceof fcf.Actions) {
            a_taskInfo.actions.then(function(a_res, a_act){
              result
              .then(function(result){
                a_act.complete();
              })
              .catch(function(a_error){
                a_act.error(a_error);
              });
            });
          }
        }
      },
      callHookAfterArgument: function(a_template, a_argName, a_taskInfo) {
        if (!a_template.hooks || !a_template.hooks.hooksAfterArgument || !a_template.hooks.hooksAfterArgument[a_argName])
          return;
        fcf.setContext(a_taskInfo.context);
        if (typeof a_template.hooks.hooksAfterArgument[a_argName] == "function") {
          var result = a_template.hooks.hooksAfterArgument[a_argName].call(a_template.hooks, a_taskInfo, a_argName);
          if ((typeof Promise !== "undefined" && result instanceof Promise) || result instanceof fcf.Actions) {
            a_taskInfo.actions.then(function(a_res, a_act){
              result
              .then(function(result){
                if (result !== undefined)
                  setArg(a_taskInfo, a_argName, result);
                a_act.complete();
              })
              .catch(function(a_error){
                a_act.error(a_error);
              });
            });
          } else if (result !== undefined) {
            setArg(a_taskInfo, a_argName, result);
          }
        }
        if (typeof a_template.hooks.hooksAfterArgument["*"] == "function") {
          var result = a_template.hooks.hooksAfterArgument["*"].call(a_template.hooks, a_taskInfo, a_argName);
          if ((typeof Promise !== "undefined" && result instanceof Promise) || result instanceof fcf.Actions) {
            a_taskInfo.actions.then(function(a_res, a_act){
              result
              .then(function(result){
                if (result !== undefined)
                  setArg(a_taskInfo, a_argName, result);
                a_act.complete();
              })
              .catch(function(a_error){
                a_act.error(a_error);
              });
            });
          } else if (result !== undefined) {
            setArg(a_taskInfo, a_argName, result);
          }
        }
      },
      callHookProgramableArgument: function(a_template, a_argName, a_taskInfo) {
        if (!a_template.hooks || !a_template.hooks.hooksProgramableArgument || !a_template.hooks.hooksProgramableArgument[a_argName])
          return;
        fcf.setContext(a_taskInfo.context);
        if (typeof a_template.hooks.hooksProgramableArgument[a_argName] == "function"){
          var result = a_template.hooks.hooksProgramableArgument[a_argName].call(a_template.hooks, a_taskInfo);
          if ((typeof Promise !== "undefined" && result instanceof Promise) || result instanceof fcf.Actions) {
            a_taskInfo.actions.then(function(a_res, a_act){
              result
              .then(function(result){
                setArg(a_taskInfo, a_argName, result);
                a_act.complete();
              })
              .catch(function(a_error){
                a_act.error(a_error);
              });
            });
          } else if (result !== undefined) {
            setArg(a_taskInfo, a_argName, result);
          }
        }
      }
    };

    return helper;
  }
});
