fcf.module({
  name: "fcf:NClient/Wrapper.js",
  module: function(){
    var NClient = fcf.prepareObject(fcf, "NClient");

    var stUpdatableWrappers = {};
    var stUpdateCounter     = 0;
    var stUpdateActions     = undefined;
    var stThemeInfo         = {};

    function loadThemeInfo(a_name){
      if (!stThemeInfo[a_name]){
        stThemeInfo[a_name] = fcf.loadObject({
          path: "/fcfpackages/fcf/theme",
          get:  { theme: a_name }
        })
        .then((a_themeInfo)=>{
          stThemeInfo[a_name] = a_themeInfo;
        })
      }

      return fcf.actions()
      .then((a_res, a_act)=>{
        if (stThemeInfo[a_name] instanceof Promise || stThemeInfo[a_name] instanceof fcf.Actions) {
          stThemeInfo[a_name].then(()=>{
            a_act.complete(stThemeInfo[a_name]);
          })
        } else {
          a_act.complete(stThemeInfo[a_name]);
        }
      })
    }

    class Render {
      constructor(a_id){
        this.id = a_id;
        this.buffer = [];
        this._callMap = {};
      }

      write(a_context) {
        this.buffer.push(a_context);
      }

      template(a_options){
        let self = this;
        if (typeof a_options === "string"){
          a_options = {
            template: a_options,
            args: a_args,
          };
        }

        let wrapper = fcf.getWrapper(this.id);
        let cp      = this._getCallPosition();
        let child   = undefined;
        let childs  = wrapper.getChilds();
        for(let i = 0; i < childs.length; ++i){
          let childCP = childs[i].getArg("fcfCP");
          if (childCP == cp){
            child = childs[i];
            break;
          }
        }

        if (child) {
          this.buffer.push((async ()=>{
            let info = await child.update(true);
            return info.content;
          })());
        } else {
          this.buffer.push((async ()=>{
            let template = await fcf.application.render({
              template: a_options.template,
              parent:   self.id,
              args:     a_options.args,
            });
            return template.content;
          })());
        }
      }

      _getCallPosition(){
        var stackArr = (new Error).stack.split("\n");
        var strInfo  = fcf.trim(stackArr[0].indexOf(":") == -1 ? stackArr[3] : stackArr[2], [" ", ")"]);
        var callPostition = "";
        var dc = 0;
        if (strInfo.length) {
          var charCode0 = "0".charCodeAt(0);
          var charCode9 = "9".charCodeAt(0);
          for (var i = strInfo.length-1; i >= 0; --i) {
            var charCode = strInfo.charCodeAt(i);
            if (charCode >= charCode0 && charCode <= charCode9)
              continue;
            ++dc;
            if (dc >= 2){
              callPostition = strInfo.substr(i+1);
              break;
            }
          }
        }

        var cp = "block:"+callPostition;
        if (!(cp in this._callMap))
          this._callMap[cp] = 0;
        cp += "-" + (++this._callMap[cp]);
        return cp;
      }

      _build() {
        let self = this;
        let result={content: ""};
        let asyncActions = {};
        for(let i = 0; i < self.buffer.length; ++i){
          if (self.buffer[i] instanceof Promise || self.buffer[i] instanceof fcf.Actions)
            asyncActions[i] = self.buffer[i];
        }
        return fcf.actions()
        .asyncEach(asyncActions, (a_key, a_value)=>{
          return a_value.then((a_value)=>{
            self.buffer[a_key] = a_value;
          })
        })
        .then(()=>{
          for(let i = 0; i < self.buffer.length; ++i) {
            result.content += fcf.str(self.buffer[i]);
          }
          return result;
        })
      }
    };

    function redraw(a_id, a_template, a_theme, a_args, a_restoreState) {
      let wrapper = fcf.getWrapper(a_id);
      let element = wrapper ? wrapper.getDomElement() : undefined;
      if (!element)
        return fcf.actions().then(()=>{ return { } });
      let fcftemplate = element.getAttribute("fcftemplate");
      let fcfparent   = element.getAttribute("fcfparent");
      let fcfalias    = element.getAttribute("fcfalias");

      let ri        = fcf.NDetails.renderInstructions[a_template];
      let render    = new Render(a_id);
      let args      = a_args;
      return fcf.actions()
      .then(async (a_themeInfo)=>{
        let themeInfo = await loadThemeInfo(args.fcfTheme ? args.fcfTheme : "default");
        let tokenizeData = {
          args:   args, 
          route:  fcf.getContext().route,
          decor:  themeInfo.decor,
        };
        let sources = fcf.application.getLocalData().getSourceObject(a_id);
        let modifyKeys = fcf.application.getLocalData().getModifyKeys(a_id);
        if (!modifyKeys)
          modifyKeys = {};
        let depsKeys = {};
        for(let key in sources){
          if (!fcf.isArg(sources[key]) || sources[key].type != "value")
            continue;
          if (key in modifyKeys)
            continue;
          depsKeys[key] = {};
          let deps = fcf.getVariablesString(sources[key].value);
          for(let i = 0; i < deps.length; ++i)
            if (!depsKeys[key][deps[i]])
              depsKeys[key][deps[i]] = fcf.parseObjectAddress(deps[i]);
        }
        let orderKeys = [];
        let processedKeys = {};
        while(true) {
          let fill = true;
          for(let key in depsKeys) {
            let free = true;
            for(let dep in depsKeys[key]){
              let depArr = depsKeys[key][dep];
              if (depArr[0] != "args")
                continue;
              if (depArr[1] in depsKeys){
                free = false;
                break;
              }
            }
            if (free) {
              fill = false;
              orderKeys.push(key);
              delete depsKeys[key];
            }
          }
          if (fill){
            for(let key in depsKeys)
              orderKeys.push(key);
            break;
          }
        }

        for(let i =0; i < orderKeys.length; ++i){
          let key = orderKeys[i];
          if (fcf.isArg(sources[key]) && sources[key].type === "value")
            args[key] = fcf.tokenize(sources[key].value, tokenizeData);
        }
        ri.renderFunction(render, args, themeInfo.decor, fcf.getContext().route);
        let template = await render._build();

        let content = `<span fcftemplate="${fcftemplate}" id="${a_id}" `;
        if (fcfparent)
          content += `fcfparent="${fcfparent}" `;
        if (fcfalias)
          content += `fcfalias="${fcfalias}" `;
        let styles = "";
        let classes = "";
        if (args.fcfStyleInner)
          styles +=args.fcfStyleInner + "; ";
        if (args.fcfStyle)
          styles +=args.fcfStyle;
        if (args.fcfClassInner)
          classes +=args.fcfClassInner + " ";
        if (args.fcfClass)
          classes +=args.fcfClass;
        content += `class="${classes}" style="${styles}" >`;
        content += template.content;
        content += "</span>";

        return {content: content};
      })
    }

    function fillExternalRef(a_dst, a_rootId, a_id){
      let srcArgs = fcf.application.getLocalData().getSourceObject(a_id);
      if (srcArgs) {
        for(let key in srcArgs){
          if (!fcf.isArg(srcArgs[key]) || srcArgs[key].type !== "reference")
            continue;
          let objectValues = fcf.application.getLocalData().getObject(a_id);
          let parentWrapper   = fcf.getWrapper(a_id).getParent();
          let refParentObjectValues = parentWrapper ? fcf.application.getLocalData().getObject(parentWrapper.getId()) : undefined;
          let refId   = fcf.tokenize(srcArgs[key].object.id, {args: objectValues, parent: refParentObjectValues});
          let refArg  = fcf.tokenize(srcArgs[key].arg, {args: objectValues, parent: refParentObjectValues});
          let isInner = fcf.getWrapper(a_rootId).select("[id="+refId+"]").length != 0;
          if (isInner)
            continue;

          if (!a_dst[refId])
            a_dst[refId] = {};

          let refDst = fcf.resolveEx(a_dst[refId], refArg, true);
          let wrp = fcf.getWrapper(refId);
          if (wrp)
            refDst.object[refDst.key] = wrp.getArg(refArg);
          else
            refDst.object[refDst.key] = undefined;
        }
      }
    }

    function recalculate(a_id, a_template, a_theme, a_args, a_restoreState){
      let externalRefValues = {};
      let wrapper = fcf.getWrapper(a_id);


      if (a_restoreState) {
        var modifyChildArgs = {
          childs: {},
          args: {},
        };
        if (wrapper) {
          fillExternalRef(externalRefValues, a_id, a_id);

          var childs = wrapper.getChilds();
          function clFillChilds(a_wrapper, a_modifyChildArgs){
            var fcfSaveChildsState = a_wrapper.getArg("fcfSaveChildsState");
            if (fcfSaveChildsState === undefined)
              fcfSaveChildsState = true;

            if (fcfSaveChildsState == false){
              return;
            }

            fcf.each(a_wrapper.getChilds(), function(a_key, a_child){
              var fcfCallPosition = a_child.getArg("fcfCP");
              var id = a_child.getId();
              var modifyKeys = fcf.application.getLocalData().getModifyKeys(id);
              var args = {};

              fillExternalRef(externalRefValues, a_id, id);

              var sources = fcf.application.getLocalData().getSourceObject(id);
              for(var key in sources){
                if (modifyKeys && key in modifyKeys)
                  args[key] = sources[key];
              }

              for(var key in modifyKeys){
                var srcArg = fcf.append({}, fcf.application.getLocalData().getSourceObject(id));
                if(fcf.isArg(srcArg[key]) && srcArg[key].type == "url")
                  continue;
                if(fcf.isArg(srcArg[key]) && srcArg[key].type == "reference")
                  continue;
                if(fcf.isArg(srcArg[key]) && (srcArg[key].type != "programmable" && srcArg[key].important))
                  continue;
                args[key] = a_child.getArg(key);
              }
              args["fcfId"] = a_child.getId();
              var fcfevntid = a_child.getDomElement().getAttribute("fcfevntid");
              if (fcfevntid)
                args["fcfEvntid"] = fcfevntid;

              a_modifyChildArgs.childs[fcfCallPosition] = {
                childs: {},
                args: args,
              }
              clFillChilds(a_child, a_modifyChildArgs.childs[fcfCallPosition]);
            })
          }
          clFillChilds(wrapper, modifyChildArgs);
          a_args.fcfChildsArgs = modifyChildArgs;
        }
      } else {
        function clProcessChilds(a_wrapper){
          fillExternalRef(externalRefValues, a_id, a_wrapper.getId());
          fcf.each(a_wrapper.getChilds(), (a_key, a_child)=>{
            fillExternalRef(externalRefValues, a_id, a_child.getId());
          })
        }
        if (wrapper){
          clProcessChilds(wrapper);
        }
      }
      
      return fcf.application.render({
        template: a_template,
        theme:    a_theme,
        owner:    false,
        args:     a_args,
        update:   true,
        wrapper:  false,
        externalArgs: externalRefValues,
      });
    }

    function stUpdateWrapper(a_info) {

      if (a_info.calcOnly){
        let func = a_info.redraw ? redraw : recalculate;
        return func(a_info.id, a_info.template, a_info.theme, a_info.args, a_info.restoreState)
        .then((a_template)=>{
          a_info.cb(undefined, a_template, true); 
        })
        .catch((a_error) => {
          a_info.cb(a_error, undefined, true); 
        })
      }

      if (!stUpdateActions)
        stUpdateActions = fcf.actions();

      ++stUpdateCounter;
      var updateCounterLast = stUpdateCounter;
      stUpdatableWrappers[a_info.id] = a_info;

      function clUpdate() {
        if (updateCounterLast != stUpdateCounter){
          updateCounterLast = stUpdateCounter;
          setTimeout(clUpdate, 1);
          return;
        }

        var wrappers = stUpdatableWrappers;
        stUpdatableWrappers = {};

        var rm = {};
        function clFilter(a_ownerId, a_wrapper) {
          var chlids = a_wrapper.getChilds();
          for(var k in chlids) {
            var id = chlids[k].getId();
            if (id in wrappers){
              if (wrappers[a_ownerId]) {
                fcf.append(wrappers[a_ownerId].cbs, wrappers[id].cbs);
                wrappers[a_ownerId].cbs.push(wrappers[id].cb);
              }
              rm[id] = true;
            }
            clFilter(a_ownerId, chlids[k]);
          }
        }
        for(var id in wrappers){
          var wrapper = fcf.getWrapper(id);
          if (!wrapper){
            rm[id] = true;
            continue;
          }
          clFilter(id, wrapper);
        }
        for(var id in rm)
          delete wrappers[id];

        updateCounterLast = 0;


        fcf.each(wrappers, function(rootId, info){
          stUpdateActions.then((a_res, a_act)=>{
            fcf.actions()
            .then(()=>{
              let scrollTop = document.body.scrollTop;
              let wrapper = fcf.getWrapper(rootId);
              wrapper.lock();
              let func = info.redraw ? redraw : recalculate;
              return func(rootId, info.template, info.theme, info.args, info.restoreState)
              .then((a_template)=>{
                wrapper.unlock();
                info.cb(undefined, a_template, true); 
                for(let i = 0; i < info.cbs.length; ++i) {
                  info.cbs[i](
                    undefined, 
                    {
                      state: {
                        page: a_template && a_template.state ? a_template.state.page : { header: {} },
                      }
                    }, 
                    false);
                }
                document.body.scrollTop = scrollTop;
              })
            })
            .catch((a_error)=>{
              wrapper.unlock();
              info.cb(a_error, undefined, true); 
              for(let i = 0; i < info.cbs.length; ++i) {
                info.cbs[i](
                  a_error, 
                  undefined, 
                  false);
              }
            })
            .finally(()=>{
              a_act.complete();
            })
          });
        });
      }

      setTimeout(clUpdate, 0);
    }

    var stReferences = {};

    function stAttachReferences(a_wrapper){
      let id = a_wrapper.getId();
      let srcArgs = fcf.application.getLocalData().getSourceObject(id);
      let rmMap = {};

      for(name in srcArgs) {
        var arg = srcArgs[name];
        if (fcf.isArg(arg) && arg.type == "reference") {
          if (arg.object.id.indexOf("${{") !== -1 || arg.object.id.indexOf("@{{")  !== -1 || arg.arg.indexOf("${{")  !== -1 || arg.arg.indexOf("@{{")  !== -1 )
            arg = fcf.tokenize(arg, {args: fcf.application.getLocalData().getObject(a_wrapper._id), route: fcf.getContext().route})

          var argPath = fcf.normalizeObjectAddress(arg.arg);
          var argName = fcf.parseObjectAddress(arg.arg)[0];

          if (stReferences[arg.object.id]) {
            if (!rmMap[arg.object.id] || !rmMap[arg.object.id][id]) {
              if (!rmMap[arg.object.id])
                rmMap[arg.object.id] = {};
              rmMap[arg.object.id][id] = true;
              for(let an in stReferences[arg.object.id]) {
                if (stReferences[arg.object.id][an][id]) {
                  delete stReferences[arg.object.id][an][id];
                }
              }
            }
          }

          if (!stReferences[arg.object.id])
            stReferences[arg.object.id] = {};
          if (!stReferences[arg.object.id][argName])
            stReferences[arg.object.id][argName] = {};
          if (!stReferences[arg.object.id][argName][id])
            stReferences[arg.object.id][argName][id] = {};
          stReferences[arg.object.id][argName][id][name] = argPath;

        }
      }
    }

    var argRecursion = 0;
    var callsRecursion = {};



    /**
    * @class fcf::NClient::Wrapper
    *
    *lng_en @brief <b>[client only]</b> Wrapper template on the client (module: fcf:NClient/Wrapper.js)
    *lng_en @details An instance of a class is bound to an HTML template to handle events and animate it :-)
    *
    *lng_ru @brief <b>[client only]</b> Врапер шаблона на клиенте (module: fcf:NClient/Wrapper.js)
    *lng_ru @details Экземпляр класса привязывается к HTML шаблону  для обработки событий и его оживления :-)
    **/
    class Wrapper {
      /**
      * @fn constructor(object a_initializeOptions)
      *
      *lng_en @brief Class constructor
      *lng_en @param object a_initializeOptions Initialization parameter
      *lng_en   - <b>DomElement</b>        domElement   Dom template element
      *lng_en   - <b>fcf::EventChannel</b> eventChannel the object of the message channel
      *lng_en   - <b>function</b> onReady(object a_error, fcf::NClient::Wrapper a_wrapper) The callback has completed initialization of the object
      *lng_en         - <b>object</b>                  a_error the error object is populated if an error occurs.
      *lng_en         - <b>fcf::NClient::Wrapper</b>   a_wrapper a pointer to itself
      *
      *lng_ru @brief Конструктор класа
      *lng_ru @param object a_initializeOptions Параметры инициализации
      *lng_ru   - <b>DomElement</b>        domElement   Dom элемент шаблона вафываыв
      *lng_ru   - <b>fcf::EventChannel</b> eventChannel объект канала сообщений
      *lng_ru   - <b>function</b> onReady(object a_error, fcf::NClient::Wrapper a_wrapper) Обратный вызов завершения инициализации объекта
      *lng_ru         - <b>object</b>                  a_error объект ошибки, заполняется если возникает ошибка.
      *lng_ru         - <b>fcf::NClient::Wrapper</b>   a_wrapper указатель на себя
      **/
      constructor(a_initializeOptions, a_ignoreReg){
        this._id                  = a_initializeOptions.domElement 
                                      ? a_initializeOptions.domElement.getAttribute("id")
                                      : undefined;
        this._initializeOptions   = a_initializeOptions;
        this._callPositionsSetArg = {};
        this._callPositionsCounter = 0;
        this._destroy             = false;

        if (!a_ignoreReg)
          fcf.NDetails._wrappers[this._id] = this;
      }

      /**
      * @fn void initialize()
      *
      *lng_en @brief Initializes an instance of an object
      *
      *lng_ru @brief Запускает инициализацию экземпляра объекта
      **/
      initialize() {
        let self = this;
        return fcf.actions()
        .then((a_res, a_act)=>{
          fcf.liven(undefined, this._id, function(a_error){
            if (a_error) {
              a_act.error(a_error);
              return;
            }

            self._attachInnerEvents();

            self.attach()
            .then(()=>{
              stAttachReferences(self);
              a_act.complete();
            })
            .catch((a_error)=>{
              a_act.error(a_error);
            })
          });
        });
      }

      _reattach() {
        let self = this;
        fcf.removeDomListener(this);
        return fcf.liven(undefined, this._id)
        .then(()=>{
          self._attachInnerEvents();
          stAttachReferences(self);
          return self.attach();
        })
      }

      update(a_calcOnly) {
        return this.redraw(a_calcOnly);
      }

      redraw(a_calcOnly) {
        let self = this;
        if (!this.getDomElement())
          return fcf.actions().then(()=>{ return { page: { header: {} } }; });
        let alias         = this.getDomElement().getAttribute("fcfalias");
        let template      = this.getDomElement().getAttribute("fcftemplate");
        let args          = this.getArgs();
        if (!fcf.NDetails.renderInstructions[template]){
          return this.recalculate();
        }

        return fcf.actions()
        .then((a_res, a_act)=>{
          let updateInfo = {
            id:           self.getId(),
            template:     template,
            args:         args,
            update:       true,
            restoreState: false,
            redraw:       true,
            wrapper:      false,
            calcOnly:     a_calcOnly,
            cbs: [],
            cb: function(a_error, a_template, a_rootCallback){
              if (!a_rootCallback || a_calcOnly){
                a_act.complete({ content: a_template.content, page: a_template && a_template.state && a_template.state.page ? a_template.state.page : { header: {} } });
                return;
              }

              let domElement = self.getDomElement();
              if (!domElement){
                a_act.complete({ content: a_template.content, page: a_template && a_template.state && a_template.state.page ? a_template.state.page : { header: {} }  });
                return;
              }

              let activeElement = document.activeElement;
              let activeElementTag = activeElement.tagName;
              let wrapper = fcf.getWrapper(activeElement);
              let wrapperId = wrapper ? wrapper.getId() : undefined;
              let focusElementNumber = -1;
              let selectionStart = 0;

              if (wrapperId == self.getId() || self.select("[id='" + wrapperId + "']").length){
                selectionStart = activeElement.selectionEnd;
                let elements = self.select(activeElementTag); 
                for(let i = 0; i < elements.length; ++i) {
                  ++focusElementNumber;
                  if (elements[i] == activeElement)
                    break;
                }
              }

              domElement.outerHTML = a_template.content;

              if (focusElementNumber != -1) {
                let elements = self.select(activeElementTag); 
                for(let i = 0; i < elements.length; ++i) {
                  if (i == focusElementNumber){
                    if (!fcf.empty(selectionStart))
                      elements[i].selectionStart = selectionStart;
                    elements[i].focus();
                    break;
                  }
                }
              }

              fcf.each(fcf.select(self.getDomElement(), "script"), (a_key, a_script)=>{
                eval(a_script.innerHTML);
              });

              fcf.liven(domElement, ()=>{
                a_act.complete({ content: a_template.content, page: a_template && a_template.state && a_template.state.page ? a_template.state.page : { header: {}  } });
              });
            }
          };
          stUpdateWrapper(updateInfo);
        });
      }

      recalculate(a_calcOnly) {
        let self = this;
        if (!this.getDomElement())
          return fcf.actions().then(()=>{ return { page: { header: {} } }; });
        var alias      = this.getDomElement().getAttribute("fcfalias");
        var template   = this.getDomElement().getAttribute("fcftemplate");
        var parentId   = this.getDomElement().getAttribute("fcfparent");
        if (parentId === null)
          parentId = undefined;
        var id         = this.getDomElement().getAttribute("id");
        var args       = fcf.append({}, fcf.application.getLocalData().getSourceObject(id));
        fcf.each(fcf.application.getLocalData().getModifyKeys(id), function(a_key){
          let srcArg = fcf.application.getLocalData().getSourceItem(id, a_key);
          if (fcf.isArg(srcArg) && srcArg.type == "url")
            return;
          args[a_key] = fcf.application.getLocalData().getItem(id, a_key);
        });
        if (!fcf.empty(alias))
          args.fcfAlias = alias;
        args.fcfId = id;
        args.fcfParent = parentId;
        if (this.getDomElement().getAttribute("fcfevntid"))
          args.fcfEvntid = this.getDomElement().getAttribute("fcfevntid");

        return fcf.actions()
        .then((a_res, a_act)=>{
          var updateInfo = {
            id: self.getId(),
            template: template,
            args: args,
            update: true,
            restoreState: true,
            wrapper: false,
            calcOnly: a_calcOnly,
            cbs: [],
            cb: function(a_error, a_template, a_rootCallback){
              if (!a_rootCallback || a_calcOnly){
                a_act.complete({content: a_template.content, page: a_template.state.page});
                return;
              }

              var domElement = self.getDomElement();
              if (!domElement){
                a_act.complete({content: a_template.content, page: a_template.state.page});
                return;
              }

              let activeElement = document.activeElement;
              let activeElementTag = activeElement.tagName;
              let wrapper = fcf.getWrapper(activeElement);
              let wrapperId = wrapper ? wrapper.getId() : undefined;
              let focusElementNumber = -1;
              let selectionStart = 0;

              if (wrapperId == self.getId() || self.select("[id='" + wrapperId + "']").length){
                selectionStart = activeElement.selectionEnd;
                let elements = self.select(activeElementTag); 
                for(let i = 0; i < elements.length; ++i) {
                  ++focusElementNumber;
                  if (elements[i] == activeElement)
                    break;
                }
              }

              domElement.outerHTML = a_template.content;

              if (focusElementNumber != -1) {
                let elements = self.select(activeElementTag); 
                for(let i = 0; i < elements.length; ++i) {
                  if (i == focusElementNumber){
                    if (!fcf.empty(selectionStart))
                      elements[i].selectionStart = selectionStart;
                    elements[i].focus();
                    break;
                  }
                }
              }

              fcf.each(fcf.select(self.getDomElement(), "script"), (a_key, a_script)=>{
                eval(a_script.innerHTML);
              });

              fcf.liven(domElement, ()=>{
                a_act.complete({content: a_template.content, page: a_template.state.page});
              });
            }
          };
          stUpdateWrapper(updateInfo);
        });
      }

      refresh() {
        let self = this;
        if (!this.getDomElement())
          return fcf.actions().then(()=>{ return { page: { header: {} } }; });

        let alias      = this.getDomElement().getAttribute("fcfalias");
        let template   = this.getDomElement().getAttribute("fcftemplate");
        let parentId   = this.getDomElement().getAttribute("fcfparent");
        if (parentId === null)
          parentId = undefined;
        let id         = this.getDomElement().getAttribute("id");
        let args       = {};
        let sources    = fcf.application.getLocalData().getSourceObject(id);
        let originSourcesKeys = fcf.application.getLocalData().getOriginSourcesKeys(id);
        for(let k in originSourcesKeys)
          args[k] = sources[k];

        let originData = fcf.application.getLocalData().getOriginObject(this.getId());
        for(let key in originData) 
          args[key] = originData[key]; 

        if (!fcf.empty(alias))
          args.fcfAlias = alias;
        args.fcfId = id;
        args.fcfParent = parentId;
        if (this.getDomElement().getAttribute("fcfevntid"))
          args.fcfEvntid = this.getDomElement().getAttribute("fcfevntid");

        return fcf.actions()
        .then((a_res, a_act)=>{
          let updateInfo = {
            id: self.getId(),
            template: template,
            args: args,
            update: true,
            restoreState: false,
            wrapper: false,
            cbs: [],
            cb: function(a_error, a_template, a_rootCallback){
              if (!a_rootCallback){
                a_act.complete({page: a_template.state.page});
                return;
              }
                

              let domElement = self.getDomElement();
              if (!domElement){
                a_act.complete({page: a_template.state.page});
                return;
              }

              domElement.outerHTML = a_template.content;

              fcf.each(fcf.select(self.getDomElement(), "script"), (a_key, a_script)=>{
                eval(a_script.innerHTML);
              });

              fcf.liven(domElement, ()=>{
                a_act.complete({page: a_template.state.page});
              });
            }
          };
          stUpdateWrapper(updateInfo);
        });
      }

      reload() {
        let self = this;
        if (!this.getDomElement())
          return fcf.actions().then(()=>{ return { page: { header: {} } }; });

        let alias      = this.getDomElement().getAttribute("fcfalias");
        let template   = this.getDomElement().getAttribute("fcftemplate");
        let parentId   = this.getDomElement().getAttribute("fcfparent");
        if (parentId === null)
          parentId = undefined;
        let id         = this.getDomElement().getAttribute("id");
        let args       = {};
        let sources    = fcf.application.getLocalData().getSourceObject(id);
        let originSourcesKeys = fcf.application.getLocalData().getOriginSourcesKeys(id);
        for(let k in originSourcesKeys)
          args[k] = sources[k];
        if (!fcf.empty(alias))
          args.fcfAlias = alias;
        args.fcfId = id;
        args.fcfParent = parentId;
        if (this.getDomElement().getAttribute("fcfevntid"))
          args.fcfEvntid = this.getDomElement().getAttribute("fcfevntid");

        return fcf.actions()
        .then((a_res, a_act)=>{
          let updateInfo = {
            id: self.getId(),
            template: template,
            args: args,
            update: true,
            restoreState: false,
            wrapper: false,
            cbs: [],
            cb: function(a_error, a_template, a_rootCallback){
              if (!a_rootCallback){
                a_act.complete({page: a_template.state.page});
                return;
              }
                

              let domElement = self.getDomElement();
              if (!domElement){
                a_act.complete({page: a_template.state.page});
                return;
              }

              domElement.outerHTML = a_template.content;

              fcf.each(fcf.select(self.getDomElement(), "script"), (a_key, a_script)=>{
                eval(a_script.innerHTML);
              });

              fcf.liven(domElement, ()=>{
                a_act.complete({page: a_template.state.page});
              });
            }
          };
          stUpdateWrapper(updateInfo);
        });
      }

      destroy(){
        if (this._destroy)
          return;
        this._destroy = true;
        if (typeof this.onDestroy === "function")
          this.onDestroy();
        fcf.removeDomListener(this);
        var de = this.getDomElement();
        if (de)
          de.parentElement.removeChild(this.getDomElement());
        delete stReferences[this.getId()];
        delete fcf.NDetails._wrappers[this.getId()];
      }

      emit(a_event, a_exData){
        if (typeof a_event == "string"){
          let event = document.createEvent('Event');
          event.initEvent(a_event, true, true);
          event.name = a_event;
          if (!event.type)
            event.type = a_event;
          a_event = event;
        }
        if (a_exData)
          fcf.append(a_event, a_exData);
        return fcf.emitDomEvent(this.getActionDomElement(), a_event);
      }

      send(a_data, a_inputFiles, a_cb){
        let self = this;
        return fcf.actions().then((a_res, a_act) => {
          const formData = new FormData();



          var filesAttributes = [];
          var counter = 0;
          fcf.each(a_inputFiles, function(k, input){
            let files = input instanceof File                                         ? [input] :
                        input instanceof HTMLElement && fcf.isEnumerable(input.files) ? input.files :
                                                                                        [];
            for(var i = 0; i < files.length; ++i){
              formData.append('files[' + (counter++) + ']', files[i]);
              var attributes = {};
              if (input instanceof HTMLElement)
                fcf.each(input.attributes, function(k, attr){ attributes[attr.name] = attr.value; });
              filesAttributes.push(attributes);
            }
          });

          function processFileVariables(a_object, a_path){
            for(let key in a_object) {
              if (typeof a_object !== "object" || !a_object)
                continue;
              let files = undefined;
              if (a_object[key] instanceof File)
                files = [a_object[key]];
              else if (a_object[key] instanceof HTMLElement)
                files = a_object[key].files;

              if (files) {
                let strPath = "";
                let path = [...a_path,key];
                for(let i= 0; i < path.length; ++i)
                  strPath += '["' + path[i] + '"]';

                for(var i = 0; i < files.length; ++i){
                  formData.append('files[' + (counter++) + ']', files[i]);
                  var attributes = {};
                  if (a_object[key] instanceof HTMLElement)
                    fcf.each(a_object[key].attributes, function(k, attr){ attributes[attr.name] = attr.value; });
                  attributes.objectPath = strPath;
                  filesAttributes.push(attributes);
                }
                a_object[key] = {};
              } else {
                let newPath = [...a_path, key];
                processFileVariables(a_object[key], newPath);
              }
            }
          }
          processFileVariables(a_data, []);

          formData.append('filesAttributes', JSON.stringify(filesAttributes));
          formData.append('data', fcf.str(JSON.stringify(a_data)));

          fcf.load({
            path: "@url:fcfReceive?template=" + encodeURIComponent(self.getTemplate()),
            post: formData,
            onResult: function(a_error, a_response){
              if (a_cb)
                a_cb(a_error, a_response);
              if (a_error) {
                fcf.application.getEventChannel().send("error", {action: "method_send", error: a_error});
                a_act.error(a_error);
              } else {
                try {
                  a_response = a_response == "undefined" ? undefined : JSON.parse(a_response);
                } catch(e){ }
                a_act.complete(a_response);
              }
            }
          })
        });
      };

      getTemplate(){
        return this.getDomElement().getAttribute("fcftemplate");
      }

      getId(){
        return this._id;
      }

      getAlias() {
        return this.getDomElement().getAttribute("fcfalias");
      }

      getDomElement(){
        return this != fcf.application.getRootWrapper() ? document.getElementById(this._id) : document.body;
      }

      getActionDomElement(){
        return this.getDomElement();
      }

      getParent(){
        if (!this.getDomElement())
          return;
        var parentId = this.getDomElement().getAttribute("fcfparent");
        if (fcf.empty(parentId))
          return this != fcf.application.getRootWrapper() ? fcf.application.getRootWrapper() : undefined;
        return fcf.getWrapper(parentId);
      }

      getChilds() {
        var domChilds = fcf.select(document.getElementById(this._id), "[fcfparent=" + this._id + "]");
        var result = [];
        for(var i = 0; i < domChilds.length; ++i) {
          var wrapper = fcf.NDetails._wrappers[domChilds[i].getAttribute("id")];
          if (wrapper)
            result.push(wrapper);
        }
        return result;
      }

      getFirstChild() {
        var domChilds = fcf.select(document.getElementById(this._id), "[fcfparent=" + this._id + "]");
        for(var i = 0; i < domChilds.length; ++i) {
          var wrapper = fcf.NDetails._wrappers[domChilds[i].getAttribute("id")];
          return wrapper;
        }
        return;
      }

      getChild(a_alias) {
        var domChild = fcf.first(fcf.select(document.body, "[fcfparent=\"" + this._id + "\"][fcfalias=\"" + fcf.encodeHtml(a_alias) + "\"]"));
        if (!domChild)
          return;
        return fcf.NDetails._wrappers[domChild.getAttribute("id")];
      }

      isChild(a_wrapper){
        a_wrapper = a_wrapper.getParent();
        while(a_wrapper) {
          if (a_wrapper == this)
            return true;
          a_wrapper = a_wrapper.getParent();
        }
        return false;
      }

      isOwn(a_wrapper){
        return this.isChild(a_wrapper) || a_wrapper === this;
      }

      select(a_selector){
        return fcf.select(this.getDomElement(), a_selector);
      }

      processWritenValue(a_valuesInfo, a_defaultLanguage){
        if (this.getArg("translate"))
          return;
        for(let lang in a_valuesInfo)
          if (lang != a_defaultLanguage)
            a_valuesInfo[lang].ignore = true;
      }

      getArg(a_key){
        var source = fcf.application.getLocalData().getSourceItem(this._id, a_key);
        if (fcf.isArg(source) && source.type === "reference"){
          var args = { args: fcf.application.getLocalData().getObject(this._id) }
          source = fcf.tokenize(source, args);

          var dataOwner = fcf.getWrapper(source.object.id);
          if (!source)
            return fcf.application.getLocalData().getItem(this._id, a_key);
          return dataOwner.getArg(source.arg);
        } else {
          return fcf.application.getLocalData().getItem(this._id, a_key);
        }
      }

      getArgs() {
        let self = this;
        let result = {};
        fcf.each(fcf.application.getLocalData().getSourceObject(this._id), (a_key)=>{
          result[a_key] = self.getArg(a_key);
        });
        fcf.each(fcf.application.getLocalData().getModifyKeys(this._id), (a_index, a_key)=>{
          if (!(a_key in result))
            result[a_key] = self.getArg(a_key);
        });
        return result;
      }

      getShortArgs() {
        return fcf.application.getLocalData().getObject(this._id);
      }

      setArg(a_key, a_value, a_ignoreCallbacks, a_innerCall, a_editor, a_ignoreURLArgs){
        if (!a_editor)
          a_editor = this;

        let stack = (new Error()).stack;
        if (stack === undefined){
          try {
            throw new Error();
          } catch(e) {
            stack = e.stack;
          }
        }
        let pos = stack.indexOf("\n", 0);
        pos = stack.indexOf("\n", pos+1);
        let posEnd = stack.indexOf("\n", pos+1);
        let callPosition = fcf.trim(stack.substr(pos, posEnd-pos));

        this._setArgImpl(a_key, a_value, a_ignoreCallbacks, a_innerCall, callPosition, a_editor, a_ignoreURLArgs);
      }

      validate(a_value, a_options, a_dsterros) {
        let childs = this.getChilds();
        for (let i = 0; i < childs.length; ++i)
          childs[i].validate(typeof a_value == "object" ? a_value[childs[i].getAlias()] : undefined, a_options, a_dsterros);
      }

      check() {
        let value = this.getArg("value");
        let errors = [];
        this.validate(value, {}, errors);
        return errors;
      }

      lock(){
        this._locker = fcf.locker({selector: "#" + this.getId(), class: "fcf-update"});
        let div    = document.createElement("div");
        div.innerHTML = `<center><img style="max-width: 100%" src="${fcf.getPath("fcf:styles/wait.gif")}"></center>`;
        this._locker.insertBefore(div, this._locker.firstChild);

        let rectb = this._locker.getBoundingClientRect();
        if (rectb.y < 0){
          div.style.top = "0px";
          div.style.position = "sticky";
        }

      }

      unlock(){
        if (this._locker) {
          this._locker.remove();
          this._locker = undefined;
        }
      }

      attach(){
        return fcf.actions(); 
      }

      _getOriginArgs() {
        let self = this;
        let result = {};
        fcf.each(fcf.application.getLocalData().getSourceObject(this._id), (a_key)=>{
          result[a_key] = self._getOriginArg(a_key);
        });
        return result;
      }

      _getOriginArg(a_key){
        var source = fcf.application.getLocalData().getSourceItem(this._id, a_key);
        if (fcf.isArg(source) && source.type === "reference"){
          var args = { args: fcf.application.getLocalData().getOriginObject(this._id) }
          source = fcf.tokenize(source, args);

          var dataOwner = fcf.getWrapper(source.object.id);
          if (!source)
            return fcf.application.getLocalData().getOriginItem(this._id, a_key);
          return dataOwner._getOriginArg(source.arg);
        } else {
          return fcf.application.getLocalData().getOriginItem(this._id, a_key);
        }
      }

      _setArg(a_key, a_value, a_ignoreCallbacks){
        this.setArg(a_key, a_value, a_ignoreCallbacks, true, this);
      }

      _setArgImpl(a_key, a_value, a_ignoreCallbacks, a_innerCall, a_callPosition, a_editor, a_ignoreURLArgs) {
        let self = this;

        if (a_callPosition in this._callPositionsSetArg)
          return
        ++this._callPositionsCounter;
        this._callPositionsSetArg[a_callPosition] = true
        

        fcf.application.getLocalData().setItem(this._id, a_key, a_value);

        if (a_ignoreCallbacks){
          --this._callPositionsCounter;
          if (!this._callPositionsCounter)
            this._callPositionsSetArg = {};
          return;
        }

        let eventMap = {};

        let clGetArgInfo = (a_id, a_key) => {
          let parts = fcf.parseObjectAddress(a_key);
          let name = parts[0];
          let suffix = "";
          let source = fcf.application.getLocalData().getSourceItem(a_id, name);
          for(var i = 1; i < parts.length; ++i)
            suffix += '["' + parts[i] + '"]';

          if (fcf.isArg(source) && source.type == "reference"){
            if (source.object.id.indexOf("${{") !== -1 || source.object.id.indexOf("@{{")  !== -1 || source.arg.indexOf("${{")  !== -1 || source.arg.indexOf("@{{")  !== -1 ){
              source = fcf.tokenize(source, {args: fcf.application.getLocalData().getObject(a_id), route: fcf.getContext().route})
            }
          }

          return {
            path:   fcf.normalizeObjectAddress(a_key),
            arg:    name,
            suffix: suffix,
            id:     a_id,
            source: source,
            requestPath: "",
          };
        }


        let clProcessLink = (a_id, a_key, a_value, a_req, a_eventMap, a_protectDublicattes, a_isRoot, a_upDirection) => {
          a_key = fcf.normalizeObjectAddress(a_key);
          if (a_req[a_id + " -> " + a_key])
            return;
          a_req[a_id + " -> " + a_key] = true;

          let argInfo = clGetArgInfo(a_id, a_key);

          //if (!a_protectDublicattes[a_id + argInfo.arg] || a_protectDublicattes[a_id + argInfo.arg] < argInfo.suffix.length) {
          if (!(a_id + argInfo.arg in a_protectDublicattes)) {
            //a_protectDublicattes[a_id + argInfo.arg] = argInfo.suffix.length
            a_protectDublicattes[a_id + argInfo.arg] = true;
            let callInfo = {
              id:  a_id,
              arg: argInfo.arg,
              suffix: argInfo.suffix,
              value: fcf.application.getLocalData().getItem(a_id, argInfo.arg),
            };
            a_eventMap[a_id + "->"+ a_key] = callInfo;
            if(!a_ignoreURLArgs && fcf.isArg(argInfo.source) && argInfo.source.type == "url"){
              fcf.application.setUrlArg(argInfo.source.arg, callInfo.value, fcf.getWrapper(callInfo.id));
            }
          }

          //going down on graph
          if (stReferences[a_id] && stReferences[a_id][argInfo.arg]){
            let refsInfo = stReferences[a_id][argInfo.arg];
            for(let childId in refsInfo) {
              for(let childArgPath in refsInfo[childId]) {
                let refChlidsPath = refsInfo[childId][childArgPath];
                if (argInfo.path.indexOf(refChlidsPath) == 0 || refChlidsPath.indexOf(argInfo.path) == 0){
                  let child = fcf.application.getLocalData().getSourceItem(childId, childArgPath);
                  let childArg = fcf.tokenize(child.arg, {args: fcf.application.getLocalData().getObject(a_id), route: fcf.getContext().route})
                  let childParts = fcf.parseObjectAddress(childArg);
                  childParts.shift()
                  let ownerParts = fcf.parseObjectAddress(argInfo.suffix);
                  let newSuffixParts = [];
                  let fullCopy = true; 
                  for(let i = 0; i < ownerParts.length; ++i){
                    if (i >= childParts.length)
                      newSuffixParts.push(ownerParts[i])
                  }
                  let newSuffix = "";
                  for(var i = 0; i < newSuffixParts.length; ++i)
                    newSuffix += "[\"" + newSuffixParts[i] + "\"]";
                  let value = fcf.application.getLocalData().getItem(childId, childArgPath + newSuffix);
                  clProcessLink(childId,  childArgPath + newSuffix, value, a_req, a_eventMap, a_protectDublicattes, false, false);
                }
              }
            }
          }

          if (argInfo.source === undefined)
            return;

          //going up on graph
          if (fcf.isArg(argInfo.source) && argInfo.source.type == "reference"){
            clProcessLink(argInfo.source.object.id, argInfo.source.arg + argInfo.suffix, a_value, a_req, a_eventMap, a_protectDublicattes, false, true);
          }
        }

      
        
        if (!(a_callPosition in callsRecursion)) {
          callsRecursion[a_callPosition] = true;
          ++argRecursion;
          clProcessLink(this._id, a_key, a_value, {}, eventMap, {}, true, true);
          a_key = fcf.normalizeObjectAddress(a_key);
          for(var i in eventMap){
            let id = eventMap[i].id;
            let eventInfo = eventMap[i];
            let wrp = fcf.getWrapper(id);
            if (!wrp)
              continue;
            try {
              if (wrp.onArg){
                wrp.onArg(eventInfo.arg, eventInfo.value, a_editor, a_innerCall && wrp == a_editor, eventInfo.suffix);
              }
            }
            catch(e){
              --this._callPositionsCounter;
              if (!this._callPositionsCounter)
                this._callPositionsSetArg = {};
              fcf.log.err("APPLICATION:Wrapper", e);
              throw e;
            }

            let method = "onArg" + eventInfo.arg.charAt(0).toUpperCase() + eventInfo.arg.slice(1);
            try {
              if (wrp[method])
                wrp[method](eventInfo.value, a_editor, a_innerCall && wrp == a_editor, eventInfo.suffix);
            } catch(e) {
              --this._callPositionsCounter;
              if (!this._callPositionsCounter)
                this._callPositionsSetArg = {};
              fcf.log.err("APPLICATION:Wrapper", e);
              throw e;
            }
          }

          --argRecursion;
          if (argRecursion == 0){
            callsRecursion = {};
          }
        }

        --this._callPositionsCounter;
        if (!this._callPositionsCounter)
          this._callPositionsSetArg = {};
      }


    };

    Wrapper.prototype._attachInnerEvents = function(){
      let self = this;
      // attach events by attributes
      // function clAttach(a_element){
      //   let fcfEvents = {};
      //   for(let i = 0; i < a_element.attributes.length; ++i){
      //     if(a_element.attributes[i].name.toLowerCase().indexOf("fcfevent") == 0) {
      //       fcfEvents[a_element.attributes[i].name.substr(8)] = a_element.attributes[i].value;
      //     }
      //   };

      //   for(let eventName in fcfEvents){
      //     let eventCode = fcfEvents[eventName];
      //     let options = {
      //       wrapper: self,
      //       parent: self.getParent(),
      //     };
      //     (function(options, eventCode){
      //       fcf.addDomListener(a_element, eventName, self, function(event){
      //         with (options) {
      //           return eval(eventCode);
      //         }
      //       }, true);
      //     })(options, eventCode)
      //   }
      //   for(var i = 0; i < a_element.children.length; ++i){
      //     if (a_element.children[i].getAttribute("fcftemplate"))
      //       continue;
      //     var r = clAttach(a_element.children[i]);
      //   }
      // }

      // clAttach(this.getDomElement());

      // attach events by arguments
      var templateData = this.getShortArgs();
      for(var key in templateData){
        if (typeof key !== "string")
          continue;
        if (key.indexOf("fcfEvent") !== 0)
          continue;

        var eventName = key.substr(8);
            eventName = eventName[0].toLowerCase() + eventName.substr(1);
        var eventCode = templateData[key];
        var options = {
          wrapper: self,
          parent: self.getParent(),
        };
        (function(eventName, eventCode, options){
          fcf.addDomListener(self.getActionDomElement(), eventName, self, function(event){
            with(options){
              return eval(eventCode);
            }
            return __result;
          }, true)
        })(eventName, eventCode, options);
      }

    }


    NClient.Wrapper = Wrapper;
    return NClient.Wrapper;

  }
});
