function _fcfGlobals()  { return this; }

(function() {
var fcf = {};
fcf.NDetails = {};
fcf.NDetails.renderInstructions = {};
fcf.tools    = {};


fcf.isServer = () => {
  return typeof module === "object" && typeof module.filename !== "undefined";
}

if (fcf.isServer()) {
  var libFS           = require('fs');
  var libNet          = require('net');
  var libChildProcess = require('child_process');

  fcf.settings = {
    innerRoot: process.cwd(),
    outerRoot: "/",
    libraries: {
      fcf: "",
    },
  };

} else {
  fcf.settings = {
    innerRoot: '',
    outerRoot: "/",
    routeAliases: {},
    libraries: {
      fcf: "",
    },
  };

}


fcf.id = (a_size)=>{
  if (!a_size)
    a_size = 33;
  var result = "_";
  for(var i = 0; i < a_size-1; ++i)
    result += (Math.floor(Math.random()*16)).toString(16);
  return result;
}

fcf.NDetails.translations = undefined;

fcf.t = (a_txt, a_lang) => {

  if (fcf.isServer()){
    if (!fcf.NTools)
      return a_txt;
    var translations = fcf.NSystem.cache.get("fcf", "translations");
    var context      = fcf.getContext();
    var variables    = fcf.NSystem.cache.get("fcf", "variables");
    var lang         = a_lang !== undefined ?                       a_lang :
                       context ?                                    context.get("language") :
                       variables && variables["defaultLanguage"] ?  variables["defaultLanguage"] :
                                                                    "en";
    return translations && translations[lang] &&  translations[lang][a_txt] ? translations[lang][a_txt] : a_txt;
  } else {
    if (fcf.NDetails.translations === undefined){
      fcf.loadObject({
        path: "/fcfpackages/fcf/Translations.js",
        async: false,
      })
      .then((a_data)=>{
        fcf.NDetails.translations = a_data;
      })
    }
    var lang         = a_lang ? a_lang : fcf.getContext().get("language");
    return fcf.NDetails.translations && fcf.NDetails.translations[lang] &&  fcf.NDetails.translations[lang][a_txt] ? fcf.NDetails.translations[lang][a_txt] : a_txt;
  }
  return a_txt;
}

fcf.escapeQuotes = (a_ctxt)=>{
  var rc;
  var result = "";

  a_ctxt = fcf.str(a_ctxt);

  for(var i = 0; i < a_ctxt.length; ++i) {
    switch(a_ctxt[i]){
      case "\"": rc = "\\\"";    break;
      case "'": rc = "\\'";    break;
      default:  rc = a_ctxt[i]; break;
    }
    result += rc;
  }

  return result;
}


fcf.unescape = (a_item) => {
  if (typeof a_item == "object") {
    a_item = fcf.clone(a_item);
    if (Array.isArray(a_item)) {
      for (var key = 0; key < a_item.length; ++key)
        a_item[key] = fcf.unescape(a_item[key]);
    } else {
      for (var key in a_item)
        a_item[key] = fcf.unescape(a_item[key]);
    }
    return a_item;
  } else if (typeof a_item == "string"){
    let result = "";
    let counter = 0;
    for(let i = 0; i < a_item.length; ++i) {
      let c = a_item[i];
      if (c == "\\") {
        ++counter;
        if (counter%2 == 0)
          result += c;
      } else {
        counter = 0;
        result += c;
      }
    }
    return result;
  }
}

fcf.encodeHtml = (a_ctxt) =>{
  var ch;
  var rc;
  var result = "";

  a_ctxt = a_ctxt === undefined || a_ctxt === null ? "" : a_ctxt;

  if (typeof a_ctxt === 'object'){
    a_ctxt = JSON.stringify(a_ctxt);
  } else if (typeof a_ctxt !== 'string'){
    a_ctxt = fcf.str(a_ctxt);
  }
  for(var i = 0; i < a_ctxt.length; ++i) {
    switch(a_ctxt[i]){
      case "<": rc = "&lt;";    break;
      case ">": rc = "&gt;";    break;
      case "\"": rc = "&quot;"; break;
      case "\'": rc = "&#39;";  break;
      case "&": rc = "&amp;";  break;
      default:  rc = a_ctxt[i]; break;
    }
    result += rc;
  }
  return result;
}

fcf.decodeHtml = (a_ctxt) => {
  if (typeof a_ctxt !== "string")
    return a_ctxt;

  var map = {
    "<": "&lt;",
    ">": "&gt;",
    "\"": "&quot;",
    "\'": "&#39;",
    "&": "&amp;",
  };

  for(var k in map)
    a_ctxt = fcf.replaceAll(a_ctxt, map[k], k);

  return a_ctxt;
}

fcf.stripTags = (a_txt) =>{
  if (typeof a_txt != "string")
    return a_txt;
  var reg = new RegExp("(<[^>]*>)", "g");
  return a_txt.replace(reg, "");
}

fcf.styleToString = (a_name, a_value) => {
  let result = "";
  if (typeof a_name == "object"){
    for(let key in a_name) {
      result += fcf.styleToString(key, a_name[key]);
    }
  } else {
    if (a_value === undefined || a_value === null || a_value === "")
      return result;
    if (["left", "top", "bottom", "right", "width", "min-width", "max-width", "height", "min-height", "max-height"].indexOf(a_name) != -1) {
      if (fcf.str(a_value).search(/([^\.0-9])/) == -1)
        a_value = a_value + "px";
    }
    result = a_name + ": " + a_value + "; ";
  }
  return result;
}

fcf.replaceAll = (a_str, a_search, a_replacement) => {
  if (a_str === undefined || a_str === null)
    return "";
  if (a_str.indexOf(a_search) == -1)
    return a_str;
  var result = a_str.split(a_search).join(a_replacement);
  return result;
}

fcf.count = (a_object) =>{
  if (typeof a_object == "object") {
    if (fcf.isEnumerable(a_object)) {
      return a_object.length;
    } else {
      var count = 0;
      for(var k in a_object)
        ++count;
      return count;
    }
  } else if (typeof a_object == "string") {
    return a_object.length;
  } else {
    return 0;
  }
}

fcf.append = (...args)=>{
  var startArg = typeof args[0] === "boolean" ? 1 : 0;
  var req = typeof args[0] === "boolean" && args[0];

  if (Array.isArray(args[startArg])) {
    for(var j = startArg + 1; j < args.length; ++j) {
      if (!args[j])
        continue;
      if (req) {
        for(var i = 0; i < args[j].length; ++i) {
          var itm = args[j][i] === null            ? null :
                    Array.isArray(args[j][i])      ? fcf.append(true, [], args[j][i]) :
                    args[j][i] instanceof Date     ? new Date(args[j][i]) :
                    typeof args[j][i] === "object" ? fcf.append(true, Object.create(Object.getPrototypeOf(args[j][i])), args[j][i]) :
                                                          args[j][i];
          args[startArg].push(itm);
        }
      } else {
        for(var i = 0; i < args[j].length; ++i)
          args[startArg].push(args[j][i]);
      }
    }
  } else if (typeof args[startArg] === "object") {
    for(var j = startArg + 1; j < args.length; ++j) {
      if (!args[j])
        continue;
      var prop = Object.getOwnPropertyNames(args[j]);
      for(var pkey in prop) {
        var key = prop[pkey];
        if (req && typeof args[j][key] === "object" && args[j][key] !== null){
          if (!args[startArg][key])
            args[startArg][key] = Array.isArray(args[j][key])   ? [] :
                                  args[j][key] instanceof Date  ? new Date(args[j][key]) :
                                                                  Object.create(Object.getPrototypeOf(args[j][key]));
          fcf.append(true, args[startArg][key], args[j][key]);
        } else {
          args[startArg][key] = args[j][key];
        }
      }
    }
  }
  return args[startArg];
}

fcf.clone = (a_object) => {
  return  a_object === null           ? null :
          Array.isArray(a_object)     ? fcf.append(true, [], a_object) :
          typeof a_object == "object" ? fcf.append(true, Object.create(Object.getPrototypeOf(a_object)), a_object) :
                                       a_object;
}


fcf.insertBefore = (a_object, a_insertItems, a_search) => {
  if (typeof a_object != "object" && typeof a_object != "string")
    return a_object;
  var result = Array.isArray(a_object)      ? [] :
               typeof a_object == "object"  ? {} :
                                              "";
  var isSrcFn = typeof a_search == "function";
  var isInsertComplete = false;
  if (Array.isArray(a_object) || typeof a_object != "string"){
    for(var i = 0; i < a_object.length; ++i) {
      if (isSrcFn){
        if (!isInsertComplete && a_search(i, a_object[i])){
          fcf.append(result, a_insertItems);
          isInsertComplete = true;
        }
      } else {
        if (!isInsertComplete && a_object[i] == a_search){
          fcf.append(result, a_insertItems);
          isInsertComplete = true;
        }
      }
      result.push(a_object[i]);
    }
  } else {
    for(var i in a_object) {
      if (isSrcFn){
        if (!isInsertComplete && a_search(i, a_object[i])){
          fcf.append(result, a_insertItems);
          isInsertComplete = true;
        }
      } else {
        if (!isInsertComplete && a_object[i] == a_search){
          fcf.append(result, a_insertItems);
          isInsertComplete = true;
        }
      }
      result[i] = a_object[i];
    }
  }

  return result;
}

fcf.removeByValue = (a_object, a_value) => {
  if (Array.isArray(a_object)){
    var rmKeys = [];
    for(var i = 0; i < a_object.length; ++i)
      if (fcf.equal(a_object[i], a_value))
        rmKeys.push(i);
    for(var i = 0; i < rmKeys.length; ++i)
      a_object.splice(rmKeys[i]-i,1);

  } else if (typeof a_object === "object"){
    var rmKeys = [];
    for(var i in a_object)
      if (fcf.equal(a_object[i], a_value))
        rmKeys.push(i);
    for(var i = 0; i < rmKeys.length; ++i)
      delete a_object[rmKeys[i]];
  }
}

fcf.prepareObject = (a_root, a_objectPath) => {
  a_objectPath = fcf.trim(a_objectPath, "/");
  if (fcf.empty(a_objectPath))
    return a_root;

  a_objectPath = a_objectPath.split("/").join(".");
  var items = a_objectPath.split(".");
  var obj = a_root;
  for(var i = 0; i < items.length; ++i){
    if (!(items[i] in obj))
      obj[items[i]] = {};
    obj = obj[items[i]];
  }
  return obj;
}

fcf.NDetails.types = {};
if (!fcf.isServer()){
  fcf.NDetails.types.NodeList = NodeList;
} else {
  fcf.NDetails.types.NodeList = class {};
}

fcf.isEnumerable = (a_obj) => {
  if (typeof a_obj != "object") {
    return false;
  } else if (Array.isArray(a_obj)) {
    return true;
  } else if (a_obj instanceof fcf.NDetails.types.NodeList) {
    return true;
  } else {
    for(let k in a_obj){
      if (k == "length")
        return true;
      if (k == 0 && "length" in a_obj)
        return true;
      return false;
    }
    return false;
  }
}

fcf.find = (a_obj, a_search) => {
  if (typeof a_search === "function"){
    if (fcf.isEnumerable(a_obj)){
      for(let i = 0; i < a_obj.length; ++i){
        if (a_search(i, a_obj[i]))
          return i;
      }
    } else if (typeof a_obj === "object"){
      for(let k in a_obj) {
        if (a_search(k, a_obj[k]))
          return k;
      }
    }
  } else {
    if (fcf.isEnumerable(a_obj)){
      if (Array.isArray(a_search)){
        for(let i = 0; i < a_search.length; ++i)
          for(let i = 0; i < a_obj.length; ++i)
            if (a_search[i] == a_obj[i])
              return i;
      } else {
        for(let i = 0; i < a_obj.length; ++i)
          if (a_search == a_obj[i])
            return i;
      }
    } else if (typeof a_obj === "object") {
      if (Array.isArray(a_search)){
        for(let i = 0; i < a_search.length; ++i)
          for(let k in a_obj)
            if (a_search[i] == a_obj[k])
              return k;
      } else {
        for(let k in a_obj)
          if (a_search == a_obj[k])
            return k;
      }
    } else if (typeof a_obj === "string") {
      if (Array.isArray(a_search)){
        for(let i = 0; i < a_search.length; ++i){
          let res = a_obj.indexOf(a_search[i]);
          if (res != -1)
            return res;
        }
      } else {
        let res = a_obj.indexOf(a_search);
        return res != -1 ? res : undefined;
      }
    }
  }
};

fcf.sortObject = (a_object, a_cb) => {
  a_object = fcf.append(true, {}, a_object);
  Object.defineProperty(a_object, "___fcf___order", {
    value: Object.keys(a_object).sort(a_cb),
    writable: true,
    enumerable: false,
  });
  Object.defineProperty(a_object, "forEach", {
    value: function(a_cb){
      var BreakException = {};
      let order = this.___fcf___order;
      try {
        order.forEach((a_value, a_index, a_array)=>{
          if (a_cb(a_object[a_value], a_value, a_object) !== undefined)
            throw BreakException;
        })
      } catch (e){
        if (BreakException !== e)
          throw e;
      }
    },
    writable: true,
    enumerable: false,
  });
  return a_object;
}

fcf.findVal = (a_obj, a_cb)=>{
  return a_obj[fcf.find(a_obj, a_cb)];
}


fcf.each = (a_obj, a_cb)=>{
  let isAsync = a_cb.toString().substr(0, 5) == "async" && a_cb.toString().charCodeAt(5) <= 32;
  let asyncResult = isAsync ? fcf.actions() : undefined;
  var result = undefined;
  if (a_obj && typeof a_obj == "object" && Array.isArray(a_obj.___fcf___order)){
    for(var i = 0; i < a_obj.___fcf___order.length; ++i) {
      if (isAsync){
        result = a_cb(a_obj.___fcf___order[i], a_obj[a_obj.___fcf___order[i]]).catch((e)=>{ asyncResult.error(e) });
      } else {
        result = a_cb(a_obj.___fcf___order[i], a_obj[a_obj.___fcf___order[i]]);
      }
      if (result instanceof Promise || result instanceof fcf.Actions) {
        let index         = i + 1;
        let currentResult = result;
        let actions       = fcf.actions();
        let act           = undefined;
        result  = asyncResult ? asyncResult : fcf.actions();
        result.then((a_res, a_act) => { act = a_act; });
        function doAction(){
          actions.then(()=>{
            if (index < a_obj.___fcf___order.length){
              return a_cb(a_obj.___fcf___order[index], a_obj[a_obj.___fcf___order[index]]);
            }
          })
          .then((a_res)=>{
            ++index;
            if (a_res === undefined && index < a_obj.___fcf___order.length) {
              doAction();
            } else {
              act.complete(a_res);
            }
          })
          .catch((e)=>{
            act.error(e);
          });
        }
        currentResult.then((a_res)=>{
          if (a_res === undefined) {
            doAction();
          } else {
            act.complete(a_res);
          }
        })
        .catch((e)=>{
          act.complete(e);
        })

        break;
      } if (result !== undefined) {
        break;
      }
    }
  } else if (fcf.isEnumerable(a_obj)) {
    for(var i = 0; i < a_obj.length; ++i) {
      if (isAsync){
        result = a_cb(i, a_obj[i]).catch((e)=>{ asyncResult.error(e) });
      } else {
        result = a_cb(i, a_obj[i]);
      }
      if (result instanceof Promise || result instanceof fcf.Actions) {
        let index   = i + 1;
        let currentResult = result;
        result  = asyncResult ? asyncResult : fcf.actions();
        let actions = fcf.actions();
        let act     = undefined;
        result.then((a_res, a_act) => { act = a_act; });
        function doAction(){
          actions.then(()=>{
            if (index < a_obj.length)
              return a_cb(index, a_obj[index]);
          })
          .then((a_res)=>{
            ++index;
            if (a_res === undefined && index < a_obj.length) {
              doAction();
            } else {
              act.complete(a_res);
            }
          })
          .catch((e)=>{
            act.error(e);
          });
        }
        currentResult.then((a_res)=>{
          if (a_res === undefined) {
            doAction();
          } else {
            act.complete(a_res);
          }
        })
        .catch((e)=>{
          act.complete(e);
        })
        .catch((e)=>{
          act.error(e);
        });


        break;
      } else if (result !== undefined) {
        break;
      }
    }
  } else if (typeof a_obj === "object") {
    let asyncEnable = false;
    let asyncKeys = [];
    let index = 0;
    for(var k in a_obj) {
      if (asyncEnable) {
        asyncKeys.push(k);
      } else {
        if (isAsync){
          result = a_cb(k, a_obj[k]).catch((e)=>{ asyncResult.error(e) });
        } else {
          result = a_cb(k, a_obj[k]);
        }

        if (result instanceof Promise || result instanceof fcf.Actions) {
          asyncEnable = true;
        } else if (result !== undefined) {
          break;
        }
      }
    }

    if (asyncEnable) {
      let currentResult = result;
      result            = asyncResult ? asyncResult : fcf.actions();
      let actions       = fcf.actions();
      let act           = undefined;
      result.then((a_res, a_act) => { act = a_act; });
      function doAction(){
        actions.then(() => {
          if (index < asyncKeys.length)
            return a_cb(asyncKeys[index], a_obj[asyncKeys[index]]);
        })
        .then((a_res) => {
          ++index;
          if (a_res === undefined && index < asyncKeys.length) {
            doAction();
          } else {
            act.complete(a_res);
          }
        })
        .catch((e)=>{
          act.error(e);
        });

      }
      currentResult.then((a_res) => {
        if (a_res === undefined) {
          doAction();
        } else {
          act.complete(a_res);
        }
      })
      .catch((e)=>{
        act.error(e);
      });

    }
  } else if (typeof a_obj === "string") {
    for(var i = 0; i < a_obj.length; ++i) {
      result = a_cb(i, a_obj[i]);
      if (result !== undefined)
        break;
    }
  }
  return result;
}

fcf.map = (a_object, a_cb) => {
  var result = {};
  fcf.each(a_object, function(a_key, a_value){
    var itm = a_cb(a_key, a_value);
    if (Array.isArray(itm) ) {
      result[itm[0]] = itm[1];
    } else {
      fcf.append(result, itm);
    }
  })
  return result;
}

fcf.filter = (a_object, a_cb) => {
  var result = Array.isArray(a_object) ? [] : {};
  fcf.each(a_object, (k, v) => {
    if (a_cb(k, v)){
      if (Array.isArray(a_object))
        result.push(v);
      else
        result[k] = v;
    }
  });
  return result;
}

fcf.array = (a_object, a_cb) => {
  var result = [];
  fcf.each(a_object, (a_key, a_value) => {
    var itm = a_cb(a_key, a_value);
    if (itm !== undefined)
      result.push(itm);
  })
  return result;
}

fcf.max = (a_left, a_right) => {
  if (a_left === undefined || a_left === null)
    return a_right;
  if (a_right === undefined || a_right === null)
    return a_left;
  return a_left > a_right ? a_left : a_right;
}

fcf.min = (a_left, a_right) => {
  if (a_left === undefined || a_left === null)
    return a_right;
  if (a_right === undefined || a_right === null)
    return a_left;
  return a_left < a_right ? a_left : a_right;
}

fcf.range = (a_value, a_left, a_right, a_default) => {
  a_left = parseFloat(a_left);
  a_left = isNaN(a_left) ? Number.MIN_VALUE : a_left;

  a_right = parseFloat(a_right);
  a_right = isNaN(a_right) ? Number.MAX_VALUE : a_right;

  a_value = parseFloat(a_value);
  if (isNaN(a_value) && a_default !== undefined)
    return a_default;

  a_value = isNaN(a_value) ? a_left : a_value;
  return a_value <= a_left  && a_default === undefined ? a_left :
         a_value <= a_left  && a_default !== undefined ? a_default :
         a_value >= a_right && a_default === undefined ? a_right :
         a_value >= a_right && a_default !== undefined ? a_default :
                                                         a_value;
}

fcf.inc = (a_value, a_incArg) => {
  a_incArg = parseFloat(a_incArg);
  if (isNaN(a_incArg))
    a_incArg = 1;
  var val = parseFloat(a_value)
  return isNaN(val) ? a_value : val + a_incArg;
}

fcf.dec = (a_value, a_decArg) => {
  a_decArg = parseFloat(a_decArg);
  if (isNaN(a_decArg))
    a_decArg = 1;
  var val = parseFloat(a_value)
  return isNaN(val) ? a_value : val - a_decArg;
}

fcf.first = (a_object) => {
  if (fcf.isEnumerable(a_object)) {
    return a_object[0];
  } else if (typeof a_object == "object") {
    for(var k in a_object)
      return a_object[k];
    return;
  } else if (typeof a_object == "string") {
    return a_object.charAt(0);
  }
}

fcf.firstKey = (a_object) => {
  if (fcf.isEnumerable(a_object)) {
    return a_object.length ? 0 : undefined;
  } else if (typeof a_object == "object") {
    for(var k in a_object)
      return k;
    return;
  } else if (typeof a_object == "string") {
    return a_object.length ? 0 : undefined;
  }
}

fcf.first2 = (a_object, a_default) => {
  var res = fcf.first(fcf.first(a_object));
  return res === undefined ? a_default : res;
}

fcf.last = (a_object) => {
  if (fcf.isEnumerable(a_object)) {
    return a_object.length ? a_object[a_object.length - 1] : undefined;
  } else if (typeof a_object == "object") {
    var last;
    for(var k in a_object)
      last = a_object[k];
    return last;
  } else if (typeof a_object == "string") {
    return a_object.length ? a_object.charAt(a_object.length-1) : "";
  }
}

fcf.last2 = (a_object, a_default) => {
  var res = fcf.last(fcf.last(a_object));
  return res === undefined ? a_default : res;
}

fcf.byteCount = (a_string) => {
  var result = 0;
  for(var i = 0; i < a_string.length; ++i)
    result += Math.floor((Math.log(a_string.charCodeAt(i))/Math.log(2))/8) + 1;
  return result;
}

fcf.empty = (a_object) => {
  if (a_object instanceof Error){
    return false;
  } else if (a_object === undefined || a_object === null){
    return true;
  } else if (typeof a_object === "string") {
    return a_object == "";
  } else if (Array.isArray(a_object)) {
    return a_object.length === 0;
  } else if (!fcf.isServer() && a_object instanceof NodeList) {
    return a_object.length === 0;
  } else if (typeof a_object === "object") {
    for(var k in a_object)
      return false;
    return true;
  } else if (typeof a_object === "number") {
    return isNaN(a_object);
  }
  return false;
}

fcf.isSpaceString = function(a_str, a_startPos){
  if (!a_str)
    return true;
  for(let i = a_startPos; i < a_str.length; ++i) {
    if (a_str.charCodeAt(i) > 32){
      return false;
    }
  }
  return true;
}

fcf.isEmptyTag = function(a_str){
  let posStart = 0;
  posStart = a_str.indexOf("<", posStart);
  if (posStart == -1)
    return false;
  posStart = a_str.indexOf(">", posStart);
  if (posStart == -1)
    return false;
  ++posStart;

  let posEnd;
  posEnd = a_str.indexOf("</", posStart);
  if (posEnd == -1)
    return false;

  let isEmptyBody = fcf.isSpaceString(a_str.substring(posStart, posEnd));

  posEnd = a_str.indexOf(">", posEnd);
  if (posEnd == -1)
    return false;

  return isEmptyBody && fcf.isSpaceString(a_str, posEnd+1);
}


fcf.in = (a_obj, a_value) => {
  if (Array.isArray(a_obj)) {
    if (Array.isArray(a_value)){
      for(var i = 0; i < a_obj.length; ++i)
        for(var j = 0; j < a_value.length; ++j)
          if (a_obj[i] == a_value[j])
            return true;
    } else if (typeof a_value === "object"){
      for(var i = 0; i < a_obj.length; ++i)
        for(var j in a_value)
          if (a_obj[i] == a_value[j])
            return true;
    } else {
      for(var i = 0; i < a_obj.length; ++i)
        if (a_obj[i] == a_value)
          return true;
    }
  } else if (typeof a_obj == "object") {
    if (Array.isArray(a_value)){
      for(var k in a_obj)
        for(var j = 0; j < a_value.length; ++j)
          if (a_obj[k] == a_value[j])
            return true;
    } else if (typeof a_value == "object"){
      for(var k in a_obj)
        for(var j in a_value)
          if (a_obj[k] == a_value[j])
            return true;
    } else {
      for(var k in a_obj)
        if (a_obj[k] == a_value)
          return true;
    }
  } else if (typeof a_obj == "string") {
    return a_obj.indexOf(a_value) !== -1;
  }
  return false;
}

fcf.splitSpace = (a_ctxt) => {
  var result = [];
  var empty = true;
  var beg   = 0;
  for (var i = 0; i < a_ctxt.length; ++i) {
    var code = a_ctxt.charCodeAt(i);
    if (!empty && (code <= 32 && code >= 0)){
      result.push(a_ctxt.substr(beg, i - beg));
      empty = true;
    } else {
      if (empty)
        beg = i;
      empty = false;
    }
  }

  if (!empty)
    result.push(a_ctxt.substr(beg, a_ctxt.length - beg));

  return result;
}


fcf.ltrimPos = (a_str, a_arr) => {
  var pos = 0;
  for(; pos < a_str.length; ++pos) {
    if (Array.isArray(a_arr)){
      var found = false;
      for(var i = 0; i < a_arr.length; ++i){
        if (a_str.charAt(pos) == a_arr[i]){
          found = true;
          break;
        }
      }
      if (!found)
        break;
    } else if (a_arr !== undefined){
      if (a_str.charAt(pos) != a_arr)
        break;
    } else {
      var code = a_str.charCodeAt(pos);
      if (code > 32 || code < 0)
        break;
    }
  }

  return pos;
}


fcf.rtrimPos = (a_str, a_arr) => {
  if (!a_str.length)
    return 0;

  var pos = a_str.length-1;
  for(; pos >= 0; --pos) {
    if (Array.isArray(a_arr)){
      var found = false;
      for(var i = 0; i < a_arr.length; ++i){
        if (a_str.charAt(pos) == a_arr[i]){
          found = true;
          break;
        }
      }
      if (!found)
        break;
    } else if (a_arr !== undefined) {
      if (a_str.charAt(pos) != a_arr)
        break;
    } else {
      var code = a_str.charCodeAt(pos);
      if (code > 32 || code < 0)
        break;
    }
  }

  return pos+1;
}


fcf.ltrim = (a_str, a_arr) => {
  if (typeof a_str !== "string")
    return a_str;
  var pos = fcf.ltrimPos(a_str, a_arr);
  return pos != 0 ? a_str.substr(pos) : a_str;
}


fcf.rtrim = (a_str, a_arr) => {
  if (typeof a_str !== "string")
    return a_str;
  var pos = fcf.rtrimPos(a_str, a_arr);
  return pos != a_str.length ? a_str.substr(0, pos) : a_str;
}

fcf.trim = (a_str, a_arr) => {
  if (typeof a_str !== "string")
    return a_str;
  var a_str = fcf.str(a_str);
  var posBeg = fcf.ltrimPos(a_str, a_arr);
  var posEnd = fcf.rtrimPos(a_str, a_arr);
  return posBeg != 0 || posEnd != a_str.length
          ? a_str.substr(posBeg, posEnd - posBeg)
          : a_str;
}

fcf.str = (a_data) => {
  return a_data === undefined                                                             ? "" :
         a_data === null                                                                  ? "" :
         a_data === NaN                                                                   ? "" :
         typeof a_data == "object" && a_data instanceof fcf.Exception                     ? a_data.toString() :
         typeof a_data == "object" && a_data.sqlMessage && a_data.sqlState && a_data.code ? a_data.sqlMessage :
         typeof a_data == "object" && a_data.message                                      ? a_data.message :
         typeof a_data == "object"                                                        ? JSON.stringify(a_data, undefined, 2) :
         typeof a_data !== "string"                                                       ? a_data.toString() :
                                                                                            a_data;
}

fcf.strToObject = (a_data) => {
  let result = a_data;
  if (typeof result === "string"){
    try {
      if (result.charAt(0) == "[" || result.charAt(0) == "{")
        result = JSON.parse(result);
    } catch(e) {}
  }
  return result;
}

fcf.merge = (a_dst, a_value, a_options) => {
  var type = a_options && a_options.type ? a_options.type : "append";
  a_dst = fcf.append(true, {}, a_dst);

  if (type == "append") {
    if (Array.isArray(a_value)) {
      var count = 0;
      var buf = {};
      var max = {};
      var order = a_options && !fcf.empty(a_options.position) ? a_options.position : -1;

      if (order < 0)
        order = a_dst.length;

      for(var i = 0; i < a_value.length; ++i){
        var o = (typeof a_value[i] === "object" && "mergePosition" in a_value[i]) ? a_value[i].mergePosition : order;
        if (!buf[o])
          buf[o] = [];
        buf[o].push(a_value[i]);
      }

      for(var i = 0; i < a_dst.length; ++i){
        if (!buf[i])
          buf[i] = [];
        buf[i].push(a_dst[i]);
      }


      var result = [];
      for(var k in buf) {
        for(var i = 0; i < buf[k].length; ++i) {
          result.push(buf[k][i]);
        }
      }

      return result;
    } else if (typeof a_value === "object") {
      return fcf.append(a_dst, a_value);
    }
  }
}

fcf.buildView = (a_viewDescription, a_mode) => {
  let result = fcf.append(true, {}, a_viewDescription);
  if (typeof a_viewDescription !== "object" || typeof a_viewDescription.usage !== "object")
    return result;

  let arrMode = a_mode.split(".");
  for(let usage in a_viewDescription.usage) {
    let arrUsage = usage.split(".");
    let coincidence = false;
    for(let i = 0; i < arrMode.length; ++i){
      if (i >= arrUsage.length){
        if (arrUsage[arrUsage.length-1] == "*"){
          coincidence = true;
          break;
        }
      }

      let eq = false;

      if (i == 0 || i == 1 || i > 2){
        if (arrMode[i] == "*")
          eq = true;
        else if (arrUsage[i] == "*")
          eq = true;
        else if (arrMode[i] === arrUsage[i])
          eq = true;
      } else {
        let submodes = arrMode[i] !== undefined ? arrMode[i].split("|") : [];
        if (!submodes[0])
          submodes.pop();
        let subusage = arrUsage[i] !== undefined ? arrUsage[i].split("|") : [];
        if (!subusage[0])
          subusage.pop();
        for(let submodesIndex = 0; submodesIndex < submodes.length; ++submodesIndex){
          if (fcf.find(subusage, "*") !== undefined) {
            eq = true;
            break;
          }
          else if (fcf.find(subusage, submodes[submodesIndex]) !== undefined){
            eq = true;
            break;
          }
        }
      }

      if (i == arrMode.length-1 && arrUsage.length > arrMode.length) {
        let subsequentMatches = true;
        for(let j = i+1; j < arrUsage.length; ++j){
          if (j == 0 || j == 1 || j > 2){
            if (arrUsage[j] != "*"){
              subsequentMatches = false;
              break;
            }
          } else {
            let subusage = arrUsage[j] !== undefined ? arrUsage[j].split("|") : [];
            if (fcf.find(subusage, "*") === undefined)
              subsequentMatches = false;
          }
        }

        if (!subsequentMatches)
          eq = false;
      }

      if (i == arrMode.length-1 && eq)
        coincidence = true;

      if (!eq)
        break;

    }

    if (coincidence)
      fcf.append(result, a_viewDescription.usage[usage]);
  }

  delete result.usage;
  return result;
}

fcf.modeMerge = (a_srcMode, a_modifyMode)=>{
  let arrSrc    = fcf.trim(a_srcMode, ["."]).split(".");
  let arrModify = fcf.trim(a_modifyMode, ["."]).split(".");
  let count     = fcf.max(arrModify.length, arrSrc.length);
  let result    = [];
  for(let i = 0; i < count; ++i) {
    if (i == 0 || i == 1 || i > 2){
      result.push(!!arrModify[i] && arrModify[i] != "?" ? arrModify[i] :
                  !!arrSrc[i]                           ? arrSrc[i] :
                                                          "*");
    } else {
      if ((arrModify[i] !== undefined && arrModify[i].indexOf("*") != -1) || (arrSrc[i] !== undefined && arrSrc[i].indexOf("*") != -1)) {
        result.push("*")
      } else {
        let srcItems = arrSrc[i] !== undefined ? arrSrc[i].split("|") : [];
        if (!srcItems[0])
          srcItems.pop();
        let modifyItems = arrModify[i] !== undefined ? arrModify[i].split("|") : [];
        if (!modifyItems[0])
          modifyItems.pop();
        for(let i = 0; i < modifyItems.length; ++i){
          if  (fcf.find(srcItems, modifyItems[i]) === undefined && modifyItems[i] != "?")
            srcItems.push(modifyItems[i]);
        }
        if (srcItems.length)
          result.push(srcItems.join("|"));
      }
    }
  }

  return result.join(".");
}


fcf.cutMode = (a_mode, a_cut) => {
  let res = "";
  let modeArr = fcf.trim(a_mode, ["*", "."]).split(".");
  for(let i = 0; i < a_cut; ++i)
    res += (i !== 0 ? ".": "") + modeArr[i];
  return res;
}

fcf.getVariablesFromCode = (a_str) => {
  if (typeof a_str != "string")
    return [];

  var re = new RegExp('([_a-z][_a-z0-9]*\\[[\'"][^\'"]*[\'"]\\]\\[[\'"][^\'"]*[\'"]\\])' +
                      '|([_a-z][_a-z0-9]*\\.[_a-z][_a-z0-9]*\\[[\'"][^\'"]*[\'"]\\])' +
                      '|([_a-z][_a-z0-9]*\\[[\'"][^\'"]*[\'"]\\]\\.[_a-z][_a-z0-9]*)' +
                      '|([_a-z][_a-z0-9]*\\.[_a-z][_a-z0-9]*\\.[_a-z][_a-z0-9]*)' +
                      '|([_a-z][_a-z0-9]*\\.[_a-z][_a-z0-9]*)' +
                      '|([_a-z][_a-z0-9]*\\[[\'"][^\'"]*[\'"]\\])' +
                      '|([_a-z][_a-z0-9]*)'

  , "gi");
  var res = a_str.match(re);
  if (!Array.isArray(res))
    res = [];
  return res;
}


fcf.NDetails.findBlockBegin = (a_str, a_pos) => {
  var result = { type: "", pos: -1 };
  while(true){
    a_pos = a_str.indexOf("{{", a_pos);
    if (a_pos == -1){
      return result;
    }
    if (a_pos == 0){
      a_pos += 2;
      continue;
    }
    var type = a_str[a_pos-1];
    var skip = a_pos >= 2 && a_str[a_pos-2] == "\\";
    if (skip){
      a_pos += 2;
      continue;
    }
    if (type == "$" || type == "#" || type == "@"){
      result.pos = a_pos - 1;
      result.type = type;
      return result;
    }
    a_pos += 2;
  }
}

fcf.getVariablesString = (a_str) => {
  var result = [];

  if (typeof a_str === "object") {
    for(let key in a_str)
      if (typeof a_str[key] === "object" || typeof a_str[key] === "string")
        fcf.append(result, fcf.getVariablesString(a_str[key]));
    return result;
  }

  if (typeof a_str !== "string")
    return result;


  var pos = 0;
  while(true) {
    var blockBegin = fcf.NDetails.findBlockBegin(a_str, pos);
    if (blockBegin.pos == -1)
      return result;

    var blockEndPos = a_str.indexOf("}}"+blockBegin.type, blockBegin.pos+3);
    if (blockEndPos == -1)
      return result;

    pos = blockEndPos+3;

    if (blockBegin.type == "$" || blockBegin.type == "#"){
      result.push(fcf.trim(a_str.substr(blockBegin.pos+3, blockEndPos - blockBegin.pos - 3)));
    } else if (blockBegin.type == "@"){
      var code = a_str.substr(blockBegin.pos+3, blockEndPos - blockBegin.pos - 3);
      fcf.append(result, fcf.getVariablesFromCode(code));
    }
  }
  return result;
}

fcf.pattern = (a_str, a_object, a_refInfo) => {
  if (!a_object)
    a_object = {};
  try {
    if (typeof a_str == "string") {
      return fcf._pattern(a_str, a_object, a_refInfo);
    } else if (typeof a_str == "object") {
      a_str = fcf.clone(a_str);
      fcf.each(a_str, (k,v) => {
        a_str[k] = fcf.pattern(a_str[k], a_object, a_refInfo);
      });
      return a_str;
    } else {
      return a_str;
    }
  } catch (e) {
    return "";
  }
}

fcf._pattern = (a_str, a_object, a_refInfo) => {
  a_refInfo = a_refInfo ? a_refInfo : {};
  if (!a_refInfo.references)
    a_refInfo.references = {};

  var result = "";
  var buff   = "";
  var state = 0
  var rmode = 0;
  var start = undefined;
  a_str = fcf.str(a_str);
  for (var i = 0; i < a_str.length; ++i) {
    var c = a_str.charAt(i);
    if (state == 0){
      if (c == "$")       { start = i; state = 1; rmode = 1;}
      else if (c == "@")  { start = i; state = 1; rmode = 2;}
      else if (c == "&")  { start = i; state = 1; rmode = 3;}
      else if (c == "!")  { start = i; state = 1; rmode = 4;}
      else                { result += c; }
    } else if (state == 1) {
      if (c == "{") { state = 2; buff = ""; }
      else          { result += (rmode==1 ? "$" :
                                 rmode==2 ? "@" :
                                 rmode==3 ? "&" :
                                            "!"
                                 ) + c; state = 0; }
    } else if (state == 2) {
      if (c == "}") {
        if (rmode == 1 && a_str.charAt(i+1) == "$"){
          state = 0;
          if (buff[0] == "{"){
            result += "${";
            result += buff;
            result += "}$";
          } if (start == 0 && i == a_str.length-2){
            return fcf.resolve(a_object, buff);
          } else {
            result += fcf.str(fcf.resolve(a_object, buff));
          }
          ++i;
        } else if (rmode == 2 && a_str.charAt(i+1) == "@"){
          state = 0;
          if (buff[0] == "{"){
            result += "@{";
            result += buff;
            result += "}@";
          } else if (start == 0 && i == a_str.length-2){
            return fcf.safeEvalResult(buff, a_object);
          } else {
            result += fcf.str(fcf.safeEvalResult(buff, a_object));
          }
          ++i;
        } else if (rmode == 3 && a_str.charAt(i+1) == "&"){
          state = 0;
          var refPathArr      = fcf.parseObjectAddress(buff);
          var refPathArrFirst = refPathArr[0];
          refPathArr.shift();
          var fullRef = a_refInfo.references[refPathArrFirst];
          for(var j = 0; j < refPathArr.length; ++j)
            fullRef += '["' + refPathArr[j] + '"]';
          var ref = fcf.argRef(a_refInfo.id, fullRef);
          if (start == 0 && i == a_str.length-2){
            return ref;
          } else {
            result += fcf.str(ref);
          }
          ++i;
        } else if (rmode == 4 && a_str.charAt(i+1) == "!"){
          state = 0;
          if (buff[0] == "{" && buff[buff.length-1] == "}")
            buff = buff.substr(1, buff.length-2);
          result += fcf.t(buff);
          ++i;
        } else {
          buff += c;
        }
      }
      else {
        buff += c;
      }
    }
  }
  return result;
}

fcf.translate = (a_str, a_language) => {
  try {
    if (typeof a_str == "string") {
      return fcf._translate(a_str, a_language);
    } else if (typeof a_str == "object") {
      a_str = fcf.clone(a_str);
      fcf.each(a_str, (k,v) => {
        a_str[k] = fcf.translate(a_str[k], a_language);
      });
      return a_str;
    } else {
      return a_str;
    }
  } catch (e) {
    return "";
  }
}

fcf._translate = (a_str, a_language) => {
  var result = "";
  var buff   = "";
  var state = 0
  var start = undefined;
  a_str = fcf.str(a_str);
  for (var i = 0; i < a_str.length; ++i) {
    var c = a_str.charAt(i);
    if (state == 0){
      if (c == "!")  { start = i; state = 1;}
      else           { result += c; }
    } else if (state == 1) {
      if (c == "{") { state = 2; buff = ""; }
      else          { result += "!" + c; state = 0; }
    } else if (state == 2) {
      if (c == "}") {
        if (a_str.charAt(i+1) == "!"){
          state = 0;
          if (buff[0] == "{" && buff[buff.length-1] == "}")
            buff = buff.substr(1, buff.length-2);
          result += fcf.t(buff, a_language);
          ++i;
        } else {
          buff += c;
        }
      } else {
        buff += c;
      }
    }
  }
  return result;
}


fcf.tokenize = (a_ctxtptr, a_obj, a_escape) => {
  if (!fcf.getContext())
    fcf.setContext(new fcf.Context());

  if (a_ctxtptr instanceof fcf.StringPointer){
    return fcf._tokenizePtrEx(ctxtptr, a_obj, a_escape).data
  } else if (typeof a_ctxtptr == "object"){
    a_ctxtptr = fcf.clone(a_ctxtptr);
    fcf.each(a_ctxtptr, (k, v) => {
      a_ctxtptr[k] = fcf.tokenize(v, a_obj, a_escape);
    })
    return a_ctxtptr;
  } else if (typeof a_ctxtptr == "string")  {
    if (a_ctxtptr.indexOf("{{") == -1)
      return a_ctxtptr;
    var ctxtptr = new fcf.StringPointer(a_ctxtptr);
    return fcf._tokenizePtrEx(ctxtptr, a_obj, a_escape).data;
  } else {
    return a_ctxtptr;
  }
}

fcf.tokenizeObject = (a_object, a_args, a_escape) => {
  var result = Array.isArray(a_object) ? [] : Object.create(Object.getPrototypeOf(a_object));

  if (fcf.isArg(a_object) && typeof a_object.level === "number" && a_object.level > 1)
    return fcf.clone(a_object);

  if (Array.isArray(a_object)){
    for(var i = 0; i < a_object.length; ++i){
      var v = a_object[i];
      if (typeof v === "object" && v !== null) {
        result[i] = fcf.tokenizeObject(v, a_args, a_escape);
      } else if (typeof v === "string") {
        result[i] = fcf.tokenize(v, a_args, a_escape);
      } else {
        result[i] = v;
      }
    }
  } else {
    for(var k in a_object) {
      var v = a_object[k];
      if (typeof v === "object" && v !== null) {
        if (typeof k == "string")
          k = fcf.tokenize(k, a_args, a_escape);
        result[k] = fcf.tokenizeObject(v, a_args, a_escape);
      } else if (typeof v === "string") {
        if (typeof k == "string")
          k = fcf.tokenize(k, a_args, a_escape);
        result[k] = fcf.tokenize(v, a_args, a_escape);
      } else {
        if (typeof k == "string")
          k = fcf.tokenize(k, a_args, a_escape);
        result[k] = v;
      }
    }
  }

  return result;
}



fcf.tokenizeEx = (a_ctxtptr, a_obj, a_escape) => {
  var ctxtptr = typeof a_ctxtptr === "object" ? a_ctxtptr
                                              : new fcf.StringPointer(a_ctxtptr);
  return fcf._tokenizePtrEx(ctxtptr, a_obj, a_escape);
}

fcf._tokenizePtrEx = (a_ctxtptr, a_obj, a_escape) => {
  var result = {
    data: "",
    noComplete: false,
    replace: false,
  };

  let clFindSubstitution = (a_str, a_startPos) => {
    var result = {
      beg: undefined,
      end: undefined,
      type: undefined,
    }
    var emptyResult = {
      beg: undefined,
      end: undefined,
      type: undefined,
    }
    var escCnt = 0;
    var state = 0; /*
                    0 find block
                    1 find first {
                    2 find second {
                    3 find first }
                    4 find second }
                    5 find close
                   */
    for (var i = a_startPos; i < a_str.length; ++i) {
      var c = a_str[i];
      if ((!a_escape || (escCnt % 2) == 0) && state == 0) {
        if (c == "@" || c == "#" || c == "$" || c == "!") {
          result.type = c;
          result.beg = i;
          state = 1;
        }
      } else if (state == 1) {
        if (c == "{") { state = 2; }
        else          { state = 0; --i; }
      } else if (state == 2) {
        if (c == "{") state = 3;
        else          state = 0;
      } else if (state == 3) {
        if (c == "}") {
          state = 4;
        }
      } else if (state == 4) {
        if (c == "}") {
          state = 5;
        } else {
          state = 3;
        }
      } else if (state == 5) {
        if (c == result.type) {
          result.end = i+1;
          return result;
        } else if (c == "}") {
          state = 5;
        } else {
          state = 3;
        }
      }

      if (c == "\\")
        ++escCnt;
      else
        escCnt = 0;
    }

    return emptyResult;
  }

  let clResolveSimple = (a_str, a_blockPosition) => {
    var key = a_str.substr(a_blockPosition.beg + 3, a_blockPosition.end - a_blockPosition.beg - 6);
    return fcf.resolveEx(a_obj, key);
  }

  let clResolveData = (a_str, a_blockPosition) => {
    var key = a_str.substr(a_blockPosition.beg + 3, a_blockPosition.end - a_blockPosition.beg - 6);
    return fcf.resolveEx(a_obj, key);
  }

  let clResolveCode = (a_str, a_blockPosition) => {
    var code = a_str.substr(a_blockPosition.beg + 3, a_blockPosition.end - a_blockPosition.beg - 6);
    var result;
    try {
      result = fcf.safeEvalResult('(' + code + ')', a_obj);
    } catch(e) {
      return {object: undefined, key: undefined};
    }

    if (typeof result === "number" && isNaN(result))
      return {object: undefined, key: undefined};

    return {object: {result: result}, key: "result"};
  }

  var pos    = a_ctxtptr.pos;
  var str    = a_ctxtptr.fullStr();
  while (pos !== undefined) {
    var blockPosition = clFindSubstitution(str, pos);
    var item = str.substr(
                    pos,
                    blockPosition.beg !== undefined ? blockPosition.beg - pos : str.length - pos);
    if (item !== "")
      result.data += item;

    if (blockPosition.beg !== undefined) {
      if (blockPosition.type == "$") {
        var retSubstitution = clResolveSimple(str, blockPosition);
        if (retSubstitution.object) {
          result.data = fcf.str(result.data) + fcf.str(retSubstitution.object[retSubstitution.key]);
          result.replace = true;
        } else {
          result.data += str.substr(blockPosition.beg, blockPosition.end - blockPosition.beg);
          result.noComplete = true;
        }
      } else if (blockPosition.type == "#") {
        var retSubstitution = clResolveData(str, blockPosition);
        if (retSubstitution.object) {
          result.data = (blockPosition.beg == 0 && blockPosition.end == str.length)
                            ? retSubstitution.object[retSubstitution.key]
                            : fcf.str(result.data) + retSubstitution.object[retSubstitution.key];
          result.replace = true;
        } else {
          result.data += str.substr(blockPosition.beg, blockPosition.end - blockPosition.beg);
          result.noComplete = true;
        }
      } else if (blockPosition.type == "@") {
        var retSubstitution = clResolveCode(str, blockPosition);
        if (retSubstitution.object) {
          result.data = (blockPosition.beg == 0 && blockPosition.end == str.length)
                            ? retSubstitution.object[retSubstitution.key]
                            : fcf.str(result.data) + retSubstitution.object[retSubstitution.key];
          result.replace = true;
        } else {
          result.data += str.substr(blockPosition.beg, blockPosition.end - blockPosition.beg);
          result.noComplete = true;
        }
      } else if (blockPosition.type == "!") {
          result.data += fcf.tokenize(fcf.t(str.substr(blockPosition.beg+3, blockPosition.end - blockPosition.beg-6)), a_obj, a_escape);
          result.replace = true;
      }
    }

    pos = blockPosition.end;
  }

  return result;
}


fcf.normalizeObjectAddress = (a_path) => {
  var arr = fcf.parseObjectAddress(a_path);
  var result = "";
  var key;
  for(var i = 0; i < arr.length; ++i) {
    key = fcf.replaceAll(arr[i], "\\", "\\\\");
    key = fcf.replaceAll(key, "\"", "\\\"");
    result += "[\"" + key + "\"]";
  }

  return result;
}

fcf.getObjectAddressItem = (a_path, a_numIndex) => {
  var arr = fcf.parseObjectAddress(a_path);
  var result = "";
  var key;
  for(var i = 0; i < arr.length && i <= a_numIndex; ++i) {
    key = fcf.replaceAll(arr[i], "\\", "\\\\");
    key = fcf.replaceAll(key, "\"", "\\\"");
    if (result != "")
      result += ".";
    result += "[\"" + key + "\"]";
  }

  return result;
}


class fcfStringPointer {
  constructor(a_string){
    this._str    = a_string;
    this.pos     = 0;
    this.posend  = this._str.length;
  }

  clone() {
    return fcf.append({}, this);
  }

  fullStr() {
    return this._str;
  }

  str(){
    return this._str.substr(this.pos, this.posend - this.pos);
  }

  indexOf(a_ctx, a_startPos){
    if (a_startPos === -1)
      return -1;

    var startPos = a_startPos !== undefined ? a_startPos : this.pos;
    var result = -1;

    if (typeof a_ctx === "string") {
      result = this._str.indexOf(a_ctx, startPos);
    } else {
      for(var i = startPos; i < this.posend; ++i) {
        var char = this._str.charAt(i);
        for(var j = 0; j < a_ctx.length; ++j) {
          if (a_ctx[j] == char) {
            if (result === -1 || i < result) {
              result = i;
              break;
            }
          }
        }
      }

    }
    return result >= this.posend ? -1 : result;
  }

  indexOfSpace(a_startPos){
    if (a_startPos === -1)
      return -1;
    var startPos = a_startPos !== undefined ? a_startPos : this.pos;
    for(var i = startPos; i < this.posend; ++i){
      var code = this._str.charCodeAt(i);
      if (code <= 32 && code > 0)
        return i;
    }
    return -1;
  }

  end (){
    return this.pos >= this.posend;
  }

  ltrim (a_arr) {
    var startPos = this.pos;
    if (a_arr === undefined) {
      while(!this.end()){
        var code = this._str.charCodeAt(this.pos);
        if (code <= 32 && code > 0)
          break;
        ++this.pos;
      }
    } else {
      var br = false;
      a_arr = Array.isArray(a_arr) ? a_arr : [a_arr];
      while(!this.end()) {
        var char = this._str.charAt(this.pos);
        for(var i = 0; i < a_arr.length; ++i){
          if (char == a_arr[i]){
            br = true;
          }
        }
        if (br)
          break;
        ++this.pos;
      }
    }
    return this._str.substr(startPos, this.pos - startPos);
  }
}



fcf.StringPointer = fcfStringPointer;

fcf.parseObjectAddress = (a_path) => {
  if (a_path.length === 0)
    return [];
  if (a_path.indexOf("[") == -1)
    return a_path.split(".");

  var state = 0;
  var key   = "";
  var result   = [];
  //var slashCounter = 0;
  var slashCounterMode = false;
  //0 read
  //1 read arr
  //2 read arr quote
  //3 read arr double quote
  //4 close simple
  //5 close arr
  //6 close arr quote
  //7 close arr double quote
  for(var i = 0; i < a_path.length; ++i) {
    var newState = state;
    var inserted = false;
    var c = a_path[i];

    if (state === 0){
      if (c === "[") {
        newState = 1;
      } else if (c === ".") {
        newState = 4;
      }
    } else if (state === 1) {
      if (c === "'") {
        newState = 2;
      } else if (c === "\"") {
        newState = 3;
      } else if (c === "]") {
        newState = 5;
      }
    } else if (state === 2) {
      if (c === "'") {
        if (!slashCounterMode)
          newState = 6;
      }
    } else if (state === 3) {
      if (c === "\"") {
        if (!slashCounterMode)
          newState = 7;
      }
    } else if (state === 5) {
      if (c === "[") {
        newState = 1;
      } else if (c === ".") {
        newState = 0;
        state = 0;
        continue;
      }
    } else if (state === 6) {
      if (c === "]") {
        newState = 5;
      }
    } else if (state === 7) {
      if (c === "]") {
        newState = 5;
      }
    }

    if (c == "\\"){
      slashCounterMode = !slashCounterMode;
    } else {
      slashCounterMode = false;
    }

    if (newState == state && newState < 4) {
      if (!slashCounterMode)
        key += c;
    } else if (newState == 1 && key.length) {
      result.push(key);
      key = "";
    } else if (newState == 4) {
      newState = 0;
      result.push(key);
      key = "";
    } else if (newState == 5) {
      result.push(key);
      key = "";
    }

    state = newState;
  }

  if (key !== "")
    result.push(key);

  return result;
}


fcf.resolveEx = (a_obj, a_path, a_createObj) => {
  if (typeof a_path == "string" && a_path[0] != "[" && a_path in a_obj){
    return {
      object: a_obj,
      key: a_path.length !== 0 ? a_path : undefined,
    }
  }

  var pathArr = fcf.parseObjectAddress(a_path);
  var result = {
    key: undefined,
    object: undefined,
  };
  var cur = a_obj;
  for(var i = 0; i < pathArr.length-1; ++i) {
    var key;
    if (Array.isArray(pathArr[i])) {
      var itm = fcf.resolveEx(a_obj, pathArr[i], a_createObj);
      key = itm.object[itm.key];
    } else {
      key = pathArr[i];
    }

    if (typeof cur[key] !== "object" || cur[key] === null)
      if (a_createObj)
        cur[key] = {};
      else
        return result;
    cur = cur[key];
  }

  var key;
  if (Array.isArray(pathArr[pathArr.length-1])) {
    var itm = fcf.resolveEx(a_obj, pathArr[pathArr.length-1], a_createObj);
    key = itm.object[itm.key];
  } else {
    key = pathArr[pathArr.length-1];
  }
  result.key = key;
  result.object = cur;

  return result;
}

fcf.resolve = (a_obj, a_path) => {
  if (typeof a_obj !== "object")
    return;

  if (typeof a_path == "string" && a_path[0] != "[" && a_path in a_obj){
    return a_obj[a_path];
  }

  if (Array.isArray(a_path)){
    var cur = a_obj
    for(var i = 0; i < a_path.length; ++i){
      if (typeof cur != "object")
        return;

      if (!(a_path[i] in cur))
        return;

      cur = cur[a_path[i]];
    }
    return cur;
  }

  var state = 0;
  var key   = "";
  var cur   = a_obj;
  var slashCounterMode = false;
  //0 read
  //1 read arr
  //2 read arr quote
  //3 read arr double quote
  //4 close simple
  //5 close arr
  //6 close arr quote
  //7 close arr double quote
  for(var i = 0; i < a_path.length; ++i){
    var newState = state;
    var c = a_path[i];

    if (state === 0){
      if (c === "[") {
        newState = 1;
      } else if (c === ".") {
        newState = 4;
      }
    } else if (state === 1) {
      if (c === "'") {
        newState = 2;
      } else if (c === "\"") {
        newState = 3;
      } else if (c === "]") {
        newState = 5;
      }
    } else if (state === 2) {
      if (c === "'") {
        if (!slashCounterMode)
          newState = 6;
      }
    } else if (state === 3) {
      if (c === "\"") {
        if (!slashCounterMode)
          newState = 7;
      }
    } else if (state === 5) {
      if (c === "[") {
        newState = 1;
      } else if (c === ".") {
        newState = 0;
        state = 0;
        continue;
      }
    } else if (state === 6) {
      if (c === "]") {
        newState = 5;
      }
    } else if (state === 7) {
      if (c === "]") {
        newState = 5;
      }
    }

    if (c == "\\"){
      slashCounterMode = !slashCounterMode;
    } else {
      slashCounterMode = false;
    }

    if (key.length && state == 0 && newState == 1) {
      cur = typeof cur === "object" && cur ? cur[key] : undefined;
      key = "";
    } if (newState == state && newState < 4){
      if (!slashCounterMode)
        key += c;
    } else if (newState == 4) {
      newState = 0;
      cur = typeof cur === "object" && cur ? cur[key] : undefined;
      key = "";
    } else if (newState == 5) {
      cur = typeof cur === "object" && cur ? cur[key] : undefined;
      key = "";
    }

    state = newState;

    if (cur === undefined)
      return;
  }

  if (key !== "")
    cur = typeof cur === "object" && cur ? cur[key] : undefined;

  return cur;
}

fcf.padEnd = (a_str, a_len, a_fill) => {
  if (typeof a_fill !== "string" || a_fill.length == 0)
    a_fill = " ";

  var result = a_str;
  if (result.length >= a_len)
    return result;

  var с = Math.floor((result.length - a_len) / a_fill.length);
  for(var i = 0; i < с; ++i)
    result += a_fill;

  var d = (result.length - a_len) % a_fill.length;
  if (d)
    result += a_fill.substr(0, d);

  return result;
}

fcf.serial = (a_part) => {
  var sm = fcf.NDetails.serial__map;
  if (!(a_part in sm))
    sm[a_part] = 0;
  return ++sm[a_part];
}

fcf.getLastSerial = (a_part) => {
  var sm = fcf.NDetails.serial__map;
  if (!(a_part in sm))
    sm[a_part] = 0;
  return sm[a_part];
}
fcf.NDetails.serial__map = {};


fcf.uuid = () => {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
  });
}

fcf.NPath = {};

fcf.NPath.concat = (a_left, a_right) => {
  if      (!a_left || a_left.length == 0)   return a_right;
  else if (!a_right || a_right.length == 0) return a_left;
  else if (a_left.slice(-1) == '/')         return a_left + a_right;
  else                                      return a_left + "/" + a_right;
}

fcf.equal = (a_left, a_right) => {
  if (!fcf._equalObject(a_left, a_right))
    return false;
  return fcf._equalObject(a_right, a_left);
}

fcf._equalObject = (a_left, a_right) => {
  if (Array.isArray(a_left)) {
    if (!Array.isArray(a_right))
      return false;
    if (a_left.length != a_right.length)
      return false;
    for (var i = 0; i < a_left.length; ++i) {
      if (!fcf.equal(a_left[i], a_right[i]))
        return false;
    }
  } else if (typeof a_left === "object"){
    if (typeof a_right !== "object")
      return false;
    for (let key in a_left) {
      if (a_left[key] !== undefined && !(key in a_right))
        return false;
      fcf._equalObject(a_left[key], a_right[key]);
    }
    for (let key in a_right) {
      if (a_left[key] !== undefined && !(key in a_right))
        return false;
    }
  } else {
    if (a_left != a_right) {
      return false;
    }
  }
  return true;
}

fcf.containsKeys = (a_object, a_what)=>{
  if (Array.isArray(a_what)) {
    for(let i = 0; i < a_what.length; ++i)
      if (a_what[i] in a_object)
        return true;
  } else if (typeof a_what === "object") {
    for(let k in a_what)
      if (a_what[k] in a_object)
        return true;
  } else {
    return a_what in a_object;
  }
  return false;
}

fcf.contains = (a_object, a_what)=>{
  if (Array.isArray(a_what)) {
    return fcf.find(a_object, (k, v)=>{
      for(let i = 0; i < a_what.length; ++i) {
        if (v == a_what[i])
          return true;
      }
      return false;
    }) !== undefined;
  } else {
    return fcf.find(a_object, (k, v)=>{ return v == a_what; }) !== undefined;
  }
}

fcf.access = (a_roles, a_additionalUserRoles)=>{
  if (!a_roles || fcf.empty(a_roles))
    return true;
  let context = fcf.getContext();
  let userRoles = context && context.session && context.session.user && context.session.user.roles
                    ? context.session.user.roles
                    : {};
  if ("root" in userRoles)
    return true;

  if (typeof a_roles === "string"){
    let roles = {};
    roles[a_roles] = a_roles;
    a_roles = roles;
  } else if (Array.isArray(a_roles)){
    a_roles = fcf.map(a_roles, (k,v)=>{ return [v,v]; });
  }

  if (a_roles["*"])
    return true;

  if (fcf.containsKeys(userRoles, a_roles))
    return true;

  if (a_additionalUserRoles) {
    if (Array.isArray(a_additionalUserRoles)){
      if (fcf.find(a_additionalUserRoles, "root") !== undefined)
        return true;
    } else if (typeof a_additionalUserRoles === "object"){
      if ("root" in a_additionalUserRoles)
        return true;
    } else if (typeof a_additionalUserRoles === "string"){
      if (a_additionalUserRoles == "root")
        return true;
    }
    if (fcf.contains(a_roles, a_additionalUserRoles))
      return true;
  }

  return false;
}

fcf.checkAccessObject = (a_rolesObject, a_partObject, a_additionalUserRoles)=>{
  if (!a_rolesObject) {
    return true;
  } if (typeof a_rolesObject == "string") {
    return fcf.access(a_rolesObject, a_additionalUserRoles);
  } else if (Array.isArray(a_rolesObject)) {
    return fcf.access(a_rolesObject, a_additionalUserRoles);
  } else if (typeof a_rolesObject === "object") {
    if (!a_rolesObject[a_partObject])
      return true;
    return fcf.access(a_rolesObject[a_partObject], a_additionalUserRoles);
  }
  return false;
}

fcf.parseDate = function(a_string, a_format, a_default, a_local) {
  let date = a_default instanceof Date    ? a_default :
             typeof a_default == "string" ? fcf.parseDate(a_default, a_format, (new Date((new Date(1970, 0, 1, 0, 0, 0, 0)).getTime() - ((new Date).getTimezoneOffset() * 60000)))) :
                                            (new Date((new Date(1970, 0, 1, 0, 0, 0, 0)).getTime() - ((new Date).getTimezoneOffset() * 60000)));
  if (!date || isNaN(date.getTime()))
    date = new Date();

  let setPM = false;
  let setAM = false;
  let formatIndex = 0;
  let sourceIndex = 0;
  for (;formatIndex < a_format.length && sourceIndex < a_string.length; ++formatIndex) {
    let c = a_format[formatIndex];
    if (c == "Y"){
      let val = parseInt(a_string.substr(sourceIndex, 4));
      if (isNaN(val))
        return new Date("");
      date.setYear(val);
      sourceIndex += 4;
    } else if (c == "m") {
      let val = parseInt(a_string.substr(sourceIndex, 2));
      if (isNaN(val) || val > 12 || val < 1)
        return new Date("");
      date.setMonth(val-1);
      sourceIndex += 2;
    } else if (c == "d") {
      let val = parseInt(a_string.substr(sourceIndex, 2));
      if (isNaN(val) || val > 31 || val < 1)
        return new Date("");
      date.setDate(val);
      sourceIndex += 2;
    } else if (c == "H") {
      let val = parseInt(a_string.substr(sourceIndex, 2));
      if (isNaN(val) || val > 23 || val < 0)
        return new Date("");
      date.setHours(val);
      sourceIndex += 2;
    } else if (c == "h") {
      let val = parseInt(a_string.substr(sourceIndex, 2));
      if (isNaN(val) || val > 12 || val < 1)
        return new Date("");
      if (val == 0 || val > 12)
        val = 12;
      date.setHours(val);
      sourceIndex += 2;
    } else if (c == "i") {
      let val = parseInt(a_string.substr(sourceIndex, 2));
      if (isNaN(val) || val > 59 || val < 0)
        return new Date("");
      date.setMinutes(val);
      sourceIndex += 2;
    } else if (c == "s") {
      let val = parseInt(a_string.substr(sourceIndex, 2));
      if (isNaN(val) || val > 59 || val < 0)
        return new Date("");
      date.setSeconds(val);
      sourceIndex += 2;
    } else if (c == "a" || c == "A") {
      let m = a_string.substr(sourceIndex, 2).toLowerCase();
      if (m == "pm")
        setPM = true;
      else
        setAM = true;
      sourceIndex += 2;
    } else if (c == "u") {
      let val = parseInt(a_string.substr(sourceIndex, 3));
      if (isNaN(val))
        return new Date("");
      date.setMilliseconds(val);
      sourceIndex += 3;
    } else {
      sourceIndex += 1;
    }
  }

  if (formatIndex != a_format.length)
    return new Date("");

  if (setAM && date.getHours() == 12)
    date.setHours(0);
  if (setPM && date.getHours() < 12)
    date.setHours(date.getHours() + 12);

  if (a_local === false){
    date = new Date(date.getTime() - ((new Date).getTimezoneOffset()*60000));
  } else if (typeof a_local == "number") {
    date = new Date(date.getTime() - (a_local*60000));
  }

  return date;
}

fcf.dateFormat = function(a_date, a_format, a_local) {
  let result = "";
  let slashCounter = 0;

  a_date = new Date(a_date);

  if (!(a_date instanceof Date) || isNaN(a_date.getTime()))
    return "";

  if (a_local === false){
    a_date = new Date(a_date.getTime() + ((new Date).getTimezoneOffset()*60000));
  } else if (typeof a_local == "number") {
    a_date = new Date(a_date.getTime() + (a_local*60000));
  }

  for(let i = 0; i < a_format.length; ++i){
    if (i != 0 && a_format[i-1] == "\\")
      ++slashCounter;
    else
      slashCounter=0;

    if (slashCounter%2 == 1){
      result += a_format[i];
      continue;
    } else if (a_format[i] == "\\") {
      continue;
    } else if (a_format[i] == "Y") {
      result += (a_date.getYear()+1900).toString().padStart(4, "0");;
    } else if (a_format[i] == "m") {
      result += (a_date.getMonth()+1).toString().padStart(2, "0");;
    } else if (a_format[i] == "d") {
      result += a_date.getDate().toString().padStart(2, "0");;
    } else if (a_format[i] == "H") {
      result += a_date.getHours().toString().padStart(2, "0");;
    } else if (a_format[i] == "h") {
      let h = a_date.getHours();
      result += (h == 0 ? 12 : h <= 12 ? h : h - 12).toString().padStart(2, "0");;
    } else if (a_format[i] == "i") {
      result += a_date.getMinutes().toString().padStart(2, "0");
    } else if (a_format[i] == "s") {
      result += a_date.getSeconds().toString().padStart(2, "0");
    } else if (a_format[i] == "u") {
      result += a_date.getMilliseconds().toString().padStart(3, "0");
    } else if (a_format[i] == "a") {
      result += a_date.getHours() < 12 ? "am" : "pm";
    } else if (a_format[i] == "A") {
      result += a_date.getHours() < 12 ? "AM" : "PM";
    } else {
      result += a_format[i];
    }
  }

  return result;
}

fcf.objects = {};

fcf.NDetails.objEventCounter = 0;

fcf.EventChannel = function(a_settings){
  var self = this;
  this._owner           = a_settings ? a_settings.owner : undefined;
  this._events          = {};
  this._processesEvent  = [];
  this._deferredEvents  = {};

  if (a_settings && a_settings.deferredEvents) {
    fcf.each(a_settings.deferredEvents, (k, v)=>{
      this._deferredEvents[v] = [];
    });
  }

  this.on = function(a_options) {
    if (typeof a_options == "string"){
      if (typeof arguments[1] == "function"){
        a_options = {
          name:         arguments[0],
          function:     arguments[1],
          once:         false,
          globalEvent:  false,
        };
      } else if (typeof arguments[2] == "function"){
        a_options = {
          name: arguments[0],
          function: arguments[2],
          once: false,
          globalEvent: arguments[1],
        };
      } else {
        return;
      }
    }
    return this.attach.call(this, a_options);
  }

  this.once = function(a_options) {
    if (typeof a_options == "string"){
      if (typeof arguments[1] == "function"){
        a_options = {
          name:         arguments[0],
          function:     arguments[1],
          once:         true,
          globalEvent:  false,
        };
      } else if (typeof arguments[2] == "function"){
        a_options = {
          name: arguments[0],
          function: arguments[2],
          once: true,
          globalEvent: arguments[1],
        };
      } else {
        return;
      }
    }

    return this.attach.call(this, a_options);
  }

  this.attach = function(a_options) {
    if (typeof a_options == "string"){
      if (typeof arguments[1] == "function"){
        a_options = {
          name:         arguments[0],
          function:     arguments[1],
          once:         false,
          globalEvent:  false,
        };
      } else if (typeof arguments[2] == "function"){
        a_options = {
          name: arguments[0],
          function: arguments[2],
          once: arguments[1],
          globalEvent: false,
        };
      } else if (typeof arguments[3] == "function"){
        a_options = {
          name: arguments[0],
          function: arguments[3],
          once: arguments[1],
          globalEvent: arguments[2],
        };
      } else {
        return;
      }
    }


    if (!(a_options.name in this._events))
      this._events[a_options.name] = [];

    var level = a_options.level ? a_options.level : 0;
    if (!this._events[a_options.name][level])
      this._events[a_options.name][level] = {};


    var senderId = a_options.sender ? this._applyEventObjectId(a_options.sender) : this._applyEventObjectId(a_options.object);
    if (!this._events[a_options.name][level][senderId])
      this._events[a_options.name][level][senderId] = {};

    var lintenerId = undefined;
    var lintenerItemId = undefined;
    var callinfo = {};
    if (a_options.function){
      lintenerId = this._applyEventObjectId(a_options.function);
      lintenerItemId = lintenerId;
      callinfo.function = a_options.function;
      callinfo.once = a_options.once;
      callinfo.globalEvent = a_options.globalEvent;
    } else {
      lintenerId = this._applyEventObjectId(a_options.object);
      var prefix = fcf.str(a_options.prefix);
      var suffix = a_options.suffix ? a_options.suffix : ("on" + a_options.name.charAt(0).toUpperCase() + a_options.name.slice(1));
      var method = prefix + suffix;
      lintenerItemId = lintenerId + "#" + method;
      callinfo.object      = a_options.object;
      callinfo.method      = method;
      callinfo.once        = a_options.once;
      callinfo.globalEvent = a_options.globalEvent;
    }

    if (!(lintenerId in this._events[a_options.name][level][senderId]))
      this._events[a_options.name][level][senderId][lintenerId] = {};

    this._events[a_options.name][level][senderId][lintenerId][lintenerItemId] = callinfo;
  }

  //this.detach = function(a_eventName, a_object, a_methodName)
  //this.detach = function(a_eventName, a_object)
  //this.detach = function(a_eventName, a_func)
  //this.detach = function(a_func)
  this.detach = function(a_eventName, a_funcOrObject, a_method){
    if (typeof a_eventName !== "string"){
      a_method = a_funcOrObject;
      a_funcOrObject = a_eventName;
      a_eventName = undefined;
    }

    if (a_eventName){
      if (!(a_eventName in this._events))
        return;
      for (var level = 0; level < this._events[a_eventName].length; ++level) {
        if (!this._events[eventName][level])
          continue;
        for (var senderId in this._events[a_eventName][level]) {
          var listenerId = this._applyEventObjectId(a_funcOrObject);
          if (!(listenerId in this._events[a_eventName][level][senderId]))
            continue;
          if (a_methodName)
            delete this._events[a_eventName][level][senderId][listenerId][a_methodName];
          else
            delete this._events[a_eventName][level][senderId];
        }
      }
    } else {
      for (var eventName in this._events) {
        for (var level = 0; level < this._events[eventName].length; ++level) {
          if (!this._events[eventName][level])
            continue;
          for (var senderId in this._events[eventName][level]) {
            var listenerId = this._applyEventObjectId(a_funcOrObject);
            if (!(listenerId in this._events[eventName][level][senderId]))
              continue;
            if (a_method)
              delete this._events[eventName][level][senderId][listenerId][a_method];
            else
              delete this._events[eventName][level][senderId];
          }
        }
      }
    }
  }

  this.emit = function(a_event, a_data) {
    return this.send(a_event, a_data);
  }

  this.send = function(a_event, a_data) {
    if (typeof a_event == "string"){
      a_event = new fcf.Event(a_event, a_data);
    } else {
      if (typeof a_data == "object")
        a_event = fcf.append(new fcf.Event(), a_event, a_data);
    }

    if (fcf.isServer())
      a_event.pid = process.pid;

    if (a_event.name in this._deferredEvents) {
      let deferredActions = fcf.actions();
      let deferredAct     = undefined;

      deferredActions.then((a_res, a_act)=>{ deferredAct = a_act; });
      this._deferredEvents[a_event.name].push({ event: a_event, deferredAct: deferredAct });
      return deferredActions;
    }

    var name = a_event.name;
    if (!this._events[name])
      return fcf.actions().then(()=>{ return a_event });

    var calls = [];
    for (var level = 0; level < this._events[name].length; ++level) {
      if (!this._events[name][level])
        continue;
      for (var senderId in this._events[name][level]) {
        for (var listenerId in this._events[name][level][senderId]) {
          var rm = [];
          for (var listenerItemId in this._events[name][level][senderId][listenerId]) {
            var callinfo = this._events[name][level][senderId][listenerId][listenerItemId];
            if (callinfo.function) {
              calls.push({
                cb: callinfo.function,
                object: undefined,
                info:   callinfo
              });
            } else if (callinfo.object) {
              if (callinfo.object[callinfo.method]){
                calls.push({
                  cb:     callinfo.object[callinfo.method],
                  object: callinfo.object,
                  info:   callinfo
                });
              }
            }
            if (callinfo.once)
              rm.push(listenerItemId);
          }
          for(var i = 0; i < rm.length; ++i)
            delete this._events[name][level][senderId][listenerId][rm[i]];
        }
      }
    }

    var actions = fcf.actions();
    for(var i = 0; i < calls.length; ++i){(function(i){
      var globalEvent = false;
      actions.then(function(){
        if (calls[i].info.globalEvent)
          globalEvent = true;
        return calls[i].cb.call(calls[i].object, a_event);
      })
      .then(function(){
        if (!globalEvent || !fcf.isServer()){
          return a_event;
        }

        if (a_event.prohibitionGlobal){
          return a_event;
        }

        var message = JSON.stringify({type: "event", data: a_event});

        fcf.actions()
        .asyncEach(self._processesEvent, function(a_key, a_info, a_res, a_subact){
          if (fcf.application.getConfiguration().controlPort == a_info.port && (a_info.host == "localhost" || a_info.host == "121.0.0.1")){
            a_subact.complete();
            return;
          }
          try {
            var client = new libNet.Socket();
            client.connect(a_info.port, a_info.host, function() {
              try {
                let mesasgeBuffer = Buffer.from(message, "utf8");
                let preffixBuffer = Buffer.from(mesasgeBuffer.length.toString()+"\n", "utf8");
                client.write(Buffer.concat([preffixBuffer, mesasgeBuffer]));
              } catch(e) {
                a_subact.complete();
              }
            })
            client.on("data", function(a_data){
              client.destroy();
              a_subact.complete();
            });
            client.on("error", function(){
              client.destroy();
              a_subact.complete();
            });
          } catch(e) {}
        });

        return a_event
      })
      .catch((e)=>{
        fcf.log.err("FCF", `Event error[${a_event.name}]: `, e);
      })
    })(i)}

    return actions;

  }

  this.setOwner = function(a_owner){
    this._owner = a_owner;
  }

  this.startupDeferredEvents = function(a_name){
    let self = this;
    let events = this._deferredEvents[a_name];


    delete this._deferredEvents[a_name];
    return fcf.actions()
    .each(events, (a_key, a_eventInfo)=>{
      return self.send(a_eventInfo.event)
      .then((a_event)=>{
        a_eventInfo.deferredAct.complete(a_event);
      })
    });
  }



  this._applyEventObjectId = function(a_obj){
    if (typeof a_obj != "object" && typeof a_obj != "function")
      return;

    if (!(this._getParamIdName() in a_obj))
      a_obj[this._getParamIdName()] = this._getId();
    return a_obj[this._getParamIdName()];
  }

  this.setProcessEnvironment = function(a_processesEvent){
    this._processesEvent = a_processesEvent;
  }

  this._getParamIdName = function(){
    return "_fcfeventchannelid";
  }

  this._getId = function() {
    return fcf.id();
  }

}


fcf.NDetails.modules = {};

fcf.NDetails.loadModule = (a_module, a_showError)=>{
  a_module = fcf.getPath(a_module);
  let state = fcf.NDetails.modules[a_module] ? fcf.NDetails.modules[a_module].state
                                             : undefined;
  if (state == "ok") {
    return fcf.actions()
    .then(()=> {
      return fcf.NDetails.modules[a_module].result;
    });
  } else if (state == "error") {
    return fcf.actions()
    .then((a_res, a_act)=> {
      a_act.error(fcf.NDetails.modules[a_module].error);
    });
  } else if (state == "wait" || state == "processing") {
    return fcf.actions()
    .then((a_res, a_act)=> {
      fcf.NDetails.modules[a_module].actions
      .then(()=>{
        a_act.complete(fcf.NDetails.modules[a_module].result);
      })
      .catch((a_error)=>{
        a_act.error(a_error);
      })
    });
  } else {
    fcf.NDetails.modules[a_module] = {
      state:    "wait",
      result:   undefined,
      error:    undefined,
      actions:  fcf.actions(),
      act:      undefined,
    };

    fcf.NDetails.eventChannel.send("watch_file", {file: a_module});

    return fcf.actions()
    .then((a_res, a_rootAct)=>{
      fcf.NDetails.modules[a_module].actions
      .then((a_res, a_act)=>{
        fcf.NDetails.modules[a_module].act = a_act;
        if (fcf.isServer()){
          let isCompleted = false;
          let originComplete = fcf.NDetails.modules[a_module].act.complete;
          fcf.NDetails.modules[a_module].act.complete = function(a_res){
            isCompleted = true;
            originComplete.call(this, a_res);
          }
          try {
            require(a_module);
          }catch(e) {
            fcf.NDetails.modules[a_module].act.error(e);
            if (e.toString().indexOf("SyntaxError") != -1)
              fcf.log.err("FCF", e);
            return;
          }
          if (!isCompleted)
            fcf.log.err("FCF", "The module name '" + a_module + "' is incorrectly specified in its declaration");
        } else {
          var script = document.createElement('script');
          script.onerror = ()=>{
            fcf.NDetails.modules[a_module].act.error(new Error("Failed load module '" + a_module + "'"));
          }
          let loadPath = (a_module[0] != "/" && a_module.indexOf(":") == -1) ? ":" + a_module : a_module;
          script.src = fcf.getPath(loadPath);
          document.head.appendChild(script);
        }
      })
      .then(()=>{
        a_rootAct.complete(fcf.NDetails.modules[a_module].result);
      })
      .catch((a_error)=>{
        if (a_showError)
          fcf.log.err("FCF", "Failed load module '" + a_module + "': ", a_error);
        a_rootAct.error(a_error);
      })
    });
  }
}


fcf.module = (a_options) => {
  let moduleName = fcf.getPath(a_options.name);
  let rootCall = false;

  let p1          = a_options.module.toString().indexOf("(");
  let p2          = p1 != -1 ? a_options.module.toString().indexOf(")") : -1;
  let dependenciesArgs  = [];
  if (p2 != -1) {
    fcf.each(a_options.module.toString().substring(p1+1, p2).split([","]), (a_key, a_value)=>{
      a_value = fcf.trim(a_value);
      if (!!a_value)
        dependenciesArgs.push(a_value);
    })
  }
  if (!fcf.NDetails.modules[moduleName]){
    fcf.NDetails.modules[moduleName] = {
      state:      "wait",
      result:     undefined,
      error:      undefined,
      actions:    fcf.actions(),
      act:        undefined,
    };
    rootCall = true;
    fcf.NDetails.eventChannel.send("watch_file", {file: moduleName});
  } else if (fcf.NDetails.modules[moduleName].state != "wait"){
    fcf.NDetails.modules[moduleName].dependencies     = Array.isArray(a_options.dependencies) ? a_options.dependencies : [];
    fcf.NDetails.modules[moduleName].dependenciesArgs = dependenciesArgs;
    fcf.NDetails.modules[moduleName].lazy             = Array.isArray(a_options.lazy) ? a_options.lazy : [];
    return;
  }

  fcf.NDetails.modules[moduleName].dependencies     = Array.isArray(a_options.dependencies) ? a_options.dependencies : [];
  fcf.NDetails.modules[moduleName].dependenciesArgs = dependenciesArgs;
  fcf.NDetails.modules[moduleName].lazy             = Array.isArray(a_options.lazy) ? a_options.lazy : [];




  let moduleInfo = fcf.NDetails.modules[moduleName];
  moduleInfo.state = "processing";

  fcf.each(a_options.dependencies, (a_key, a_value)=>{
    if (a_value[0] == ":")
      a_options.dependencies[a_key] = a_value.substr(1);
  });

  let actions = rootCall ? fcf.NDetails.modules[moduleName].actions
                         : fcf.actions();

  return actions
  .asyncEach(a_options.dependencies, (k, mod)=>{
    return fcf.NDetails.loadModule(mod, true);
  })
  .then(()=>{
    let modAct       = fcf.NDetails.modules[moduleName].act;
    let dependencies = [];
    fcf.each(a_options.dependencies, (k, mod)=>{
      dependencies.push(fcf.NDetails.modules[fcf.getPath(mod)].result);
    })
    moduleInfo.result = a_options.module.apply(undefined, dependencies);
    moduleInfo.state = "ok";
    if (moduleInfo.act){
      moduleInfo.act.complete();
    }
    fcf.actions()
    .asyncEach(a_options.lazy, (k, mod)=>{
      return fcf.NDetails.loadModule(mod, true);
    })
  })
  .catch((a_error)=>{
    moduleInfo.state = "error";
    moduleInfo.error = a_error;
    if (rootCall)
      fcf.log.err("FCF", "Failed load module '" + a_options.name + "': ", a_error);
    if (moduleInfo.act)
      moduleInfo.act.error(a_error);
  })
}

fcf.require = (a_modules, a_cb) => {
  a_modules = Array.isArray(a_modules) ? a_modules :
                            a_modules  ? [a_modules] :
                                         [];
  return fcf.actions()
  .asyncEach(a_modules, (k, mod)=>{
    return fcf.NDetails.loadModule(mod, true);
  })
  .then(()=>{
    let modules = [];
    fcf.each(a_modules, (k, mod) => {
      modules.push(fcf.NDetails.modules[fcf.getPath(mod)].result);
    });
    if (a_cb)
      a_cb.apply(undefined, modules);
    return modules;
  });
}

fcf.requireEx = (a_modules, a_options, a_cb) => {
  if (typeof a_options == "function"){
    a_cb = a_options;
    a_options = {};
  } else if (typeof a_options != "object"){
    a_options = {};
  }

  let modules = [undefined];
  return fcf.actions()
  .asyncEach(a_modules, (k, mod)=>{
    return fcf.NDetails.loadModule(mod, !!a_options.showError);
  })
  .then(()=>{
    fcf.each(a_modules, (k, mod) => {
      modules.push(fcf.NDetails.modules[fcf.getPath(mod)].result);
    });
    if (a_cb)
      return a_cb.apply(undefined, modules);
  })
  .then(()=>{
    return modules;
  })
  .catch((a_error)=>{
    if (a_cb)
      a_cb.apply(undefined, [a_error]);
  })
}


fcf.NDetails._filters = {};

fcf.loadFilters = (a_typesOrViews) => {
  if (fcf.isServer())
    return fcf.actions();

  let types = {};
  let conf  = fcf.application.getConfiguration().filters;
  let error = undefined;
  fcf.each(a_typesOrViews, (k, v)=> {
    let type = typeof v == "object" ? v.type : v;
    if (type in fcf.NDetails._filters)
      return;

    if (!(type in conf)){
      error = new fcf.Exception("ERROR_INCORRECT_DATA_TYPE", {type: type});
      return;
    }
    types[type] = conf[type];
  });

  if (fcf.empty(types))
    return fcf.actions();


  if (error){
    return fcf.actions()
    .then((a_res, a_act)=>{
      a_act.error(error);
    });
  }

  return fcf.actions()
  .asyncEach(types, (a_type, a_file, a_res, a_act)=>{
    fcf.requireEx([a_file], (a_error, a_module)=>{
      if (a_error){
        a_act.error(a_error);
      } else {
        fcf.NDetails._filters[a_type] = new a_module();
        a_act.complete();
      }
    })
  });
}

fcf.getFilter = function(a_typesOrViews){
  let type = typeof a_typesOrViews == "object" ? a_typesOrViews.type : a_typesOrViews;
  return fcf.NDetails._filters[type];
}


fcf.getDirectory = (a_path) => {
  if (a_path.indexOf("/") != -1) {
    let arr = a_path.split("/");
    arr.pop();
    a_path = arr.join("/");
  } else if (a_path.indexOf("\\") != -1){
    arr = a_path.split("\\");
    arr.pop();
    a_path = arr.join("\\");
  } else {
    a_path = "";
  }
  return a_path;
}

fcf.getExtension = (a_path) => {
  if (!a_path)
    return "";
  a_path = a_path.split("?")[0];
  var arr = a_path.split(".");
  return arr.length > 1 ? arr[arr.length-1] : "";
}

fcf.getShortFileName = (a_path) => {
  if (!a_path)
    return "";
  var offset = -1;
  for (var p = 0; p < a_path.length; ++p) {
    var c = a_path.charAt(p);
    if (c == ":" || c == "/" || c == "\\")
      offset = p;
  }

  let arr = a_path.substr(offset == -1 ? 0 : offset+1).split(".");
  if (arr.length > 1)
    arr.pop();
  return arr.join(".");
}

fcf.getFileName = (a_path) => {
  a_path = fcf.rtrim(a_path, "/");
  if (!a_path)
    return "";
  var offset = 0;
  for (var p = 0; p < a_path.length; ++p) {
    var c = a_path.charAt(p);
    if (c == ":" || c == "/" || c == "\\")
      offset = p;
  }

  if (offset)
    offset += 1;

  return a_path.substr(offset);
}

//fcf.getPath = function(a_modURI, a_innerServerPath);
fcf.getPath = (a_modURI, a_aliases, a_innerServerPath) => {
  if (typeof a_modURI !== "string" )
    return a_modURI;
  return fcf.replaceAll(fcf._getPath(a_modURI, a_aliases, a_innerServerPath), "/./", "/");
}

fcf._getPath = (a_modURI, a_aliases, a_innerServerPath) => {
  if (typeof a_aliases !== "object") {
    a_innerServerPath = a_aliases;
    a_aliases = undefined;
  }

  if (!a_aliases && fcf.application && fcf.application.isAvailable()) {
    var theme = fcf.application.getTheme(undefined, true);
    if (theme)
      a_aliases = theme.getAliases();
  }

  if (fcf.empty(a_aliases) && fcf.application && fcf.application.getConfiguration()){
    a_aliases = fcf.application.getConfiguration().aliases;
  }


  if (a_innerServerPath === undefined)
    a_innerServerPath = fcf.isServer();

  if (!a_innerServerPath){
    if (a_modURI.charAt(0) != "@" && a_modURI.indexOf(":") == -1)
      return a_modURI;
    if (a_modURI.indexOf("://") != -1)
      return a_modURI;
    if (a_modURI.charAt(0) == ":")
      return "/" + a_modURI.substr(1);
  } else {
    if (a_modURI.charAt(0) == ":"){
      return process.cwd() + "/" + a_modURI.substr(1);
    }
  }


  if (a_modURI.charAt(0) == "@") {
    var slashPos = a_modURI.indexOf("/");
    var prefix   = slashPos != -1 ? a_modURI.substr(0, slashPos) : undefined;
    var suffix   = slashPos != -1 ? a_modURI.substr(slashPos) : undefined;
    if (prefix){
      a_modURI = prefix;
    }

    var urlData = fcf.parseUrl(a_modURI);
    var key = urlData.referer.substr(1);
    if (a_aliases && a_aliases[key]){
      a_modURI = fcf.buildUrl(a_aliases[key], urlData.urlArgs);
    }

    if (suffix){
      a_modURI += suffix;
    }
  }

  // if the local path
  if (a_innerServerPath) {
    if (a_modURI.indexOf("/") == 0 || a_modURI.indexOf(":\\") == 0 || a_modURI.indexOf("://") != -1 || a_modURI.indexOf(":\\") != -1) {
      return a_modURI;
    }
  } else {
    if (a_modURI.indexOf("/") == 0 || a_modURI.indexOf(":\\") == 0 || a_modURI.indexOf("://") != -1) {
      return a_modURI;
    }
  }

  var libraries = fcf.settings.libraries ? fcf.settings.libraries : {};
  var modItems = a_modURI.split(":");
  var libName = modItems.length > 1 ? modItems[0] : "";
  var modName = modItems.length > 1 ? modItems[1] : modItems[0];

  var root = a_innerServerPath ? fcf.settings.innerRoot : fcf.settings.outerRoot;

  var libRPath = libName;
  if (a_innerServerPath && fcf.settings.libraries && libName && (libName in fcf.settings.libraries))
    libRPath = a_innerServerPath ? fcf.settings.libraries[libName].path : "fcfpackages/" + libName;
  else if (!a_innerServerPath && a_modURI[0] !== ":" && a_modURI.indexOf(":") !== -1)
    libRPath = "fcfpackages/" + libName;

  if (a_innerServerPath && libRPath == "")
    libRPath = ".";

  var absoluteLibPath = (libRPath.indexOf("/") == 0 || libRPath.indexOf(":\\") != -1);

  var libPath = !absoluteLibPath ? fcf.NPath.concat(root, libRPath) : libRPath;
  var modPath = fcf.NPath.concat(libPath, fcf.ltrim(modName, "/"));

  return modPath;
}

fcf.registerPackagePath = (a_name, a_dirPath, a_rootUrl) => {
  fcf.settings.libraries[a_name] = { url: a_rootUrl, path: a_dirPath };
}


fcf.prepareObject(fcf, "NFSQL");
fcf.prepareObject(fcf, "NDetails");
fcf.prepareObject(fcf, "NDetails.renderInstructions");


fcf.Message = function(a_name, a_message, a_args, a_subMessage) {
  if (a_message === undefined || a_message === null) {
    a_message = "";
  } else if (typeof a_message === "object"){
    a_subMessage = a_args;
    a_args = a_message;
    a_message = "";
  }
  if (fcf.empty(a_message))
    a_message = a_name;
  if (typeof arguments[0] === 'string') {
    this.directionalСall = false;
    this.name = a_name;
    this.subEvent = a_subMessage;
    this._templateMessage = a_message;
    if (Array.isArray(a_args)) {
      for(var i = 0; i < a_args.length; ++i)
        this[i+1] = a_args[i];
      this.length = a_args.length;
    } else {
      fcf.append(this, a_args);
    }
  } else {
    fcf.append(this, arguments[0]);
    delete this.message;
  }


  this.toString = function(a_enableDefaultLanguage) {
    let lang    = a_enableDefaultLanguage && fcf.isServer() && fcf.application.getSystemVariable("fcf", "defaultLanguage") ? fcf.application.getSystemVariable("fcf", "defaultLanguage") : undefined;
    let message = fcf.t(this._templateMessage, lang);
    message = fcf.tokenize(message, this);

    if (this.subEvent) {
      var subText = "\n" + this.subEvent.toString();
      if (this.subEvent.stack)
        subText += "\nSub stack: " + this.subEvent.stack;
      fcf.replaceAll(subText, "\n", "\n    ");
      message += subText;
    }

    this.message = message;

    return message;
  }

  this.message = this.toString();
}

fcf.NDetails.messages = {};
fcf.addException = function(a_messageName, a_messageText) {
  fcf.NDetails.messages[a_messageName] = a_messageText;
}

fcf.Exception = function(a_messageName, a_args, a_subEvent) {
  let stackTxt = undefined;
  if (a_messageName instanceof Error){
    stackTxt = a_messageName.stack || a_messageName.stacktrace;
    a_args = {error: a_messageName ? a_messageName.toString() : "" };
    a_messageName = "ERROR";
  }
  var messageTemplate = fcf.NDetails.messages[a_messageName];
  fcf.Message.call(this, a_messageName, messageTemplate, a_args, a_subEvent);
  if (!stackTxt)
    stackTxt = typeof console === "object" &&  console.trace === "function" ? console.trace() : (new Error()).stack
  if (stackTxt === undefined) {
    try {
      throw new Error();
    } catch(e) {
      stackTxt = e.stack;
    }
  }
  this.stackArr = fcf.Exception.parseStack(stackTxt, 1);
}

fcf.Exception.stackToString = function(a_stackArr){
  let result = "";
  for(let i = 0; i < a_stackArr.length; ++i){
    result += a_stackArr[i].file + ": " +
              a_stackArr[i].function + " [" +
              a_stackArr[i].line + ":" + a_stackArr[i].column + "]" +
              "\n";
  }
  return result;
}


fcf.Exception.is = function(a_exception, a_name) {
  return typeof a_exception === "object" && a_exception !== null && a_exception.name && a_exception.name == a_name;
}

fcf.Exception.parseStack = function(a_stackTxt, a_startLevel) {
  if (a_startLevel === undefined)
    a_startLevel = 0;
  var result = [];
  if (!a_stackTxt)
    return result;
  var stackArr = a_stackTxt.split("\n");
  for(var i = a_startLevel + 1; i < stackArr.length; ++i) {
    var level = {};
    var endPosFunc = stackArr[i].indexOf("(");
    var posInfoArr = stackArr[i].substr(endPosFunc+1, stackArr[i].length - endPosFunc - 2).split(":");
    level.function = stackArr[i].substr(7, endPosFunc-8);
    level.file = posInfoArr[0];
    if (level.file.indexOf("at ") != -1)
      level.file = level.file.split("at ")[1];
    level.line = posInfoArr[posInfoArr.length - 2];
    level.column = posInfoArr[posInfoArr.length - 1];
    result.push(level);
  }
  return result;
}


fcf.Event = function(a_name, a_data){
  this.name = a_name;
  if (typeof a_data == "object")
    fcf.append(this, a_data);
}



fcf.addException("ERROR_ACCESS",                                    "Access denied to ${{resource}}$");
fcf.addException("ERROR_404",                                       "Page not found ${{address}}$");
fcf.addException("OPERATION_NOT_SUPPORTED",                         "Operation not supported");
fcf.addException("ERROR_TEST_OPEN_DIRECTORY",                       "Unable to open the '${{directory}}$' directory for reading");
fcf.addException("ERROR_TEST_FAILED_EQUAL",                         "The value '${{1}}$' is not equal to '${{2}}$'");
fcf.addException("ERROR_TEST_FAILED_NOT_EQUAL",                     "The value '${{1}}$' must not be equal to '${{2}}$'");
fcf.addException("ERROR_TEST_FAILED_EQUAL_OBJECT",                  "The value '${{1}}$' is not equal to '${{2}}$ ' in the '${{3}}$' object");
fcf.addException("ERROR_TEST_FAILED_EQUAL_OBJECT_ITEM_NOT_FOUND",   "The '${{element}}$' element is missing from the '${{object}}$' object");
fcf.addException("ERROR_TEST_FAILED_EQUAL_OBJECT_ARG2_NOT_OBJECT",  "Argument 2 is not an object in the object '${{1}}$'");
fcf.addException("ERROR_TEST_FAILED_EQUAL_OBJECT_ARG2_NOT_ARRAY",   "Argument 2 is not an array in the object  '${{1}}$'");
fcf.addException("ERROR_TEST_FAILED_EQUAL_OBJECT_LENGTH_ARRAY",     "The array size of the first array (${{1}}$) is not equal to the size of the second (${{2}}$) in the '${{3}}$' object");

fcf.addException("ERROR_EXECUTE_PROCESS",                           "Process execution failed \"@{{command}}@\": @{{error}}@\n STDERR: @{{stderr}}@");

fcf.addException("ERROR_THEME_NOT_FOUND",                           "Failed to load @{{theme}}@ theme");
fcf.addException("FAILD_FORM_INPUT",                                "The form was filled out incorrectly");

fcf.addException("ERROR",                                           "${{error}}$"); // + stack
fcf.addException("ERROR_FILE_NOT_FOUND",                            "File ${{file}}$ not found");
fcf.addException("ERROR_FORM_INPUT",                                "Incorrectly filled out form"); // Исключение должно содержать массив errors
                                                                                                       // new fcf.Exception("ERROR_FORM_INPUT", {errors: errors})
fcf.addException("ERROR_READ_FILE",                                 "Failed to read the file ${{1}}$ (${{2}}$)");
fcf.addException("ERROR_READ_NOT_FILE",                             "Failed to read the file ${{1}}$ (The specified path is not a file)");
fcf.addException("ERROR_READ_FORMAT_FILE",                          "Invalid file format ${{file}}$");
fcf.addException("ERROR_INCORRECT_FORMAT_HANDLER",                  "Invalid format of the request handler ${{file}}$");
fcf.addException("ERROR_REQUEST_PARAMETER_NOT_SET",                 "The request parameter ${{1}}$ is not set");
fcf.addException("ERROR_REQUEST_PARAMETER_NOT_VALID",               "Invalid request parameter format ${{1}}$");
fcf.addException("ERROR_ASYNC_ACTIONS_CLOSED",                      "Adding a method to a completed execution queue");
fcf.addException("ERROR_EVAL_TEMPL",                                "Execution error in the template ${{1}}$[${{2}}$:${{3}}$]");
fcf.addException("ERROR_EVAL_SCRIPT",                               "Execution error in the file ${{1}}$[${{2}}$]");
fcf.addException("ERROR_INCORRECT_TEMPL_REQUEST_WRAPPER",           "Wrapper request to an unavailable template ${{template}}$");
fcf.addException("ERROR_NOSET_GET_ARG",                             "The required GET request parameter '${{arg}}$' was omitted");
fcf.addException("ERROR_INCORRECT_DATA_TYPE",                       "Invalid data type '${{type}}$'");
fcf.addException("ERROR_NOT_LOADED_FILTER",                         "Data filter not loaded for \"${{type}}$\" type use fcf.loadFilter() method");

fcf.addException("ERROR_HTTP_REQUEST_ERROR",                        "HTTP request failed ${{1}}$");
fcf.addException("ERROR_HTTP_REQUEST_OBJECT_FORMAT",                "The server returned a response not in JSON format (${{data}}$)");

fcf.addException("ERROR_HTTP_ARG_NOT_SET",                          "The '${{arg}}$' argument in the request to the server is not set");

fcf.addException("ERROR_REDNER_LOOP",                               "Render looping error on '@{{template}}@' template");
fcf.addException("ERROR_MAX_REQURSION_RENDER",                      "Exceeded the maximum number of nested templates when rendering a template @{{template}}@");



fcf.addException("ERROR_TEMPLATE_VIEWS_NOT_SET_DATA_FILED",         "The views data field in the ${{template}}$ template is not set");
fcf.addException("ERROR_UNKNOWN_PROJECTION",                        "An unknown '${{projection}}$' projection is requested");
fcf.addException("ERROR_INVALID_PROJECTION_FORMAT",                 "Invalid projection format '${{projection}}$'");
fcf.addException("ERROR_UNKNOWN_FIELD_TYPE",                        "An unknown '${{param}}$' type in '${{projection}}$' projection");
fcf.addException("ERROR_INCORRECT_PROJECTION_PARAM_NOT_SET",        "The '${{param}}$' parameter for the '${{projection}}$' projection is not set");
fcf.addException("ERROR_UNKNOWN_PROJECTION_IN_JOIN",                "Projection '${{joinProjection}}$' was not found in the join block for projection '${{projection}}$'");
fcf.addException("ERROR_PROJECTION_UNSET_WHERE",                    "The where block is not set in the '${{projection}}$' projection");
fcf.addException("ERROR_PROJECTION_UNSET_ON",                       "The on block is not set in the '${{projection}}$' projection");

fcf.addException("ERROR_PROJECTION_UNSET_ALIAS",                    "The 'alias' property of field in the '${{projection}}$' projection is not set");
fcf.addException("ERROR_PROJECTION_UNSET_TYPE",                     "The 'type' property of field in the '${{projection}}$' projection is not set");
fcf.addException("ERROR_SAFE_EVAL_ERROR_COMMAND",                   "safeEval() does not allow 'while', 'for', '=>', 'function', 'class' constructs");
fcf.addException("ERROR_SAFE_EVAL_ERROR_LENGTH",                    "the maximum length of safeEval must not exceed ${{length}}$");

fcf.addException("ACCESS_SELECT_PROJECTION",                        "Access to the selection of data from the \"${{projection}}$\" projection is prohibited");
fcf.addException("ACCESS_UPDATE_PROJECTION",                        "Access to the update of data from the \"${{projection}}$\" projection is prohibited");
fcf.addException("ACCESS_INSERT_PROJECTION",                        "Access to the insert of data from the \"${{projection}}$\" projection is prohibited");
fcf.addException("ACCESS_DELETE_PROJECTION",                        "Access to the delete of data from the \"${{projection}}$\" projection is prohibited");

fcf.addException("FCF_LOGIN_NOT_SET_USER",             "Is not specified the user");
fcf.addException("FCF_LOGIN_NOT_SET_PASSWORD",         "Is not specified, the password");
fcf.addException("FCF_LOGIN_INVALID_USER_OR_PASSWORD", "Invalid username or password");
fcf.addException("FCF_LOGIN_INVALID_USER_BLOCKED",     "The user is blocked");
fcf.addException("FCF_LOGIN_INVALID_USER_NOT_CREATED", "The registration procedure was not completed for the user");
fcf.addException("FCF_LOGIN_TIMEOUT",                  "Please try to authorize again in ${{minutes}}$ minutes ${{seconds}}$ seconds");


fcf.NDetails.evalFunctions = {};

fcf.NDetails.safeEvalResult_findKeyWord = (a_code, a_word)=>{
  let an = "a".charCodeAt(0);
  let zn = "z".charCodeAt(0);
  let An = "A".charCodeAt(0);
  let Zn = "Z".charCodeAt(0);
  let zeron = "0".charCodeAt(0);
  let ninen = "9".charCodeAt(0);
  let _n = "_".charCodeAt(0);
  let pos = a_code.indexOf(a_word);
  if (pos == -1)
    return false;
  let bc  = a_code[pos-1] ? a_code[pos-1].charCodeAt(0) : 0;
  let nc  = a_code[pos+a_word.length] ? a_code[pos+a_word.length].charCodeAt(0) : 0;
  let valid = (bc>=an && bc<=zn) || (bc>=An && bc<=Zn) || (bc==_n) ||
              (nc>=an && nc<=zn) || (nc>=An && nc<=Zn) || (nc>=zeron && nc<=ninen) || (nc==_n);
  return !valid;
}

fcf.safeEvalResult = (a_evalCode, a_evalEnvironment) => {
  let context = fcf.getContext();
  let safeEnv = context && context.get("safeEnv") ? context.get("safeEnv") : {};
  let result = undefined;
  let func   = fcf.NDetails.evalFunctions[a_evalCode];

  if (!func){
    let foundErrorCommand = false;
    foundErrorCommand |= fcf.NDetails.safeEvalResult_findKeyWord(a_evalCode, "constructor");
    foundErrorCommand |= fcf.NDetails.safeEvalResult_findKeyWord(a_evalCode, "while");
    foundErrorCommand |= fcf.NDetails.safeEvalResult_findKeyWord(a_evalCode, "for");
    foundErrorCommand |= fcf.NDetails.safeEvalResult_findKeyWord(a_evalCode, "function");
    foundErrorCommand |= fcf.NDetails.safeEvalResult_findKeyWord(a_evalCode, "prototype");
    foundErrorCommand |= fcf.NDetails.safeEvalResult_findKeyWord(a_evalCode, "__proto__");
    foundErrorCommand |= a_evalCode.indexOf("__defineGetter__") != -1;
    foundErrorCommand |= a_evalCode.indexOf("__defineSetter__") != -1;
    foundErrorCommand |= a_evalCode.indexOf("=>") != -1;
    foundErrorCommand |= fcf.NDetails.safeEvalResult_findKeyWord(a_evalCode, "class");
    foundErrorCommand |= fcf.NDetails.safeEvalResult_findKeyWord(a_evalCode, "goto");
    foundErrorCommand |= fcf.NDetails.safeEvalResult_findKeyWord(a_evalCode, "delete");
    foundErrorCommand |= a_evalCode.indexOf("\\") != -1;
    foundErrorCommand |= a_evalCode.indexOf("`") != -1;
    foundErrorCommand |= a_evalCode.indexOf("++") != -1;
    foundErrorCommand |= a_evalCode.indexOf("--") != -1;

    if (!foundErrorCommand) {
      let lc    = undefined;
      let quote = undefined;
      for(let i = 0; i < a_evalCode.length; ++i){
        let c = a_evalCode[i];
        if (c == "'" || c == "\""){
          if (quote)
            quote = undefined;
          else
            quote = c;
          continue;
        }

        if (!quote){
          if (a_evalCode[i-1] == "<" && c == "=")
            continue;
          if (a_evalCode[i-1] == ">" && c == "=")
            continue;
          if (a_evalCode[i-1] == "!" && c == "=")
            continue;
          if (a_evalCode[i-1] != "=" && c == "=" && a_evalCode[i+1] != "="){
            foundErrorCommand = true;
            break;
          }
        }
      }
    }


    if (foundErrorCommand)
      throw new fcf.Exception("ERROR_SAFE_EVAL_ERROR_COMMAND");
    if (a_evalCode.length > 512)
      throw new fcf.Exception("ERROR_SAFE_EVAL_ERROR_LENGTH", {length: 512});

    func = eval("(function fcfsafeEvalResult79323(a_env, a_args){ with(a_env) { with(a_args) { var result = undefined; result = " + a_evalCode + "; return result; } }; })");
    fcf.NDetails.evalFunctions[a_evalCode] = func;
  }

  result = func(safeEnv, a_evalEnvironment);
  return result;
}

class FCFScriptExecutor {
  constructor(){
    this._funcStorage = {};
    this._dataFuncStorage = {};
  }

  execute(a_code, a_args, a_file, a_startLine, a_startChar, a_async){
    let self = this;
    let error = undefined;

    if (a_file)
      a_file = fcf.getPath(a_file);

    let result = undefined;

    return this._execute(
      a_code,
      a_args,
      a_file,
      a_startLine,
      function(a_code, a_args){
        let func = undefined;
        let code = undefined;
        try {
          if (self._funcStorage[a_file] && self._funcStorage[a_file][a_startLine] && self._funcStorage[a_file][a_startLine][a_startChar]){
            func = self._funcStorage[a_file][a_startLine][a_startChar].func;
            code = self._funcStorage[a_file][a_startLine][a_startChar].code;
          } else {
            code = "(" + (a_async ? "async" : "") + " function fcfinnerevalfunction(a_args){";
            for(let key in a_args)
              code += `var ${key} = a_args.${key};`;
            code += a_code;
            code += "})";
            func = eval(code);
            if (!self._funcStorage[a_file])
              self._funcStorage[a_file] = {};
            if (!self._funcStorage[a_file][a_startLine])
              self._funcStorage[a_file][a_startLine] = {};
            self._funcStorage[a_file][a_startLine][a_startChar] = {func: func, code: code};
          }
          if (a_async){
            result = fcf.actions();
            result.then(()=>{ return func(a_args); });
          } else {
            result = func(a_args);
          }

        } catch(e) {
          error = e;
        }
      self = undefined;
      return {
        code: code,
        error: error,
        result: result,
        };
      }
    );
  }

  parse(a_code, a_args, a_file, a_startLine, a_startChar){
    let self = this;
    let error = undefined;

    if (a_file)
      a_file = fcf.getPath(a_file);

    return this._execute(
      a_code,
      a_args,
      a_file,
      a_startLine,
      function(a_code, a_args){
        let func = undefined;
        let code = undefined;
        let result = undefined;
        try {
          if (self._funcStorage[a_file] && self._funcStorage[a_file][a_startLine] && self._funcStorage[a_file][a_startLine][a_startChar]){
            func = self._funcStorage[a_file][a_startLine][a_startChar].func;
            code = self._funcStorage[a_file][a_startLine][a_startChar].code;
          } else {
            code = "(function fcfinnerevalfunction(a_args){";
            for(let key in a_args)
              code += "var "+ key + " = a_args." + key + ";";
            code += "var result = (" + a_code + "); return result; })";
            func = eval(code);
            if (!self._funcStorage[a_file])
              self._funcStorage[a_file] = {};
            if (!self._funcStorage[a_file][a_startLine])
              self._funcStorage[a_file][a_startLine] = {};
            self._funcStorage[a_file][a_startLine][a_startChar] = {func: func, code: code};
          }
          result = func(a_args);
        } catch(e) {
          error = e;
        }
      self = undefined;
      return {
        code: code,
        error: error,
        result: result,
        };
      }
    );
  }

  clear(a_fileName){
    a_fileName = fcf.getPath(a_fileName);
    delete this._funcStorage[a_fileName];
  }

  _execute(a_code, a_args, a_file, a_startLine, a_handler){
    let self = this;

    let handlerResult = a_handler(a_code, a_args);
    if (handlerResult.result instanceof Promise || handlerResult.result instanceof fcf.Actions) {
      let rs = handlerResult.result;
      handlerResult.result = fcf.actions()
      .then((a_res, a_act)=>{
        fcf.actions()
        .then(()=>{
          return rs;
        })
        .then((a_res)=>{
          a_act.complete(a_res);
        })
        .catch((a_error)=>{
          try {
            self._error(a_code, a_error, a_file, a_startLine);
          } catch (error){
            a_act.error(error);
          }
        })
      });
    } else {
      if (handlerResult.error)
        this._error(a_code, handlerResult.error, a_file, a_startLine);
    }
    return handlerResult.result;
  }

  _error(a_code, a_error, a_file, a_startLine){
    a_startLine = a_startLine ? a_startLine : 0;
    let stack = a_error.stackArr ?    a_error.stackArr :
                a_error.stack ?       fcf.Exception.parseStack(a_error.stack) :
                a_error.stacktrace ?  fcf.Exception.parseStack(a_error.stacktrace) :
                                      [];
    let line = undefined;
    for(let i = 0; i < stack.length; ++i){
      if(stack[i].file.indexOf("eval at") == 0 || stack[i].file.indexOf("fcfEvalTmpl7652492") == 0 || stack[i].function == "fcfinnerevalfunction"){
        line = parseInt(stack[i].line) + a_startLine;
        break;
      }
    }

    if (fcf.isServer() && line === undefined){
      let babel;
      fcf.require(["fcf:NSystem/babel.js"]).then((a_modules)=>{ babel = a_modules[0] });
      try {
        babel.transformSync(a_code);
      } catch(e){
        let match = e.toString().match(/\(([0-9]*)/)
        if (match)
          line = parseInt(match[1]) + a_startLine;
        line = !line && !a_startLine ? 0 :
               !line                 ? a_startLine :
                                       line;
        throw new fcf.Exception("ERROR_EVAL_SCRIPT", [a_file, line], a_error);
      }
    }

    line = !line && !a_startLine ? 0 :
           !line                 ? a_startLine :
                                   line;
    throw new fcf.Exception("ERROR_EVAL_SCRIPT", [a_file, line], a_error);
  }
};
fcf.scriptExecutor = new FCFScriptExecutor();

fcf.NDetails.downloadStorage = {};
fcf.load = (a_options) => {
  if (typeof a_options == "string")
    a_options = {path: a_options};

  let clError = (a_error, a_data) => {
    if (a_data) {
      var subErrorContent = undefined;
      try {
        var obj = JSON.parse(a_data);
        if (obj.error){
          a_error.subEvent = fcf.append(new fcf.Exception("ERROR"), obj.error);
          a_error.toString();
        }
      } catch(e){
        a_error.subEvent = new fcf.Exception("ERROR", {error: fcf.stripTags(a_data)});
        a_error.toString();
      }
    }

    if (a_options.onResult)
      a_options.onResult(a_error, a_data);
  }

  return fcf.actions()
  .then((a_res, a_act) => {
    var path = fcf.getPath(a_options.path, a_options.aliases);
    var resData = undefined;
    if (fcf.isServer()) {
      path = path.split("+")[0];
      let context = fcf.getContext();
      libFS.readFile(path, 'utf8', function (a_error, a_data) {
        resData = a_data;
        if (a_error) {
          var error = new fcf.Exception("ERROR_READ_FILE", [a_options.path, a_error]);
          clError(error);
          a_act.error(error);
        } else {
          if (a_options.onResult)
            a_options.onResult(undefined, resData);
          a_act.complete(resData);
        }
      });
    } else {
      var extension = fcf.getExtension(a_options.path);
      if (extension == "js" || extension == "tmpl"){
        if (fcf.NDetails.downloadStorage[a_options.path]){
          if (a_options.onResult)
            a_options.onResult(undefined, fcf.NDetails.downloadStorage[a_options.path]);
          a_act.complete(fcf.NDetails.downloadStorage[a_options.path]);
          return;
        }
      }

      var xmlHttp = new XMLHttpRequest();
      xmlHttp.onreadystatechange = () => {
        if (xmlHttp.readyState == 4) {
          if (xmlHttp.status == 200) {
            resData = xmlHttp.responseText;

            var extension = fcf.getExtension(a_options.path);
            if (extension == "js" || extension == "tmpl"){
              fcf.NDetails.downloadStorage[a_options.path] = resData;
            }
            if (a_options.onResult)
              a_options.onResult(undefined, resData);
            a_act.complete(resData);
          } else {
            resData = xmlHttp.responseText;

            let data = fcf.strToObject(resData);
            let error = undefined;

            if (typeof data === "object"){
              if (data._templateMessage) {
                error = new fcf.Exception();
                error.message = undefined;
                error.toString();
                fcf.append(error, data);
              } else if (data.message) {
                error = new fcf.Exception("ERROR", {error: data.message, stack: data.stack});
                fcf.append(error, data);
              }
            }
            if (error === undefined){
              error = new fcf.Exception("ERROR_HTTP_REQUEST_ERROR", [xmlHttp.status]);
            }

            clError(error, resData);
            a_act.error(error);
          }
        }
      }

      if (!fcf.empty(a_options.get))
        path = fcf.buildUrl(path, a_options.get);

      var async = a_options.async !== undefined ? a_options.async : true;
      xmlHttp.open("POST", path, async);

      let isSendContext = a_options.path[0] == "@" || a_options.path[0] == "/" ||  a_options.path.indexOf(window.location.hostname+":") == 0;
      if (isSendContext){
        let ctxt = fcf.append({}, fcf.getContext());
        delete ctxt.route;
        delete ctxt.args;
        delete ctxt.safeEnv;
        xmlHttp.setRequestHeader("FCF-Context", fcf.base64Encode(JSON.stringify(ctxt)));
      }

      if (!fcf.empty(a_options.post)){
        if (a_options.post instanceof FormData){
          xmlHttp.send(a_options.post);
        } else {
          xmlHttp.setRequestHeader("Content-Type", "application/json");
          xmlHttp.send(JSON.stringify(a_options.post));
        }
      } else {
        xmlHttp.send(null);
      }
    }
  });
}

fcf.loadObject = (a_options) => {
  return fcf.actions()
  .then((a_res, a_act)=> {
    fcf.load({
      path: a_options.path,
      aliases: a_options.aliases,
      post: a_options.post,
      get: a_options.get,
      async: a_options.async,
      onResult: (a_error, a_data) => {
        a_data = fcf.strToObject(a_data);

        if (typeof a_data !== "object") {
          a_error = new fcf.Exception("ERROR_HTTP_REQUEST_OBJECT_FORMAT", {data: a_data});
        }

        if (a_options.onResult)
          a_options.onResult(a_error, a_data);

        if (fcf.empty(a_error))
          a_act.complete(a_data)
        else
          a_act.error(a_error);
      }
    });
  });
}



fcf.parseUrl = (a_url)=> {
  var result = {};
  var sep = a_url.indexOf('?');

  anchorArr = a_url.split("#");
  a_url = anchorArr[0];
  result.anchor = anchorArr[1];

  if (sep === -1) {
    result.url   = a_url;
    result.referer   = a_url;
    result.urlArgs = {};
  } else {
    var rawQuery = a_url.substr(sep+1);
    result.referer = a_url.substr(0, sep);
    result.url = a_url;
    result.urlArgs = {};
    var arr = fcf.str(rawQuery).split('&');
    for(var i = 0; i < arr.length; ++i) {
      var val = arr[i].split('=');
      result.urlArgs[decodeURIComponent(fcf.str(val[0]))] = decodeURIComponent(fcf.str(val[1]));
    }
  }

  if (result.referer[0] == '/') {
    result.uri = result.referer.substr(1);
    result.server = '';
    result.protocol = '';
  } else {
    var pos = result.referer.indexOf('://');
    if (pos !== -1) {
      var pos2 = result.referer.indexOf('/', pos+3);
      if (pos2 === -1) {
        result.uri = '/';
        result.server = result.referer.substr(pos+3);
        result.protocol = result.referer.substr(0, pos);
      } else {
        result.uri = result.referer.substr(pos2);
        result.server = result.referer.substr(pos+3, pos2 - (pos+3));
        result.protocol = result.referer.substr(0, pos);
      }
    } else {
      result.server = '';
      result.protocol = '';
      result.uri = result.referer;
    }
  }

  result.args = fcf.append({}, result.urlArgs);

  let serverArr = result.server.split(":");
  result.server = serverArr[0];
  result.port   = serverArr[1];

  return result;
}

fcf.buildUrl = (a_url, a_args, a_anchor) => {
  var urlInfo = fcf.parseUrl(a_url);
  a_url = urlInfo.referer;
  a_args = fcf.append({}, urlInfo.args, a_args);

  var first = true;
  for(var k in a_args) {
    if (first)
      a_url += a_url.indexOf('?') != -1 ? '&' : '?';
    else
      a_url += '&';

    a_url += k;
    a_url += '=';

    if (typeof a_args[k] != 'object') {
      a_url += encodeURIComponent(fcf.str(a_args[k]));
    } else {
      a_url += encodeURIComponent(JSON.stringify(a_args[k]));
    }

    first = false;
  }

  if (!fcf.empty(a_anchor))
    a_url += "#" + a_anchor;
  else if (!fcf.empty(urlInfo.anchor))
    a_url += "#" + urlInfo.anchor;

  return a_url;
}

fcf.link = (a_options, a_args) => {
  a_options = typeof a_options === "string" ? {url: a_options, a_args: typeof a_args == "object" ? a_args: {} } :
              typeof a_options === "object" ? a_options :
                                              {};

  let aliases = typeof a_options.aliases === "object" ? a_options.aliases : {};
  let url = fcf.getPath(fcf.str(a_options.url), aliases, false);
  let urlInfo = fcf.parseUrl(url);
  let args = fcf.append({}, urlInfo.urlArgs);
  if (a_options.args)
    fcf.append(args, a_options.args);
  if (a_options.urlArgs)
   fcf.append(args, a_options.urlArgs);

  return fcf.buildUrl(urlInfo.referer, args, urlInfo.anchor);
}

fcf.Actions = class FcfActions {
  constructor(a_options){
    this._deferred = a_options && !!a_options.deferred;
    this._stack    = [];
    this._context  = a_options && a_options.context ? a_options.context : fcf.getContext();
    this._error    = undefined;
    this._errorcbs = [];
    this._run      = false;
    this.result    = undefined;
  }

  end(){
    let self = this;
    this.finally(()=>{
      setTimeout(()=>{
        if (self._stack)
          for(let key in self)
            delete self[key];
      },1);
    })
  }

  then(a_cb, a_cberror){
    if (typeof Promise !== "undefined" && a_cb instanceof Promise){
      let self = this;
      a_cb.catch((e)=>{
        self.error(e);
      });
      this.then(()=>{
        return a_cb;
      })
    } else if (a_cb instanceof fcf.Actions){
      this.then(()=>{
        return a_cb;
      })
    } else {
      if (a_cb) {
        this._stack.push({cb: a_cb, args: undefined, autoComplete: fcf.getParamCount(a_cb) < 2 });
        this._execute();
      }
    }

    if (a_cberror)
      this.catch(a_cberror);
    return this;
  }

  each(a_obj, a_cb){
    if (typeof a_obj != "function"){
      if (typeof a_obj == "object") {
        if (fcf.isEnumerable(a_obj)) {
          for(let k = 0; k < a_obj.length; ++k)
            this._stack.push({cb: a_cb, args: [k, a_obj[k]], autoComplete: fcf.getParamCount(a_cb) < 4});
        } else {
          for(let k in a_obj)
            this._stack.push({cb: a_cb, args: [k, a_obj[k]], autoComplete: fcf.getParamCount(a_cb) < 4});
        }
      }
      this._execute();
    } else {
      this.then((a_res)=>{
        a_obj = a_obj();
        if (fcf.isEnumerable(a_obj)) {
          for(let i = a_obj.length-1; i >= 0; --i)
            this._stack.unshift({cb: a_cb, args: [i, a_obj[i]], autoComplete: fcf.getParamCount(a_cb) < 4});
        } else {
          let keys = [];
          for(let k in a_obj)
            keys.push(k);
          for(let i = keys.length-1; i >= 0; --i)
            this._stack.unshift({cb: a_cb, args: [keys[i], a_obj[keys[i]]], autoComplete: fcf.getParamCount(a_cb) < 4});
        }
        this._execute();
        return a_res;
      })
    }
    return this;
  }

  asyncEach(a_obj, a_cb){
    let self = this;

    this.then((a_value, a_act) => {
      a_obj = typeof a_obj == "function" ? a_obj() : a_obj;
      let autoComplete = fcf.getParamCount(a_cb) < 4;
      let size         = fcf.count(a_obj);
      let counter      = 0;
      let error        = false;

      if (size == 0) {
        a_act.complete();
        return;
      }

      let brk = false;
      fcf.each(a_obj, (k)=>{
        if (brk)
          return false;
        let act = {
          _complete: false,
          complete: function(a_value){
            fcf.setContext(self._context);
            if (this._complete || error || self._error)
              return;

            ++counter;

            self.result = a_value;

            if (counter == size){
              this._complete = true;
              a_act.complete(a_value);
            }
          },
          error: function(a_error){
            fcf.setContext(self._context);
            if (this._complete || error || self._error)
              return;

            self.result = undefined;
            this._complete = true;
            error = true;
            a_act.error(a_error);
          },
        };

        let args = [k, a_obj[k], self.result];
        if (!autoComplete)
          args.push(act);

        let forceExit = false;
        let res;
        try {
          let asyncPrefix = a_cb.toString().substr(0, 5);
          if (asyncPrefix == "async" && a_cb.toString().charCodeAt(5) <= 32){
            res = a_cb.apply(self, args).catch((e)=>{
              forceExit = true;
              brk = true;
              act.error(e)
            });
          } else {
            res = a_cb.apply(self, args)
          }
        } catch(e) {
          brk = true;
          act.error(e);
          return;
        }

        if (forceExit)
          return;

        if (autoComplete && ((typeof Promise !== "undefined" && res instanceof Promise) || res instanceof fcf.Actions)){
          res
          .then((a_value)=>{
            act.complete(a_value);
          })
          .catch((a_error)=>{
            act.error(a_error);
          });
        } else if (autoComplete){
          act.complete();
        }
      });
    });
    return this;
  }

  async promise() {
    let self = this;
    return new Promise((a_resolve, a_reject)=>{
      self
      .then((a_res)=>{
        a_resolve(a_res);
      })
      .catch((a_error)=>{
        a_reject(a_error);
      })
    })
  }

  catch(a_cb){
    if (!this._stack)
      return;
    if (a_cb && this._error)
      a_cb(this._error);
    else if (a_cb)
      this._errorcbs.push(a_cb);
    return this;
  }

  finally(a_cb){
    if (a_cb && this._error) {
      a_cb(this._error);
    } else {
      this._stack.push({cb: a_cb, args: undefined, finally: true, autoComplete: true });
      this._execute();
    }
    return this;
  }

  error(a_error){
    if (this._error)
      return this;
    this._error = a_error ? a_error : new Error("Unknown error");
    this._callErrors(this._error);
    return this;
  }

  startup(){
    this._deferred = false;
    this._execute();
    return this;
  }

  _callErrors(a_error){
    for (let i = 0; i < this._stack.length; ++i){
      if (this._stack[i].finally){
        try {
          this._stack[i].cb.call(this);
        } catch(e){
        }
      }
    }

    for (let i = 0; i < this._errorcbs.length; ++i){
      try {
        this._errorcbs[i](a_error);
      } catch(e){
        fcf.log.err("FCF:Actions:ErrorHandler", e);
      }
    }
  }

  _execute(){
    let self = this;
    fcf.setContext(self._context);
    if (this._run || !this._stack || this._stack.length == 0 || this._error)
      return;
    if (this._deferred)
      return;
    this._run = true;
    let cbi = this._stack.shift();
    let act = {
      _end: false,
      complete: function(a_value) {
        fcf.setContext(self._context);
        if (this._end || self._error)
          return;
        this._end = true;
        self._run = false;
        if ((typeof Promise !== "undefined" && a_value instanceof Promise) || a_value instanceof fcf.Actions){
          a_value
          .then((a_value)=>{
            if (!cbi.finally)
              self.result = a_value;
            self._execute();
          })
          .catch((a_error)=>{
            this._end = false;
            this.error(a_error);
          })
          if (a_value instanceof fcf.Actions)
            a_value.end();
        } else {
          if (!cbi.finally)
            self.result = a_value;
          self._execute();
        }
      },
      error: function(a_error) {
        fcf.setContext(self._context);
        if (this._end || self._error)
          return;
        this._end = true;
        self._run = false;
        self.result = undefined;
        self._error = a_error ? a_error : new Error("Unknown error");
        self._callErrors(self._error);
      },
    };
    let args = [];
    if (cbi.args) {
      args.push(cbi.args[0]);
      args.push(cbi.args[1]);
    }
    args.push(this.result);
    if (!cbi.autoComplete)
      args.push(act);

    let res = undefined;
    let forceExit = false;
    try {
      let asyncPrefix = cbi.cb.toString().substr(0, 5);
      if (asyncPrefix == "async" && cbi.cb.toString().charCodeAt(5) <= 32){
        res = cbi.cb.apply(this, args).catch((e)=>{
          forceExit = true;
          act.error(e);
        });
      } else {
        res = cbi.cb.apply(this, args);
      }
    } catch(e) {
      act.error(e);
      return;
    }

    if (forceExit)
      return;

    if ((typeof Promise !== "undefined" && res instanceof Promise) || res instanceof fcf.Actions){
      if (cbi.autoComplete){
        res
        .then((a_value)=>{
          act.complete(a_value);
        })
        .catch((a_error)=>{
          act.error(a_error);
        });
        if (res instanceof fcf.Actions)
          res.end();
      } else {
        res
        .catch((a_error)=>{
          act.error(a_error);
        });
      }
    } else if (cbi.autoComplete){
      act.complete(res);
    }
  }
}


fcf.actions = (a_options)=>{
  return new fcf.Actions(a_options);
}

fcf.getParamCount = (a_function)=>{
  let str = a_function.toString();
  let pos = str.indexOf("(");
  let startPos = pos;
  let end = str.indexOf(")");
  if (pos == -1 || end == -1 || pos >= end)
    return 0;
  let counter = 0;
  while(true){
    pos = str.indexOf(",", pos+1);
    if (pos == -1 || pos >= end)
      break
    if (!counter)
      counter = 2;
    else
      ++counter;
  }
  if (counter == 0){
    pos = startPos+1;
    while(pos < end){
      let c = str.charCodeAt(pos);
      if (c > 32)
        return 1;
      ++pos
    }
  }
  return counter;
}

if (!fcf.isServer()) {

  (()=>{
    let div = document.createElement("div");
    fcf.NDetails.emptyNodeList = div.querySelectorAll("null");
  })();



  fcf.select = (a_node, a_selector) => {
    if (!a_node && typeof a_selector === "string"){
      return document.querySelectorAll(a_selector);
    } else if (typeof a_node === 'string'){
      a_selector = a_node;
      return document.querySelectorAll(a_selector);
    } else if (a_node && typeof a_node == "object" && a_node.querySelectorAll){
      try {
        return a_node.querySelectorAll(a_selector);
      } catch(e){
        if (a_node.innerHTML == ""){
          if (fcf.NDetails.emptyNodeList.length)
            fcf.NDetails.emptyNodeList = document.createElement("div").querySelectorAll("null");
          return fcf.NDetails.emptyNodeList;
        } else {
          throw e;
        }
      }
    } else {
      if (fcf.NDetails.emptyNodeList.length)
        fcf.NDetails.emptyNodeList = document.createElement("div").querySelectorAll("null");
      return fcf.NDetails.emptyNodeList;
    }
  };
}


fcf.RouteInfo = function(a_settings){

  this.url      = '';
  this.referer  = '';
  this.urlArgs  = {};
  this.postArgs = {};
  this.subUri   = '';
  this.anchor   = '';

  if (typeof a_settings == "string")
    a_settings = {url: a_settings};
  if ("url" in a_settings && "referer" in a_settings && "args" in a_settings) {
    fcf.append(this, a_settings);
    if (typeof this.urlArgs != "object")
      this.urlArgs = {};
    if (typeof this.postArgs != "object")
      this.postArgs = {};
  } else if ('url' in a_settings) {
    this.url = a_settings.url;
    var qi = fcf.parseUrl(a_settings.url);
    this.urlArgs = fcf.append(qi.urlArgs, a_settings.urlArgs);
    this.referer = qi.referer;
    this.uri = "/" + fcf.ltrim(qi.uri, "/");
    this.args  = a_settings.args ? a_settings.args : {};
    this.url = fcf.link(this.url, this.urlArgs);
  } else if (a_settings.request) {
    var request = a_settings.request.req ? a_settings.request.req : a_settings.request;
    this.url += request.protocol + '://';
    this.url += request.get('host');
    this.url += request.originalUrl;
    var qi = fcf.parseUrl(this.url);
    this.args    = a_settings.args ? a_settings.args : {};
    this.urlArgs = fcf.append(qi.urlArgs, a_settings.urlArgs);
    this.referer = qi.referer;
    this.uri = "/"+fcf.ltrim(qi.uri, "/");
    if (!fcf.empty(request.body))
      this.postArgs = request.body;
    this.url = fcf.link(this.url, this.urlArgs);
  }

  let processUrlArgs = (a_args) => {
    for(var k in a_args){
      var itm = a_args[k];
      var isJSON = typeof itm === "string" &&
                   itm.length >= 2 &&
                   ( (itm[0] === '{' && itm[itm.length-1] === '}' ) ||
                     (itm[0] === '[' && itm[itm.length-1] === ']' )
                   );

      if (isJSON)
        try { a_args[k] = JSON.parse(a_args[k]); } catch(e) {}
      else if (a_args[k] == "false")
        a_args[k] = false;
      else if (a_args[k] == "true")
        a_args[k] = true;
      else if (!isNaN(a_args[k]) && a_args[k] !== "")
        a_args[k] = parseFloat(a_args[k])
    }
  }

  processUrlArgs(this.urlArgs);

  fcf.append(this.args, this.urlArgs);
  fcf.append(this.args, this.postArgs);

  let anchor = this.url.split("#")[1];
  if (anchor)
    this.anchor = anchor;
}


fcf.NDetails._thisLoggerMessage = false;

var Logger = function(){
  this.TST = 0;
  this.CRH = 10;
  this.ERR = 20;
  this.WRN = 30;
  this.LOG = 40;
  this.INF = 50;
  this.DBG = 60;
  this.TRC = 70;

  this._level = this.LOG;

  this.getLevel = function(){
    return this._level;
  }

  this.setLevel = function(a_level){
    this._level = a_level;
  }

  this.tst = function(a_module, a_str){
    var args = fcf.append([this.TST], arguments);
    this._logStr.apply(this, args);
  }

  this.crh = function(a_module, a_str){
    var args = fcf.append([this.CRH], arguments);
    this._logStr.apply(this, args);
  }

  this.err = function(a_module, a_str){
    var args = fcf.append([this.ERR], arguments);
    this._logStr.apply(this, args);
  }

  this.wrn = function(a_module, a_str){
    var args = fcf.append([this.WRN], arguments);
    this._logStr.apply(this, args);
  }

  this.log = function(a_module, a_str){
    var args = fcf.append([this.LOG], arguments);
    this._logStr.apply(this, args);
  }

  this.inf = function(a_module, a_str){
    var args = fcf.append([this.INF], arguments);
    this._logStr.apply(this, args);
  }

  this.dbg = function(a_module, a_str){
    var args = fcf.append([this.DBG], arguments);
    this._logStr.apply(this, args);
  }

  this.trc = function(){
    var args = fcf.append([this.TRC], arguments);
    this._logStr.apply(this, args);
  }

  this.write = function(){
    this._logStr.apply(this, arguments);
  }

  this._levelToStr = function(a_level){
    return a_level <= this.TST ? "TST" :
           a_level <= this.CRH ? "CRH" :
           a_level <= this.ERR ? "ERR" :
           a_level <= this.WRN ? "WRN" :
           a_level <= this.LOG ? "LOG" :
           a_level <= this.INF ? "INF" :
           a_level <= this.DBG ? "DBG" :
                                 "TRC";
  }

  this._logStr = function(a_level, a_module){
    if (a_level > this._level)
      return;

    var datetime = new Date();
    var output = "";
    output += fcf.dateFormat(datetime, "Y-m-d H:i:s.u");

    if (fcf.isServer())
      output += " [PID:" + process.pid + "]";
    output += " [" + this._levelToStr(a_level) + "]";
    output += " [MOD:" + a_module + "]: ";

    var outputArr = [];
    outputArr.push(output);

    for(var i = 2; i < arguments.length; ++i) {
      if (fcf.isServer() && typeof arguments[i] == "object"){
        if (arguments[i] instanceof Error) {
          outputArr.push(arguments[i].toString());
          outputArr.push(arguments[i].stack);
        } else if (arguments[i] instanceof fcf.Exception){
          outputArr.push(arguments[i].toString(true));
          outputArr.push("\nStack:\n" + fcf.Exception.stackToString(arguments[i].stackArr));
        } else {
          outputArr.push(JSON.stringify(arguments[i], 0, 2));
        }
      } else {
        outputArr.push(arguments[i]);
      }
    }

    fcf.NDetails._thisLoggerMessage = true;
    console.log.apply(console, outputArr);
    fcf.NDetails._thisLoggerMessage = false;

    if (fcf.isServer()) {
      if (fcf.application) {
        if (fcf.application.getConfiguration()){
          let file = fcf.getPath(fcf.application.getConfiguration().logFile + fcf.dateFormat(new Date(), "Y-m-d") + ".log");
          libFS.appendFile(file, outputArr.join(" ") + "\r\n", "utf-8", function(){} );
        }
      }
    }

  }
}

fcf.log = new Logger();
fcf.NDetails._wrappers = {};
fcf.NDetails._rwrappers = {};

fcf.getWrapper = (a_domElementOrId) => {
  if (typeof a_domElementOrId != "object"){
    return fcf.NDetails._wrappers[a_domElementOrId]
  } else {
   var element = a_domElementOrId;
   while (element){
    if (element.getAttribute("fcftemplate"))
      return fcf.NDetails._wrappers[element.getAttribute("id")];
    element = element.parentElement;
   }
  }
}


fcf.getOffsetForAbsolute = (a_element)=>{
  let screenOffsetX = window.scrollX !== undefined ? window.scrollX : document.documentElement.scrollLeft;
  let screenOffsetY = window.scrollY !== undefined ? window.scrollY : document.documentElement.scrollTop;
  let rect = a_element.getBoundingClientRect();
  let offsetX = screenOffsetX + rect.left;
  let offsetY = screenOffsetY + rect.top;
  while(a_element){
    let cs = getComputedStyle(a_element);
    let position = cs.getPropertyValue("position");
    if (position == "absolute" || position == "sticky"){
      let blw = parseFloat(cs.getPropertyValue("border-left-width"));
      let btw = parseFloat(cs.getPropertyValue("border-top-width"));
      rect = a_element.getBoundingClientRect();
      offsetX -= (rect.left + screenOffsetX + blw);
      offsetY -= (rect.top + screenOffsetY + btw);
      break;
    }
    a_element = a_element.parentElement;
  }
  return {left: offsetX, top: offsetY};
}


fcf.argVal = (a_data, a_options) => {
  let arg = { "fcf.Arg": true, type: "value", value: a_data };
  if (a_options)
    fcf.append(arg, a_options);
  return arg;
}

fcf.argUrl = (a_arg, a_options) => {
  let arg = { "fcf.Arg": true, type: "url", arg: a_arg };
  if (!a_options)
    fcf.append(arg, a_options);
  return arg;
}

fcf.argRef = (a_id, a_arg, a_options) => {
  let object;
  if (a_arg === undefined || a_arg === null) {
    a_arg  = a_id;
    object = fcf.getContext().currentTemplate;
  } else if (typeof a_arg === "object") {
    a_options = a_arg;
    a_arg  = a_id;
    object = fcf.getContext().currentTemplate;
  } else {
    object = {id: a_id};
  }
  let arg = { "fcf.Arg": true, type: "reference", id: object.id, object: object, arg: a_arg };
  if (a_options)
    fcf.append(arg, a_options);
  return arg;
}

fcf.argRecordRef = (a_arg) => {
  return { "fcf.Arg": true, type: "recordreference", arg:  a_arg };
}

fcf.argTmpl = (a_template, a_args, a_options) => {
  if (!a_args)
    a_args = {};
  let arg = { "fcf.Arg": true, type: "template", template: a_template, args: a_args };
  if (a_options)
    fcf.append(arg, a_options);
  if (arg.type == "template" && fcf.NDetails.currentTemplate && arg.template.charAt(0) == "+")
    arg.template = fcf.NDetails.currentTemplate.split("+")[0] + arg.template;
  return arg;
}

fcf.argProg = (a_options) => {
  let arg = { "fcf.Arg": true, type: "programmable" };
  if (a_options)
    fcf.append(arg, a_options);
  return arg;
}

fcf.isArg = (a_object) => {
  return typeof a_object == "object" && a_object !== null && a_object["fcf.Arg"];
}


fcf.NDetails.currentContext = undefined;


class FCFSafeContext {
  get(a_variableName) {
    let result = fcf.getContext().get(a_variableName);
    if (a_variableName == "safeEnv")
      return result;
    return typeof result == "object" ? fcf.clone(result) : result;
  }
}

fcf.Context = class FCFContext{
  constructor(a_obj){

    fcf.append(this, a_obj);
    if (!fcf.isServer())
      this.route = new fcf.RouteInfo(this.route);

    if (fcf.isServer()){
      let globalObjects = fcf.append(
        [],
        Object.getOwnPropertyNames(_fcfGlobals()),
        Object.getOwnPropertyNames(global),
        ["Object","Function","Array","Number","parseFloat","parseInt","Infinity","NaN",
         "undefined","Boolean","String","Symbol","Date","Promise","RegExp","Error","EvalError",
         "RangeError","ReferenceError","SyntaxError","TypeError","URIError","globalThis","JSON",
         "Math","console","Intl","ArrayBuffer","Uint8Array","Int8Array","Uint16Array","Int16Array",
         "Uint32Array","Int32Array","Float32Array","Float64Array","Uint8ClampedArray","BigUint64Array",
         "BigInt64Array","DataView","Map","BigInt","Set","WeakMap","WeakSet","Proxy","Reflect",
         "decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape","eval",
         "isFinite","isNaN","SharedArrayBuffer","Atomics","FinalizationRegistry","WeakRef","WebAssembly",
         "global","process","Buffer","URL","URLSearchParams","TextEncoder","TextDecoder","clearInterval",
         "clearTimeout","setInterval","setTimeout","queueMicrotask","clearImmediate","setImmediate","fcf",
         "parts","endstr","anchorArr","c2","c1","inputArgs","module","require","assert","async_hooks",
         "buffer","child_process","cluster","constants","crypto","dgram","diagnostics_channel","dns",
         "domain","events","fs","http","http2","https","inspector","os","path","perf_hooks","querystring",
         "readline","repl","stream","string_decoder","sys","timers","tls","trace_events","url","vm","wasi",
         "worker_threads","_","_error","net","punycode","zlib","util","v8","tty","import","export","exports",
         "package","debugger","arguments","py","__filename","__dirname","GLOBAL","root","_fcfGlobals", "global, async, await"]);

      let safeEnv = {};
      for(let i = 0; i < globalObjects.length; ++i)
        if (globalObjects[i] != "undefined")
          safeEnv[globalObjects[i]] = null;
      safeEnv.Date     = Date;
      safeEnv.Math     = Math;
      safeEnv.decodeURIComponent = decodeURIComponent;
      safeEnv.encodeURIComponent = encodeURIComponent;
      safeEnv.fcf      = fcf.append({}, fcf.NDetails.safeFcf);
      this.set("safeEnv", safeEnv);
    }

  }

  destroy() {
    for(let key in this)
      delete this[key];
  }

  get(a_variableName){
    let value = this[a_variableName];
    if (typeof value == "object")
      return fcf.clone(value);
    else
      return value;
  }

  set(a_variableName, a_value){
    this[a_variableName] = a_value;
    fcf.saveContext();
  }
}

fcf.getContext = () => {
  if (fcf.isServer())
    return fcf.NServer && fcf.NServer.Application && typeof fcf.NServer.Application.getContext == "function" && fcf.NServer.Application.getContext() ? fcf.NServer.Application.getContext() :
           fcf.NServer && fcf.NServer.Application && typeof fcf.NServer.Application.geEmptyContext == "function"                                     ? fcf.NServer.Application.geEmptyContext() :
                                                                                                                                                       undefined;
  else
    return fcf.NDetails.currentContext;
}

fcf.getSafeContext = () => {
  return new FCFSafeContext();
}

fcf.setContext = (a_context) => {
  if (fcf.isServer()) {
    if (fcf.NServer && fcf.NServer.Application) {
      fcf.NServer.Application.setContext(a_context);
    }
  } else {
    fcf.NDetails.currentContext = a_context;
  }
}

fcf.saveContext = (a_request) => {
  let sortCtxt = fcf.append({}, fcf.getContext());
  delete sortCtxt.route;
  delete sortCtxt.args;
  delete sortCtxt.safeEnv;

  if (fcf.isServer()){
    if (a_request)
      a_request.setCookie("___fcf___context",
                          fcf.base64Encode(JSON.stringify(sortCtxt)),
                          {
                            maxAge:   fcf.application.getConfiguration().sessionLifetime*3600*24,
                            path:     "/",
                            sameSite: "Lax",
                          });
  } else {
    let d = new Date();
    d.setTime(d.getTime() + (365*24*60*60*1000));
    var expires = "expires="+ d.toUTCString();
    document.cookie = "___fcf___context=" + fcf.base64Encode(JSON.stringify(sortCtxt)) + "; path=/ ; SameSite=Lax;" + expires;
  }
}


fcf.NDetails._keyBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

fcf.base64Encode  = function (input) {

  function utf8Encode (string) {
      string = string.replace(/\r\n/g,"\n");
      var utftext = "";

      for (var n = 0; n < string.length; n++) {

          var c = string.charCodeAt(n);

          if (c < 128) {
              utftext += String.fromCharCode(c);
          }
          else if((c > 127) && (c < 2048)) {
              utftext += String.fromCharCode((c >> 6) | 192);
              utftext += String.fromCharCode((c & 63) | 128);
          }
          else {
              utftext += String.fromCharCode((c >> 12) | 224);
              utftext += String.fromCharCode(((c >> 6) & 63) | 128);
              utftext += String.fromCharCode((c & 63) | 128);
          }

      }

      return utftext;
  }

  var output = "";
  var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
  var i = 0;

  input = utf8Encode(input);

  while (i < input.length) {

      chr1 = input.charCodeAt(i++);
      chr2 = input.charCodeAt(i++);
      chr3 = input.charCodeAt(i++);

      enc1 = chr1 >> 2;
      enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
      enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
      enc4 = chr3 & 63;

      if (isNaN(chr2)) {
          enc3 = enc4 = 64;
      } else if (isNaN(chr3)) {
          enc4 = 64;
      }

      output = output +
      fcf.NDetails._keyBase64.charAt(enc1) + fcf.NDetails._keyBase64.charAt(enc2) +
      fcf.NDetails._keyBase64.charAt(enc3) + fcf.NDetails._keyBase64.charAt(enc4);

  }

  return output;
}


fcf.base64Decode  = function (input) {
  function utf8Decode (utftext) {
      var string = "";
      var i = 0;
      var c = 0;
      var c1 = 0;
      var c2 = 0;

      while ( i < utftext.length ) {

          c = utftext.charCodeAt(i);

          if (c < 128) {
              string += String.fromCharCode(c);
              i++;
          }
          else if((c > 191) && (c < 224)) {
              c2 = utftext.charCodeAt(i+1);
              string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
              i += 2;
          }
          else {
              c2 = utftext.charCodeAt(i+1);
              c3 = utftext.charCodeAt(i+2);
              string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
              i += 3;
          }

      }

      return string;
  }

  var output = "";
  var chr1, chr2, chr3;
  var enc1, enc2, enc3, enc4;
  var i = 0;

  input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

  while (i < input.length) {

      enc1 = fcf.NDetails._keyBase64.indexOf(input.charAt(i++));
      enc2 = fcf.NDetails._keyBase64.indexOf(input.charAt(i++));
      enc3 = fcf.NDetails._keyBase64.indexOf(input.charAt(i++));
      enc4 = fcf.NDetails._keyBase64.indexOf(input.charAt(i++));

      chr1 = (enc1 << 2) | (enc2 >> 4);
      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
      chr3 = ((enc3 & 3) << 6) | enc4;

      output = output + String.fromCharCode(chr1);

      if (enc3 != 64) {
          output = output + String.fromCharCode(chr2);
      }
      if (enc4 != 64) {
          output = output + String.fromCharCode(chr3);
      }

  }

  output = utf8Decode(output);

  return output;
}

fcf.NDetails._firstCallLiven = true;
fcf.liven = (a_domElement, a_parentIdOrDom, a_cb) =>{
  if (fcf.NDetails._firstCallLiven){
    fcf.NDetails._firstCallLiven = false;
    let elements = fcf.select("div[fcftemplate]");
    let templateMap = {}
    for(let i = 0; i < elements.length; ++i){
      if (elements[i].hasAttribute("nowrapper"))
        continue;
      let path = elements[i].getAttribute("fcftemplate");
      if (!path)
        continue;
      templateMap[path] = true;
    }
    let wrappers = []
    for(let template in templateMap){
      var subpart = template.split("+")[1];
      var wrapperPath = template.split("+")[0];
      var url = wrapperPath.substr(0, wrapperPath.length-5) + (subpart ? "+" + subpart : "") + ".wrapper.js";
      wrappers.push(url)
    }

    fcf.require(wrappers);
  }



  if (typeof a_domElement === "function") {
    a_cb = a_domElement;
    a_domElement = document.body;
  }
  else if (typeof a_parentIdOrDom === "function") {
    a_cb = a_parentIdOrDom;
    a_parentIdOrDom = undefined;
  }
  if (!a_domElement)
    a_domElement = document.body;

  var elements =
    a_parentIdOrDom                          ? fcf.select(a_domElement, "[fcfparent='" + (typeof a_parentIdOrDom === "object" ? a_parentIdOrDom.getAttribute("id") : a_parentIdOrDom) + "'][fcftemplate]") :
    a_domElement.getAttribute("fcftemplate") ? [a_domElement] :
                                               fcf.select(a_domElement, ":not([fcfparent])[fcftemplate]");

  let rootInfo = fcf.application.getConfiguration().root;
  if (a_domElement == document.body && a_domElement.id != rootInfo.id){
    if (fcf.find(elements, document.body) === undefined)
      elements = fcf.append([document.body], elements);
  }

  return fcf.actions()
  .asyncEach(elements, function(a_key, a_element, a_res, a_done){
    if (a_element == document.body) {
      a_element.setAttribute("id", rootInfo.id);
      a_element.setAttribute("fcftemplate", rootInfo.template);
    }

    var object = fcf.NDetails._wrappers[a_element.getAttribute("id")];
    if (object) {
      object._reattach()
      .then(()=>{
        a_done.complete();
      })
      .catch((a_error)=>{
        a_done.error(a_error);
      })
      return;
    }

    var template = a_element.getAttribute("fcftemplate");
    var subpart = template.split("+")[1];
    var wrapperPath = template.split("+")[0];
    var url = wrapperPath.substr(0, wrapperPath.length-5) + (subpart ? "+" + subpart : "") + ".wrapper.js";

    if (a_element.hasAttribute("nowrapper")) {
      var wrapper = new fcf.NClient.Wrapper({
            eventChannel: fcf.application.getEventChannel(),
            domElement: a_element
          });
      wrapper.initialize()
      .then(()=>{
        let afe = fcf.select(a_element, "[autofocus]")[0]
        if (afe)
          afe.focus();
        a_done.complete();
      })
      .catch((a_error)=>{
        a_done.error(a_error);
      })
    } else {
      fcf.require([url],
        (Wrapper) =>{
          var wrapper = new Wrapper({
            eventChannel: fcf.application.getEventChannel(),
            domElement: a_element
          });
          wrapper.initialize()
          .then(()=>{
            let afe = fcf.select(a_element, "[autofocus]")[0]
            if (afe)
              afe.focus();
            a_done.complete();
          })
          .catch((a_error)=>{
            a_done.error(a_error);
          })
        }
      );
    }
  })
  .then(()=>{
    if (a_cb)
      a_cb();
  })
  .catch((a_error)=>{
    if (a_cb)
      a_cb(a_error);
  })
}

/*********************
------- EVENTS -------
**********************/

if (!fcf.isServer()) {
  fcf.NDetails.oldEPD = Event.prototype.preventDefault;
  Event.prototype.preventDefault = function() {
    this.stopDefault = true;
    fcf.NDetails.oldEPD.call(this);
  };
  fcf.NDetails.oldESP = Event.prototype.stopPropagation;
  Event.prototype.stopPropagation = function() {
    this._stopPropagation = true;
    fcf.NDetails.oldESP.call(this);
  };
}

fcf.NDetails.domListenerFeedback  = {};
fcf.NDetails.domListenerCallbacks = {};
fcf.NDetails.registerWindowEvents = {};

fcf.NDetails.domListener = (a_element, a_eventName, a_event) => {
  if (typeof a_eventName == "object"){
    a_event = a_eventName;
    a_eventName = a_event.type;
  }

  var fcfevntid = a_element == window ? "window" : a_element.getAttribute("fcfevntid");

  if (!fcfevntid || !fcf.NDetails.domListenerCallbacks[fcfevntid] || !fcf.NDetails.domListenerCallbacks[fcfevntid][a_eventName])
    return a_event;

  var callbacks = fcf.NDetails.domListenerCallbacks[fcfevntid][a_eventName];

  let stop = false;
  fcf.each(callbacks, (a_key, a_level)=>{
    fcf.each(a_level, (a_key, a_cbi)=>{
      if (a_event._stopPropagation){
        stop = true;
        return false;
      }
      typeof a_cbi.cb == "object" ? a_cbi.cb.object[a_cbi.cb.method](a_event)
                                  : a_cbi.cb.call(a_element, a_event);
    })
    if (stop)
      return false;
  });
  return a_event;
}

fcf.emitDomEvent = (a_element, a_name, a_data) => {
  if (typeof a_name == "object"){
    var event = a_name;
    a_name = event.type;
    fcf.append(event, a_data);
  } else {
    var event = document.createEvent('Event');
    event.initEvent(a_name, true, true);
    event.name = a_name;
    if (!event.type)
      event.type = a_name;
    fcf.append(event, a_data);
  }
  if (fcf.empty(a_element))
    return event;;
  return fcf.NDetails.domListener(a_element, a_name, event);
}

//fcf.addDomListener = (a_element, a_eventName, a_owner, a_level, a_cb)
//fcf.addDomListener = (a_element, a_eventName, a_owner, a_cb)
fcf.addDomListener = (a_element, a_eventName, a_owner, a_level, a_cb) => {
  if (typeof a_level === "function"){
    a_cb = a_level;
    a_level = 0;
  }

  var fcfevntid = a_element === window ? "window" : a_element.getAttribute("fcfevntid");
  if (!fcfevntid){
    fcfevntid = fcf.id();
    a_element.setAttribute("fcfevntid", fcfevntid);
  }

  let id = fcf.uuid();

  if (!fcf.NDetails.domListenerCallbacks[fcfevntid])
    fcf.NDetails.domListenerCallbacks[fcfevntid] = {};
  if (!fcf.NDetails.domListenerCallbacks[fcfevntid][a_eventName])
    fcf.NDetails.domListenerCallbacks[fcfevntid][a_eventName] = {};
  if (!(a_level in fcf.NDetails.domListenerCallbacks[fcfevntid][a_eventName]))
    fcf.NDetails.domListenerCallbacks[fcfevntid][a_eventName][a_level] = {};
  if (!(id in fcf.NDetails.domListenerCallbacks[fcfevntid][a_eventName][a_level]))
    fcf.NDetails.domListenerCallbacks[fcfevntid][a_eventName][a_level][id] = {};

  let ownerId = a_owner.getId();
  if (!(ownerId in fcf.NDetails.domListenerFeedback))
    fcf.NDetails.domListenerFeedback[ownerId] = {};
  if (!(a_level in fcf.NDetails.domListenerFeedback[ownerId]))
    fcf.NDetails.domListenerFeedback[ownerId][a_level] = {};
  fcf.NDetails.domListenerFeedback[ownerId][a_level][id] = {
    eventId: fcfevntid,
    eventName: a_eventName,
  };

  fcf.NDetails.domListenerCallbacks[fcfevntid][a_eventName][a_level][id] = {
    fcfevntid: fcfevntid,
    cb: a_owner instanceof fcf.NClient.Wrapper && a_cb === undefined
              ? { object: a_owner, method: "on" + a_eventName[0].toUpperCase() + a_eventName.substr(1)}
              : a_cb,
  };

  var code = "return fcf.NDetails.domListener(this, \"" + a_eventName + "\", event);";
  if (a_element === window) {
    if (!(a_eventName in fcf.NDetails.registerWindowEvents)){
      fcf.NDetails.registerWindowEvents[a_eventName] = true;
      window.addEventListener(a_eventName, function(event){
        fcf.NDetails.domListener(this, a_eventName, event);
      })
    }
  } else {
    if (!a_element["fcfEventReg" + a_eventName]){
      a_element["fcfEventReg" + a_eventName] = true;
      a_element.addEventListener(a_eventName, function(event){
        fcf.NDetails.domListener(this, a_eventName, event);
      });
    }
  }
}


fcf.removeDomListener = (a_owner, a_element, a_eventName) => {
  let ownerId = typeof a_owner === "string" ? a_owner : a_owner.getId();
  if (!(ownerId in fcf.NDetails.domListenerFeedback))
    return;
  let eventid = a_element === window    ? "window" :
                a_element !== undefined ? a_element.getAttribute("fcfevntid") :
                                          undefined;
  let full = true;
  for(let level in fcf.NDetails.domListenerFeedback[ownerId]) {
    for(let id in fcf.NDetails.domListenerFeedback[ownerId][level]) {
      let eventPos = fcf.NDetails.domListenerFeedback[ownerId][level][id];
      if ((eventid === undefined || eventid == eventPos.eventId) &&
          (a_eventName === undefined || a_eventName == eventPos.eventName)) {
        delete fcf.NDetails.domListenerFeedback[ownerId][level][id];
        delete fcf.NDetails.domListenerCallbacks[eventPos.eventId][eventPos.eventName][level][id];
        if (fcf.empty(fcf.NDetails.domListenerCallbacks[eventPos.eventId][eventPos.eventName][level]))
          delete fcf.NDetails.domListenerCallbacks[eventPos.eventId][eventPos.eventName][level];
        if (fcf.empty(fcf.NDetails.domListenerCallbacks[eventPos.eventId][eventPos.eventName]))
          delete fcf.NDetails.domListenerCallbacks[eventPos.eventId][eventPos.eventName];
        if (fcf.empty(fcf.NDetails.domListenerCallbacks[eventPos.eventId]))
          delete fcf.NDetails.domListenerCallbacks[eventPos.eventId];
      } else {
        full = false;
      }
    }
  }
  if (full)
    delete fcf.NDetails.domListenerFeedback[ownerId];
}

function grabcollectorEvents(){
  setTimeout(()=>{
    for(let id in fcf.NDetails.domListenerFeedback){
      let wrp = fcf.getWrapper(id);
      if (!wrp && id != "window") {
        fcf.removeDomListener(id);
      } else {
        for(let level in fcf.NDetails.domListenerFeedback[id]) {
          for(let recid in fcf.NDetails.domListenerFeedback[id][level]) {
            let info = fcf.NDetails.domListenerFeedback[id][level][recid];
            if (info.eventId === "window")
              continue;
            let element = fcf.select(`[fcfevntid="${info.eventId}"]`)[0];
            if (element)
              continue;
            delete fcf.NDetails.domListenerFeedback[id][level][recid];
            delete fcf.NDetails.domListenerCallbacks[info.eventId];
          }
        }
      }
    }
    grabcollectorEvents();
  },10000);
}

if (!fcf.isServer())
  grabcollectorEvents();


fcf.NDetails._removeWrappers = (a_runCyclically) =>{
  for(var id in fcf.NDetails._wrappers){
    if (document.getElementById(id))
      continue;
    fcf.NDetails._rwrappers[id] = fcf.NDetails._wrappers[id];
  }

  if (!fcf.empty(fcf.NDetails._rwrappers)) {
    setTimeout(()=>{
      for(var id in fcf.NDetails._rwrappers){
        fcf.NDetails._rwrappers[id].destroy();
        delete fcf.NDetails._wrappers[id];
        delete fcf.NDetails._rwrappers[id];
      }
    }, 1000);
  }

  if (a_runCyclically)
    setTimeout(()=>{ fcf.NDetails._removeWrappers(true); }, 10000);
}


if (!fcf.isServer()){
  fcf.NDetails._removeWrappers(true);
}

fcf.NDetails._includes = {};

fcf.include = (a_fileOrFiles, a_cb) => {
  var files = Array.isArray(a_fileOrFiles) ? a_fileOrFiles : [a_fileOrFiles];
      files = fcf.append([], files);
  var actions = fcf.actions();

  for(var i = 0; i < files.length; ++i) {
    var file = files[i];
    if (file[0] == ":")
      file = "/" + file.substr(1);
    var url  = fcf.getPath(file);

    if (url in fcf.NDetails._includes)
      continue;

    if (extension == "css"){
      var elements = fcf.select(document.head, "link");
      for(var j = 0; j < elements.length; ++j){
        if (elements[j].href == url){
          fcf.NDetails._includes[url] = true;
          break;
        }
      }
    } else if (extension == "js"){
      var elements = fcf.select(document.head, "script");
      for(var j = 0; j < elements.length; ++j){
        if (elements[j].src == url){
          fcf.NDetails._includes[url] = true;
          break;
        }
      }
    }


    if (url in fcf.NDetails._includes)
      continue;

    fcf.NDetails._includes[url] = true;

    var extension = fcf.getExtension(file).toLowerCase();
    if (extension == "css"){
      ((url)=>{
        actions.then((a_res, a_act)=>{
          var element = document.createElement('link');
          element.rel = 'stylesheet';
          element.type = 'text/css';
          element.media = "all";
          element.href = url;
          element.onload  = () => { a_act.complete() }
          element.onerror = () => { a_act.complete() }
          document.head.appendChild(element);
        });
      })(url);
    } if (extension == "js") {
      ((url)=>{
        actions.then((a_res, a_act) => {
          var element = document.createElement('script');
          element.src = url;
          element.onload  = () => { a_act.complete() }
          element.onerror = () => { a_act.complete() }
          document.head.appendChild(element);
        });
      })(url);
    }
  }
  actions.then(()=>{
    if (a_cb)
      a_cb();
  });
  actions.catch((a_error)=>{
    if (a_cb)
      a_cb(a_error);
  });
  return actions;
}


fcf.NDetails.modalZIndex = 10000;
fcf.getModalZIndex = ()=>{
  return ++fcf.NDetails.modalZIndex;
}

fcf.locker = (a_options) => {
  if (typeof a_options == "string")
    a_options = {selector: a_options};
  else if (a_options instanceof fcf.NClient.Wrapper)
    a_options = {selector: a_options.getDomElement()};
  else if (a_options instanceof Node)
    a_options = {selector: a_options};
  let id = fcf.uuid();
  let locker = document.createElement("div");
  locker.setAttribute("id", id);
  locker.classList.add("fcflocker");
  if (a_options.class)
    locker.classList.add(a_options.class);
  locker.style.zIndex = fcf.getModalZIndex();
  locker.innerHTML = '<div class="fcflocker-container"><div class="fcflocker-item"></div></div>';
  let element = fcf.select(a_options.selector)[0];
  if (!element)
    return locker;

  let firstCall = true;
  function resize(){
    if (!firstCall && !fcf.select("[id='" + id + "']")[0]) {
      window.removeEventListener("resize", resize);
      window.removeEventListener("scroll", resize);
      return;
    }
    firstCall = false;
    let lockerOffset = fcf.getOffsetForAbsolute(element);
    if (element != document.body){
      locker.style.left = lockerOffset.left+"px";
      locker.style.top  = lockerOffset.top+"px";
    } else {
      locker.style.left = "0px";
      locker.style.top  = "0px";
    }
    locker.style.width  = fcf.width(element);
    locker.style.height = fcf.height(element);
  }
  element.appendChild(locker);
  window.addEventListener("resize", resize);
  window.addEventListener("scroll", resize);
  resize();
  setTimeout(()=>{
    resize();
  },100);

  return locker;
}


fcf.NDetails.getRect = (a_element, a_isNotRoot) => {
  let rect = a_element.getBoundingClientRect();
  if (rect.height || rect.width)
    return rect;
  rect = {
    left: Number.MAX_VALUE,
    right: 0,
    top: Number.MAX_VALUE,
    bottom: 0,
  };
  for(let i =0; i < a_element.children.length; ++i){
    let subrect = fcf.NDetails.getRect(a_element.children[i], true);
    rect.left =   Math.floor(fcf.min(rect.left, subrect.left));
    rect.right =  Math.floor(fcf.max(rect.right, subrect.right));
    rect.top =    Math.floor(fcf.min(rect.top, subrect.top));
    rect.bottom = Math.floor(fcf.max(rect.bottom, subrect.bottom));
  }

  if (!a_isNotRoot) {
    if (rect.left == Number.MAX_VALUE)
      rect.left = rect.right;
    if (rect.top == Number.MAX_VALUE)
      rect.top = rect.bottom;
  }
  return rect;
}

fcf.width = (a_element) =>{
  if (typeof a_element == "string")
    a_element = fcf.select(a_element)[0];
  let rect = fcf.NDetails.getRect(a_element);
  return rect.right - rect.left;
}

fcf.height = (a_element) =>{
  if (a_element === document.body) {
    return Math.max(a_element.scrollHeight, a_element.offsetHeight, a_element.clientHeight,
                    document.documentElement.scrollHeight, document.documentElement.offsetHeight, document.documentElement.clientHeight);
  }

  if (typeof a_element == "string")
    a_element = fcf.select(a_element)[0];
  let rect = fcf.NDetails.getRect(a_element);
  return rect.bottom - rect.top;
}

if (!fcf.isServer()) {
  if (!('remove' in Element.prototype)){
    Element.prototype.remove = function(){
      if (this.parentNode)
        this.parentNode.removeChild(this);
    }
  }

  if (!('padStart' in String.prototype)){
    String.prototype.padStart = function(a_size, a_pad){
      if (a_size <= 0)
        return this;
      if (typeof a_pad !== "string" || !a_pad.length)
        return this;
      let preffix = "";
      while((preffix.length + this.length) < a_size)
        preffix += a_pad.substr(0, a_size - (preffix.length + this.length));
      return preffix + this;
    }
  }
}


fcf.NDetails.safeFcf = {
  arg:                    fcf.arg,
  argVal:                 fcf.argVal,
  argRef:                 fcf.argRef,
  argTmpl:                fcf.argTmpl,
  argUrl:                 fcf.argUrl,
  argRecordRef:           fcf.argRecordRef,
  isServer:               fcf.isServer,
  id:                     fcf.id,
  t:                      fcf.t,
  escapeQuotes:           fcf.escapeQuotes,
  unescape:               fcf.unescape,
  encodeHtml:             fcf.encodeHtml,
  decodeHtml:             fcf.decodeHtml,
  stripTags:              fcf.stripTags,
  sortObject:             fcf.sortObject,
  replaceAll:             fcf.replaceAll,
  count:                  fcf.count,
  insertBeforeByValue:    fcf.insertBefore,
  isEnumerable:           fcf.isEnumerable,
  find:                   fcf.find,
  findVal:                fcf.findVal,
  map:                    fcf.map,
  array:                  fcf.array,
  max:                    fcf.max,
  min:                    fcf.min,
  range:                  fcf.range,
  first:                  fcf.first,
  inc:                    fcf.inc,
  firstKey:               fcf.firstKey,
  first2:                 fcf.first2,
  last:                   fcf.last,
  last2:                  fcf.last2,
  byteCount:              fcf.byteCount,
  empty:                  fcf.empty,
  in:                     fcf.in,
  splitSpace:             fcf.splitSpace,
  ltrim:                  fcf.ltrim,
  rtrim:                  fcf.rtrim,
  trim:                   fcf.trim,
  str:                    fcf.str,
  cutMode:                fcf.cutMode,
  buildView:              fcf.buildView,
  normalizeObjectAddress: fcf.normalizeObjectAddress,
  resolve:                fcf.resolve,
  resolveEx:              fcf.resolveEx,
  uuid:                   fcf.uuid,
  equal:                  fcf.equal,
  parseUrl:               fcf.parseUrl,
  buildUrl:               fcf.buildUrl,
  getPath:                fcf.getPath,
  dateFormat:             fcf.dateFormat,
  getContext:             fcf.getSafeContext,
};

if (typeof module !== 'undefined')
  module.exports = fcf;
if (typeof global !== 'undefined')
  global.fcf = fcf;
if (typeof window !== 'undefined')
  window.fcf = fcf;

if (fcf.isServer()){
  fcf.registerPackagePath("fcf", __dirname, "fcfpackages/fcf");
} else {
  fcf.registerPackagePath("fcf", "", "fcfpackages/fcf");
}

if (fcf.isServer()){
  fcf.NDetails.eventChannel = new fcf.EventChannel({
    deferredEvents: ["watch_file"],
  });
  fcf.require(["fcf:NServer/Application.js"]);
} else {
  fcf.NDetails.eventChannel = new fcf.EventChannel();
}

})();

