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, a_strong) => {
  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_strong){
              if (a_search[i] === a_obj[i]) {
                return i;
              }
            } else {
              if (a_search[i] == a_obj[i]) {
                return i;
              }
            }
          }
        }
      } else {
        for(let i = 0; i < a_obj.length; ++i){
          if (a_strong){
            if (a_search === a_obj[i]){
              return i;
            }
          } else {
            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_strong){
              if (a_search[i] === a_obj[k]) {
                return k;
              }
            } else {
              if (a_search[i] == a_obj[k]) {
                return k;
              }
            }
          }
        }
      } else {
        for(let k in a_obj){
          if (a_strong) {
            if (a_search === a_obj[k]){
              return k;
            }
          } else {
            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, a_theme) => {
  if (typeof a_viewDescription !== "object")
    return {};

  function applyUsage(a_dst, a_usage, a_mode) {
    if (typeof a_usage !== "object")
      return;

    let arrRes = [];

    let arrMode = a_mode.split(".");
    for(let usage in a_usage) {
      let arrUsage = usage.split(".");
      let coincidence = false;
      let weight = arrUsage.length;
      if (arrUsage.length && arrUsage[arrUsage.length - 1] == "*")
        weight -= 0.5;

      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) {
        arrRes.push({weight: weight, key: usage});
      }
    }

    arrRes.sort((a_left, a_right) => { return a_left.weight - a_right.weight; });

    for(let i = 0; i < arrRes.length; ++i) {
      fcf.append(a_dst, a_usage[arrRes[i].key]);
    }
  }

  if (!a_theme)
    a_theme = fcf.application.getTheme();

  let type       = a_viewDescription.type;
  let themeUsage = a_theme.getConfiguration().usageViews[type];

  let result = {};
  applyUsage(result, themeUsage, a_mode);

  fcf.append(result, a_viewDescription);

  if (typeof a_viewDescription.usage === "object")
    applyUsage(result, a_viewDescription.usage, a_mode);

  delete result.usage;

  if (!result.template)
    result.template = "@controls:text";

  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((a_len - result.length) / a_fill.length);
  for(var i = 0; i < с; ++i)
    result += a_fill;

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

  return result;
}

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

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

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

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

  return preffix + 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){
  this._events          = {};
  this._deferredEvents  = {};

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

  // EventChannel::on(a_eventName, a_callback);
  // EventChannel::on(a_eventName, a_options, a_callback);
  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,
          global:   false,
          active:   false,
          sync:     false,
          lock:     false
        };
      } else if (typeof arguments[2] == "function"){
        a_options = {
          name:     arguments[0],
          function: arguments[2],
          once:     false,
          global:   typeof arguments[1] == "boolean" ? arguments[1] :
                    typeof arguments[1] == "object"  ? arguments[1].global : false,
          active:   typeof arguments[1] == "object"  ? !!arguments[1].active : false,
          sync:     typeof arguments[1] == "object"  ? !!arguments[1].sync   : false,
          lock:     typeof arguments[1] == "object"  ? arguments[1].lock   : false,
        };
      } else {
        return;
      }
    }
    return this._attach.call(this, a_options);
  }

  /**
  / @method once(a_eventName, a_callback);
  / @method once(a_eventName, a_options, a_callback);
  / @brief Attach event for only one call
  / @param string a_eventName - event name
  / @param object a_options - extends 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,
          global:   false,
          active:   false,
          sync:     false,
          lock:     false,
        };
      } else if (typeof arguments[2] == "function"){
        a_options = {
          name:     arguments[0],
          function: arguments[2],
          once:     true,
          global:   typeof arguments[1] == "boolean" ? arguments[1] :
                    typeof arguments[1] == "object"  ? arguments[1].global : false,
          active:   typeof arguments[1] == "object"  ? !!arguments[1].active : false,
          sync:     typeof arguments[1] == "object"  ? !!arguments[1].sync   : false,
          lock:     typeof arguments[1] == "object"  ? arguments[1].lock   : false,
        };
      } else {
        return;
      }
    }

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



  //this.detach = function(a_eventName, a_func)
  //this.detach = function(a_func)
  this.detach = function(a_eventName, a_func){
    if (!(a_eventName in this._events))
      return;
    let listenerId = a_func ? this._applyEventObjectId(a_func) : false;
    for(var eventLestenerId in this._events[a_eventName].events) {
      if (listenerId === false || eventLestenerId == listenerId){
        delete this._events[a_eventName].events[eventLestenerId];
      }
    }
  }

  /**
  / @method send(string a_event)
  / @method send(string a_event, object a_data)
  / @method send(string a_event, object a_data, object a_options)
  / @brief  Send event
  / @param string a_event   - event name
  / @param object a_data    - event fields
  / @param object a_options - [SERVER ONLY] sending parameters
  /                           - bool inner          - inner event for server
  /                           - bool sync           - sync processed mode
  /                           - bool noWaitExternal - don't wait external execution for other processes.
  /                                                 - for current process (inner option)
  /                           - array endpoints     - endpoints info
  / @result fcf.Actions->fcf.Event
  **/
  this.send = function(a_event, a_data, a_options) {
    if (typeof a_event === "object"){
      a_options = a_data;
      a_data = a_event;
      a_event = a_data.name;
    }
    if (typeof a_options !== "object" || a_options === null)
      a_options = {};

    if (typeof a_event == "string"){
      a_event = new fcf.Event(a_event, a_data);
    } else if (typeof a_data == "object") {
      a_event = a_event instanceof fcf.Event ? fcf.append(a_event, a_data)
                                             : fcf.append(new fcf.Event(), a_event, a_data);
    } else if (!(a_event instanceof fcf.Event)){
      a_event = fcf.append(new fcf.Event(), a_event);
    }

    if (!Array.isArray(a_event.eventItems)){
      a_event.eventItems = [];
    }

    if (!a_options._fromServer && fcf.application){
      a_event.eventServer = fcf.application.getConfiguration().serverName;
    }


    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;
    }

    let events            = (a_event.name in this._events) ? this._events[a_event.name].events : {};
    let info              = (a_event.name in this._events) ? this._events[a_event.name].info   : {};

    let lock = a_options.lock;
    locki = 0;
    if (typeof a_options.lock == "string") {
      if (a_options.lock == "global") {
        locki = 4;
      } else if (a_options.lock == "global_handler") {
        locki = 3;
      } else if (a_options.lock == "server") {
        locki = 2;
      } else if (a_options.lock == "server_handler") {
        locki = 1;
      }
    }
    ilocki = 0;
    if (typeof info.lock == "string") {
      if (info.lock == "global") {
        ilocki = 4;
      } else if (info.lock == "global_handler") {
        ilocki = 3;
      } else if (info.lock == "server") {
        ilocki = 2;
      } else if (info.lock == "server_handler") {
        ilocki = 1;
      }
    }
    if (ilocki > locki) {
      lock = info.lock;
      locki = ilocki;
    }

    let endpoints = undefined;

    if (typeof a_options.endpoints == "string"){
      endpoints = [{ behavior: endpoints }]
    } else if (typeof a_options.endpoints == "object" && !Array.isArray(a_options.endpoints)){
      endpoints = [a_options.endpoints]
    } else if (fcf.empty(a_options.endpoints)) {
      endpoints = [{ behavior: "auto" }]
    } else {
      endpoints = fcf.append(true, [], a_options.endpoints);
    }

    let innerCall           = false;
    let currentProcessPair  = fcf.isServer() ? fcf.application.getConfiguration().processPair : undefined;
    let currentHandlerName  = fcf.isServer() ? fcf.application.getConfiguration().handlerName : undefined;
    let currentServerName   = fcf.isServer() ? fcf.application.getConfiguration().serverName : undefined;

    fcf.each(endpoints, (a_key, a_endpoint)=>{
      if (typeof a_endpoint === "string"){
        a_endpoint = { behavior: a_endpoint };
        endpoints[a_key] = a_endpoint;
      }

      a_endpoint.directed = true;

      if (!a_endpoint.behavior || a_endpoint.behavior == "default"){
        a_endpoint.behavior = "server" in a_endpoint ||
                              "processPair" in a_endpoint ||
                              "handler" in a_endpoint
                              ? "directed"
                              : "auto";
      }
      if (a_endpoint.behavior == "inner") {
        a_endpoint.processPair = currentProcessPair;
        a_endpoint.handler = currentHandlerName;
        a_endpoint.server      = currentServerName;
      } else if (a_endpoint.behavior == "handler") {
        a_endpoint.processPair = "*";
        a_endpoint.handler = currentHandlerName;
        a_endpoint.server      = currentServerName;
      } else if (a_endpoint.behavior == "local") {
        a_endpoint.processPair = "*";
        a_endpoint.handler = "*";
        a_endpoint.server      = currentServerName;
      } else if (a_endpoint.behavior == "global") {
        a_endpoint.processPair = "*";
        a_endpoint.handler = "*";
        a_endpoint.server      = "*";
      } else if (a_endpoint.behavior == "auto") {
        a_endpoint.server       = (typeof info.global != "string" && info.global) || info.global === "global"  ? "*" : undefined;
        a_endpoint.processPair  = info.global ? "*" : undefined;
        a_endpoint.handler      = currentHandlerName;
        a_endpoint.directed     = false;
      }

      if (a_endpoint.processPair === undefined || a_endpoint.processPair === null)
        a_endpoint.processPair = currentProcessPair;
      if (a_endpoint.handler === undefined || a_endpoint.handler === null || a_endpoint.handler === "")
        a_endpoint.handler = currentHandlerName;
      if (a_endpoint.server === undefined || a_endpoint.server === null || a_endpoint.server === "")
        a_endpoint.server = currentServerName;

      innerCall |=  (!fcf.isServer()) ||
                    (
                      (a_options._fromServer || locki == 0) &&
                      (!a_options.active) &&
                      (!a_options.inner) &&
                      (
                        (a_endpoint.behavior == "inner") ||
                        (
                          (a_endpoint.server == "*" || a_endpoint.server == currentServerName) &&
                          (a_endpoint.handler == "*" || a_endpoint.handler == currentHandlerName) &&
                          (a_endpoint.processPair == "*" || a_endpoint.processPair == currentProcessPair)
                        )
                      )
                    );
    });

    let actions           = fcf.actions();
    let noactive          = locki != 0;

    if (innerCall) {
      let rm = [];
      for(let listenerId in events){
        let eventInfo = events[listenerId];
        if (!a_options._fromServer && eventInfo.active)
          continue;
        if (a_options._fromServer && a_options._ignoreNoActiveCalls && !eventInfo.active)
          continue;
        if (!a_options._directedCall &&
             eventInfo.global == "local" &&
             !!a_event.eventServer &&
             !!fcf.application && a_event.eventServer != fcf.application.getConfiguration().serverName)
          continue;
        noactive |= !eventInfo.active;
        actions.then(()=>{ return eventInfo.function(a_event); });
        if (eventInfo.once)
          rm.push(listenerId);
      }
      for(let i = 0; i < rm.length; ++i)
        delete this._events[a_event.name][rm[i]];
    }

    if (fcf.isServer() && !a_options._fromServer) {
      let endpointCount = fcf.count(endpoints);

      if (locki) {
        a_event._fcfLockId = "";
        for(var i = 0; i < 18; ++i) {
          let value = Math.round(Math.random()*127);
          if (!value)
            value = 1;
          a_event._fcfLockId += String.fromCharCode(value);
        }
        if (a_options.lockKey) {
          a_event._fcfLockKey = a_options.lockKey;
        }
      }

      fcf.each(endpoints, (a_key, a_endpoint)=>{
        let externalCall =  (locki != 0) ||
                            ( info.active || a_options.active ) ||
                            (
                              (a_endpoint.behavior != "inner") &&
                              (
                                (
                                  (a_endpoint.server != currentServerName) ||
                                  (a_endpoint.handler == "*" || a_endpoint.handler != currentHandlerName) ||
                                  (a_endpoint.processPair == "*" || a_endpoint.processPair != currentProcessPair)
                                ) ||
                                ( a_options.inner )
                              )
                            );
        if (!externalCall)
          return;

        let extenalActions = actions;
        if (a_options.noWaitExternal)
          extenalActions = fcf.actions();

        extenalActions.then(()=>{
          return fcf.application.sendEvent(a_event, {
                                                      id:                       a_options.id,
                                                      active:                   a_options.active || info.active,
                                                      noactive:                 locki == 0 && a_options.active ? false : noactive,
                                                      ignoreSelfNoActiveCalls:  !a_options.active && info.active && locki == 0,
                                                      error:                    a_options.error,
                                                      inner:                    a_options.inner,
                                                      sync:                     !!(a_options.sync | info.sync),
                                                      server:                   a_endpoint.server,
                                                      handler:                  a_endpoint.handler,
                                                      processPair:              a_endpoint.processPair,
                                                      directed:                 a_endpoint.directed,
                                                      lock:                     locki ? lock : false,
                                                      lockCounter:              locki ? endpointCount : false,
                                                    })
          .then((a_revent)=>{
            if (!a_options.noWaitExternal)
              a_event = a_revent;
          })
        });
      });
    }

    actions.then(()=>{ return a_event; });

    return actions;
  }

  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._attach = function(a_options) {
    if (!(a_options.name in this._events))
      this._events[a_options.name] = { info: {global: false, active: false, sync: false, lock: false }, events: {} };
    let globali;
    let global;
    let iglobal = a_options.global === true     ? "global" :
                  a_options.global === "global" ? "global" :
                  a_options.global === "local"  ? "local" :
                                                  false;
    if (a_options.global === true || a_options.global === "global") {
      global = "global";
      globali = 2;
    } else if (a_options.global === "local") {
      global = "local";
      globali = 1;
    } else {
      global = false;
      globali = 0;
    }

    let sglobal = this._events[a_options.name].info.global;
    let sglobali;
    if (sglobal == "global") {
      sglobali = 2;
    } else if (sglobal == "local") {
      sglobali = 1;
    } else {
      sglobali = 0;
    }

    if (globali > sglobali){
      sglobal = global;
    }

    locki = 0;
    if (a_options.lock == "global") {
      locki = 4;
    } else if (a_options.lock == "global_handler") {
      locki = 3;
    } else if (a_options.lock == "server") {
      locki = 2;
    } else if (a_options.lock == "server_handler") {
      locki = 1;
    }
    slock = this._events[a_options.name].info.lock;
    slocki = 0;
    if (slock == "global") {
      slocki = 4;
    } else if (slock == "global_handler") {
      slocki = 3;
    } else if (slock == "server") {
      slocki = 2;
    } else if (slock == "server_handler") {
      slocki = 1;
    }

    if (locki > slocki) {
      slock = a_options.lock;
    }

    this._events[a_options.name].info.global  = sglobal;
    this._events[a_options.name].info.lock    = slock;
    this._events[a_options.name].info.active  |= !!a_options.active;
    this._events[a_options.name].info.sync    |= !!a_options.sync;
    this._events[a_options.name].events[this._applyEventObjectId(a_options.function)] = {
      function: a_options.function,
      once:     a_options.once,
      global:   a_options.global,
      active:   a_options.active,
    };
  }

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

    let param = this._getParamIdName();
    if (!(param in a_obj))
      a_obj[param] = this._getId();

    return a_obj[param];
  }

  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("fcf_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("fcf_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, a_throw) => {
  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 conf)){
      if (a_throw || a_throw === undefined) {
        error = new fcf.Exception("ERROR_INCORRECT_DATA_TYPE", {type: type});
        return false;
      } else {
        type = "text";
      }
    }
    types.push({
      type: type,
      file: conf[type],
    });
  });

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

  if (fcf.isServer()){
    let modules = [];
    for(let index = 0; index < types.length; ++index){
      modules.push(fcf.NDetails._filters[types[index].type]);
    }
    return fcf.actions().then(()=>{ return modules; });
  }

  let typesMap = {};
  for(let index = 0; index < types.length; ++index){
    if (!fcf.NDetails._filters[types[index].type]){
      typesMap[types[index].type] = types[index];
    }
  }

  return fcf.actions()
  .asyncEach(typesMap, (a_index, a_info, a_res, a_act)=>{
    if (fcf.NDetails._filters[a_info.type]){
      a_act.complete();
      return;
    };
    fcf.requireEx([a_info.file], (a_error, a_module)=>{
      if (a_error){
        a_act.error(a_error);
      } else {
        if (!fcf.NDetails._filters[a_info.type]){
          fcf.NDetails._filters[a_info.type] = new a_module();
        }
        a_act.complete();
      }
    })
  })
  .then(()=>{
    let modules = [];
    for(let index = 0; index < types.length; ++index){
      modules.push(fcf.NDetails._filters[types[index].type]);
    }
    return modules;
  });
}

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 = function(a_uri, a_aliases, a_innerServerPath){
  if (typeof a_aliases !== "object") {
    a_innerServerPath = a_aliases;
    a_aliases = undefined;
  }

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

  if (a_uri.charAt(0) == "@"){
    let pos;
    if (a_innerServerPath){
      let plusPos = a_uri.indexOf("+");
      if (plusPos == -1)
        plusPos = Number.MAX_VALUE;
      let hooksExtPos = a_uri.indexOf(".hooks.js");
      if (hooksExtPos == -1)
        hooksExtPos = Number.MAX_VALUE;
      let wrapperExtPos = a_uri.indexOf(".wrapper.js");
      if (wrapperExtPos == -1)
        wrapperExtPos = Number.MAX_VALUE;
      let slashPos = a_uri.indexOf("/");
      if (slashPos == -1)
        slashPos = Number.MAX_VALUE;
      let bslashPos = a_uri.indexOf("\\");
      if (bslashPos == -1)
        bslashPos = Number.MAX_VALUE;
      pos = Math.min(plusPos, hooksExtPos, wrapperExtPos, slashPos, bslashPos);
      if (pos == Number.MAX_VALUE)
        pos = undefined;
    } else {
      let plusPos = a_uri.indexOf("+");
      if (plusPos == -1)
        plusPos = Number.MAX_VALUE;
      let hooksExtPos = a_uri.indexOf(".hooks.js");
      if (hooksExtPos == -1)
        hooksExtPos = Number.MAX_VALUE;
      let wrapperExtPos = a_uri.indexOf(".wrapper.js");
      if (wrapperExtPos == -1)
        wrapperExtPos = Number.MAX_VALUE;
      let slashPos = a_uri.indexOf("/");
      if (slashPos == -1)
        slashPos = Number.MAX_VALUE;
      let bslashPos = a_uri.indexOf("\\");
      if (bslashPos == -1)
        bslashPos = Number.MAX_VALUE;
      let argsPos = a_uri.indexOf("?");
      if (argsPos == -1)
        argsPos = Number.MAX_VALUE;
      let hashPos = a_uri.indexOf("#");
      if (hashPos == -1)
        hashPos = Number.MAX_VALUE;
      pos = Math.min(plusPos, hooksExtPos, wrapperExtPos, slashPos, bslashPos, argsPos, hashPos);
      if (pos == Number.MAX_VALUE)
        pos = undefined;
    }
    let prefix   = a_uri.substring(1, pos);
    let suffix   = pos !== undefined ? a_uri.substring(pos) : "";
    if (typeof a_aliases == "object" && prefix in a_aliases){
      a_uri = a_aliases[prefix] + suffix;
      if (!a_innerServerPath && a_aliases[prefix] == -1){
        a_uri = "/" + fcf.ltrim(a_uri, "/");
      }
    } else if (fcf.application && prefix in fcf.application.getConfiguration().aliases) {
      a_uri = fcf.application.getConfiguration().aliases[prefix] + suffix;
      if (!a_innerServerPath && fcf.application.getConfiguration().aliases[prefix].indexOf(":") == -1){
        a_uri = "/" + fcf.ltrim(a_uri, "/");
      }
    } else {
      throw new fcf.Exception("ERROR_GET_PATH", {path: a_uri});
    }
  }

  if (a_innerServerPath) {
    let winDSPos = a_uri.indexOf(":\\");
    if (a_uri[0] == "/" || (winDSPos != -1 && winDSPos < 2))
      return a_uri;
  } else {
    if (a_uri.indexOf("://") != -1)
      return a_uri;
  }

  let modulePos    = a_uri.indexOf(":");
  let moduleName   = modulePos != -1 ? a_uri.substring(0, modulePos) : "";
  let relativePath = modulePos != -1 ? a_uri.substring(modulePos + 1) : a_uri;
  if (moduleName.indexOf("/") != -1 || moduleName.indexOf("\\") != -1){
    throw new fcf.Exception("ERROR_GET_PATH_INVALID_MODULE_NAME", { path: a_uri});
  }
  if (moduleName != "") {
    if (a_innerServerPath){
      if (!fcf.settings.libraries || !(moduleName in fcf.settings.libraries)){
        throw new fcf.Exception("ERROR_GET_PATH_INVALID_MODULE_NAME", { path: a_uri});
      }
      a_uri = fcf.settings.libraries[moduleName].path + "/" + fcf.ltrim(relativePath, ["/", "\\", ":"]);
    } else {
      a_uri = "/fcfpackages/" + moduleName  + "/" + fcf.ltrim(relativePath, ["/", "\\", ":"]);
    }
  } else {
    if (a_innerServerPath){
      a_uri = process.cwd() + "/" + fcf.ltrim(a_uri, ["/", "\\", ":"]);
    } else {
      if (modulePos == 0){
        a_uri = "/" + fcf.ltrim(a_uri, ["/", "\\", ":"]);
      }
    }
  }

  return a_uri;
}

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.NDetails.messages = {};
fcf.addException = function(a_messageName, a_messageText) {
  fcf.NDetails.messages[a_messageName] = a_messageText;
}

fcf.Exception = class {
  constructor(a_nameOrMessageOrException, a_args, a_subEvent) {
    let exceptionName;
    let template;
    let stackTxt;
    let stackTxtStartLevel = 1;

    if (Array.isArray(a_args)) {
      for(var i = 0; i < a_args.length; ++i)
        this[i+1] = a_args[i];
    } else if (typeof a_args == "object"){
      for(let key in a_args)
        this[key] = a_args[key];
    }

    if (typeof a_nameOrMessageOrException == "string"){
      template = fcf.NDetails.messages[a_nameOrMessageOrException];
      if (!template) {
        exceptionName = "ERROR";
        template      = fcf.NDetails.messages[exceptionName];
        this.error    = a_nameOrMessageOrException;
      } else {
        exceptionName = a_nameOrMessageOrException;
      }
    } else if (a_nameOrMessageOrException instanceof Error) {
      exceptionName       = "ERROR";
      stackTxt            = a_nameOrMessageOrException.stack || a_nameOrMessageOrException.stacktrace;
      stackTxtStartLevel  = 0;
      template            = fcf.NDetails.messages[exceptionName];
      this.error          = a_nameOrMessageOrException.toString();
    } else if (typeof a_nameOrMessageOrException == "object"){
      template = fcf.NDetails.messages[a_nameOrMessageOrException.name];
      if (!template){
        if (a_nameOrMessageOrException._templateMessage){
          for(let key in a_nameOrMessageOrException)
            this[key] = a_nameOrMessageOrException[key];
          exceptionName = a_nameOrMessageOrException.name ? a_nameOrMessageOrException.name : "ERROR";
          template      = a_nameOrMessageOrException._templateMessage;
        } if (a_nameOrMessageOrException.message && (a_nameOrMessageOrException.stack || a_nameOrMessageOrException.stacktrace)){
          exceptionName       = "ERROR";
          stackTxt            = a_nameOrMessageOrException.stack || a_nameOrMessageOrException.stacktrace;
          stackTxtStartLevel  = 0;
          template            = fcf.NDetails.messages[exceptionName];
          this.error          = a_nameOrMessageOrException.message;
        } else {
          for(let key in a_nameOrMessageOrException)
            this[key] = a_nameOrMessageOrException[key];
          exceptionName = a_nameOrMessageOrException.name ? a_nameOrMessageOrException.name : "ERROR";
          template      = fcf.NDetails.messages["ERROR"];
          this.error    = a_nameOrMessageOrException.error    ? a_nameOrMessageOrException.error :
                          a_nameOrMessageOrException.message  ? a_nameOrMessageOrException.message :
                                                                "Unknown error";
        }
      } else {
        for(let key in a_nameOrMessageOrException)
          this[key] = a_nameOrMessageOrException[key];
        exceptionName = a_nameOrMessageOrException.name;
      }
    }

    this.name             = exceptionName;
    this._templateMessage = template;
    this.subEvent         = a_subEvent;
    if (!Array.isArray(this.stackArr)) {
      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, stackTxtStartLevel);
    }
    this.message          = undefined;
    this.toString();
  }

  toString(a_enableDefaultLanguage) {
    if (this._enableDefaultLanguage === a_enableDefaultLanguage && !!this.message){
      return this.message;
    }

    let lang    = a_enableDefaultLanguage && fcf.isServer() && fcf.application.getSystemVariable("fcf", "defaultLanguage") ? fcf.application.getSystemVariable("fcf", "defaultLanguage") : undefined;
    this.message = fcf.t(this._templateMessage, lang);
    this.message = fcf.tokenize(this.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    ");
      this.message += subText;
    }

    this._enableDefaultLanguage = a_enableDefaultLanguage;

    return this.message;
  }
}

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}}$");
fcf.addException("ERROR_GET_PATH",                                  "Failed resolve path '${{path}}$'");
fcf.addException("ERROR_GET_PATH_INVALID_MODULE_NAME",              "Failed resolve path '${{path}}$'. Invalid module name");
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_WRITE_FILE",                                "Failed to write the file ${{file}}$ (${{error}}$)");
fcf.addException("ERROR_COPY_FILE",                                 "Failed to copy '${{source}}$' to '${{destination}}$' : (${{error}}$)");
fcf.addException("ERROR_DELETE_FILE",                               "Failed to delete ${{file}}$ file: (${{error}}$)");
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_TEMPLATE_NOT_FOUND",                        "Template '@{{template}}@' not found");
fcf.addException("ERROR_TEMPLATE_MULTIPLE_CONTROL_IN_ATTR",         "Diferent types of control structures are used togethe with #{{}}# in th same atrrtibute in the '@{{template}}@' template");
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_NOT_SET_TEMPLATE_IN_VIEW",                  "Not set template for view [type:@{{type}}@]");
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_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(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.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    = [];
    if (a_options && a_options.context)
      fcf.setContext(a_options.context);
    this._state  = fcf.getState();
    this._error    = undefined;
    this._errorcbs = [];
    this._run      = false;
    this.result    = undefined;
  }

  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.setState(self._state);
            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.setState(self._state);
            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.setState(self._state);
    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.setState(self._state);
        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);
          })
        } else {
          if (!cbi.finally)
            self.result = a_value;
          self._execute();
        }
      },
      error: function(a_error) {
        fcf.setState(self._state);
        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);
        });
      } 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.cloneArgs = (a_args, a_ignoreSystemArgs, a_cloneData) => {
  let result = {};
  for(let key in a_args){
    if (a_ignoreSystemArgs && key.toString().indexOf("fcf") == 0){
      continue;
    }
    result[key] = a_cloneData ? fcf.clone(a_args[key]) : a_args[key];
  }
  return result;
}

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.template.charAt(0) == "+"){
    if (fcf.NDetails.currentTemplate){
      arg.template = fcf.NDetails.currentTemplate.split("+")[0] + arg.template;
    } else if (fcf.getContext().currentTemplate && fcf.getContext().currentTemplate.template){
      arg.template = fcf.getContext().currentTemplate.template.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 ? fcf.NServer.Application.getContext() :
           fcf.NServer && fcf.NServer.Application ? fcf.NServer.Application.getEmptyContext() :
                                                    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.getState = () => {
  if (fcf.isServer()){
    return fcf.NServer && fcf.NServer.Application ? fcf.NServer.Application.getState() :undefined;
  }
  return {context: fcf.getContext()};
}

fcf.setState = (a_state) => {
  if (fcf.isServer()){
    if (fcf.NServer && fcf.NServer.Application) {
      fcf.NServer.Application.setState(a_state)
    }
  } else {
    if (typeof a_state !== "object")
      a_state = undefined;
    fcf.setContext(a_state.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.processDomEvent = (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.processDomEvent(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" || typeof a_level === "string"){
    a_cb = a_level;
    a_level = 0;
  }

  if (typeof a_cb == "string"){
    let name = a_cb;
    a_cb = function(a_event){
      return a_owner[name](a_event);
    }
  }

  let recordId = fcf.id();

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

  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 (!(recordId in fcf.NDetails.domListenerCallbacks[fcfevntid][a_eventName][a_level]))
    fcf.NDetails.domListenerCallbacks[fcfevntid][a_eventName][a_level][recordId] = {};

  let ownerId;
  if (fcf.NClient && fcf.NClient.Wrapper && a_owner instanceof fcf.NClient.Wrapper) {
    ownerId = a_owner.getId();
  } else if ("___fcf___event_object_id" in a_owner) {
    ownerId = a_owner.___fcf___event_object_id;
  } else if (typeof a_owner == "object") {
    ownerId = fcf.id();
    a_owner.___fcf___event_object_id = ownerId;
  } else {
    ownerId = a_owner;
  }

  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][recordId] = {
    eventId: fcfevntid,
    eventName: a_eventName,
  };

  fcf.NDetails.domListenerCallbacks[fcfevntid][a_eventName][a_level][recordId] = {
    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.processDomEvent(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.processDomEvent(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.processDomEvent(this, a_eventName, event);
      });
    }
  }
}


fcf.removeDomListener = (a_owner, a_element, a_eventName) => {
  let ownerId;
  if (typeof a_owner === "string") {
    ownerId = a_owner;
  } else if (fcf.NClient && fcf.NClient.Wrapper && a_owner instanceof fcf.NClient.Wrapper) {
    ownerId = a_owner.getId();
  } else {
    ownerId = a_owner.___fcf___event_object_id;
  }
  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 recordId in fcf.NDetails.domListenerFeedback[ownerId][level]) {
      let eventPos = fcf.NDetails.domListenerFeedback[ownerId][level][recordId];
      if ((eventid === undefined || eventid == eventPos.eventId) &&
          (a_eventName === undefined || a_eventName == eventPos.eventName)) {
        delete fcf.NDetails.domListenerFeedback[ownerId][level][recordId];
        delete fcf.NDetails.domListenerCallbacks[eventPos.eventId][eventPos.eventName][level][recordId];
        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 recordId in fcf.NDetails.domListenerFeedback[id][level]) {
            let info = fcf.NDetails.domListenerFeedback[id][level][recordId];
            if (info.eventId === "window")
              continue;
            let element = fcf.select(`[fcfevntid="${info.eventId}"]`)[0];
            if (element)
              continue;
            delete fcf.NDetails.domListenerFeedback[id][level][recordId];
            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(){
    element = fcf.select(a_options.selector)[0];
    if (!element)
      return;
    if (!firstCall && !fcf.select("[id='" + id + "']")[0]) {
      window.removeEventListener("resize", resize);
      window.removeEventListener("scroll", resize);
      return;
    }
    firstCall = false;
    let rect = fcf.getRect(element)
    let absoluteOffset = fcf.getAbsoluteOffset(element);
    rect.left += absoluteOffset.left;
    rect.top += absoluteOffset.top;
    if (element != document.body){
      locker.style.left = rect.left+"px";
      locker.style.top  = rect.top+"px";
    } else {
      locker.style.left = "0px";
      locker.style.top  = "0px";
    }
    locker.style.width  = rect.width;
    locker.style.height = rect.height;
  }
  element.appendChild(locker);
  window.addEventListener("resize", resize);
  window.addEventListener("scroll", resize);
  resize();
  locker.resize = resize;

  return locker;
}

fcf.getAbsoluteOffset = (a_element)=>{
  let screenOffsetX = window.scrollX !== undefined ? window.scrollX : document.documentElement.scrollLeft;
  let screenOffsetY = window.scrollY !== undefined ? window.scrollY : document.documentElement.scrollTop;
  let offsetX = 0;
  let offsetY = 0;
  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"));
      let 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.getRect = (a_element, a_isNotRoot) => {
  if (a_element === document.body){
    let h = Math.max(a_element.scrollHeight, a_element.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight);
    let w = Math.max(a_element.scrollWidth, a_element.offsetWidth, document.documentElement.clientWidth, document.documentElement.scrollWidth, document.documentElement.offsetWidth);
    return {
      left: 0,
      right: w,
      top: 0,
      bottom: h,
      width: w,
      height: h
    };
  }

  let crect = a_element.getBoundingClientRect();
  let rect = {
    left: crect.left,
    right: crect.right,
    top: crect.top,
    bottom: crect.bottom,
    height: crect.height,
    width: crect.width
  };

  let styles = window.getComputedStyle(a_element);
  if (a_isNotRoot && (styles.position == "absolute" || styles.position == "sticky")){
    return {
      left:   Number.MAX_VALUE,
      right:  0,
      top:    Number.MAX_VALUE,
      bottom: 0,
    };
  }
  let screenOffsetX = window.scrollX !== undefined ? window.scrollX : document.documentElement.scrollLeft;
  let screenOffsetY = window.scrollY !== undefined ? window.scrollY : document.documentElement.scrollTop;
  if ((rect.height || rect.width) && styles.display !== "inline"){
    rect.left   += screenOffsetX;
    rect.right  += screenOffsetX;
    rect.top    += screenOffsetY;
    rect.bottom += screenOffsetY;
    return rect;
  }
  rect = {
    left:   rect.width ? rect.left : Number.MAX_VALUE,
    right:  rect.width ? rect.right : 0,
    top:    rect.height ? rect.top : Number.MAX_VALUE,
    bottom: rect.height ? rect.bottom : 0,
  };
  if (rect.left == Number.MAX_VALUE){
    rect.left = rect.right;
  } else {
    rect.left += screenOffsetX;
    rect.right += screenOffsetX;
  }
  if (rect.top == Number.MAX_VALUE){
    rect.top = rect.bottom;
  } else {
    rect.top += screenOffsetY;
    rect.bottom += screenOffsetY;
  }
  for(let i =0; i < a_element.children.length; ++i){
    let subrect = fcf.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));
  }
  rect.width = rect.right - rect.left;
  rect.height = rect.bottom - rect.top;
  return rect;
}

fcf.width = (a_element) =>{
  if (typeof a_element == "string")
    a_element = fcf.select(a_element)[0];
  let rect = fcf.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.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: ["fcf_watch_file"],
  });
  fcf.require(["fcf:NServer/Application.js"]);
} else {
  fcf.NDetails.eventChannel = new fcf.EventChannel();
}

})();
