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;

    helper = {
      normalizeTemplatePath(a_path, a_theme){
        if (a_path[0] == "+")
          return a_path;
        let pathArr = a_path.split("+");
        if (pathArr[0][0] == "@") {
          let theme = typeof a_theme == "object" ? a_theme : fcf.application.getTheme(a_theme, true);
          pathArr[0] = theme.resolveAlias(pathArr[0]);
          if (pathArr[0].indexOf("+") != -1){
            let arr = pathArr[0].split("+");
            pathArr[0] = arr[0];
            pathArr[1] = arr[1];
          };
        }
        if (pathArr[0][0] == ":"){
          pathArr[0] = pathArr[0].substr(1);
        }
        return pathArr[1] && pathArr[1] != "" ? pathArr[0] + "+" + pathArr[1] : pathArr[0];
      },
      resolveArg(a_arg, a_args, a_state, _a_reqursionProtected, _a_reqursionArgs){
        if (!_a_reqursionArgs)
          _a_reqursionArgs = {};
        if (!_a_reqursionProtected)
          _a_reqursionProtected = {};
        if (!fcf.isArg(a_arg)) {
          return a_arg;
        } else if (a_arg.type == "value") {
          let variables = fcf.getVariablesString(a_arg.value);
          for (let i = 0; i < variables.length; ++i) {
            let variableArr = variables[i].split(".");
            if (variableArr[0] != "args") {
              return undefined;
            }
            let argName = fcf.trim(variableArr[1]);
            if (_a_reqursionProtected[argName]) {
              if (!_a_reqursionArgs[argName]) {
                return undefined;
              }
            } else {
              _a_reqursionArgs[argName] = helper.resolveArg(a_args[argName], a_args, a_state, _a_reqursionProtected, _a_reqursionArgs);
            }
          }
          return fcf.tokenize(a_arg.value, {args: _a_reqursionArgs});
        } else if (a_arg.type == "reference") {
          let variables = fcf.getVariablesString(a_arg.id);
          for (let i = 0; i < variables.length; ++i) {
            let variableArr = variables[i].split(".");
            if (variableArr[0] != "args") {
              return undefined;
            }
            let argName = fcf.trim(variableArr[1]);
            if (_a_reqursionProtected[argName]) {
              if (!_a_reqursionArgs[argName]) {
                return undefined;
              }
            } else {
              _a_reqursionArgs[argName] = helper.resolveArg(a_args[argName], a_args, a_state, _a_reqursionProtected, _a_reqursionArgs);
            }
          }
          let id = fcf.tokenize(a_arg.id, {args: _a_reqursionArgs});
          if (id == a_args.fcfId){
            return fcf.resolve(a_args, a_arg.arg);
          } else {
            return fcf.resolve(a_state.args[id], a_arg.arg);
          }
        }
        return undefined;
      },
      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;
        if (typeof a_template.hooks[a_name] == "function"){
          a_taskInfo.actions.then(()=>{
            return a_template.hooks[a_name].call(a_template.hooks, a_taskInfo);
          });
        }
      },
      callHookBeforeArgument: function(a_template, a_argName, a_taskInfo) {
        if (!a_template.hooks || !a_template.hooks.hooksBeforeArgument || !a_template.hooks.hooksBeforeArgument[a_argName])
          return;
        if (typeof a_template.hooks.hooksBeforeArgument[a_argName] == "function") {
          a_taskInfo.actions.then(()=>{
            return a_template.hooks.hooksBeforeArgument[a_argName].call(a_template.hooks, a_taskInfo, a_argName);
          });
        }
        if (typeof a_template.hooks.hooksBeforeArgument["*"] == "function") {
          a_taskInfo.actions.then(()=>{
            return a_template.hooks.hooksBeforeArgument["*"].call(a_template.hooks, a_taskInfo, a_argName);
          });
        }
      },
      callHookAfterArgument: function(a_template, a_argName, a_taskInfo) {
        if (!a_template.hooks || !a_template.hooks.hooksAfterArgument || !a_template.hooks.hooksAfterArgument[a_argName])
          return;
        if (typeof a_template.hooks.hooksAfterArgument[a_argName] == "function") {
          a_taskInfo.actions.then(()=>{
            return a_template.hooks.hooksAfterArgument[a_argName].call(a_template.hooks, a_taskInfo, a_argName);
          })
          .then((a_result)=>{
            if (a_result !== undefined)
              setArg(a_taskInfo, a_argName, a_result);
          })
        }
        if (typeof a_template.hooks.hooksAfterArgument["*"] == "function") {
          a_taskInfo.actions.then(()=>{
            return a_template.hooks.hooksAfterArgument["*"].call(a_template.hooks, a_taskInfo, a_argName);
          })
          .then((a_result)=>{
            if (a_result !== undefined)
              setArg(a_taskInfo, a_argName, a_result);
          })
        }
      },
      callHookProgramableArgument: function(a_template, a_argName, a_taskInfo) {
        if (!a_template.hooks || !a_template.hooks.hooksProgrammableArgument || !a_template.hooks.hooksProgrammableArgument[a_argName])
          return;
        if (typeof a_template.hooks.hooksProgrammableArgument[a_argName] == "function"){
          a_taskInfo.actions.then(()=>{
            return a_template.hooks.hooksProgrammableArgument[a_argName].call(a_template.hooks, a_taskInfo);
          })
          .then((a_result)=>{
            if (a_result !== undefined)
              setArg(a_taskInfo, a_argName, a_result);
          })
        }
      }
    };

    return helper;
  }
});
