fcf.module({
  name: "fcf:NClient/LocalData.js",
  dependencies: [],
  module: function() {
    var NClient = fcf.prepareObject(fcf, "NClient");

    NClient.LocalData = function() {
      var self = this;
      this._data = {};
      this._dataKeys = {};
      this._originData = {};
      this._originSourcesKeys = {};
      this._sources = {};
      this._originSources = {};

      this.setOriginData = function(a_origin) {
        fcf.append(this._originData, a_origin);
      }

      this.setSourcesData = function(a_origin) {
        for(var id in a_origin){
          this._sources[id] = a_origin[id];
          if (!(id in this._originSources))
            this._originSources[id] =  fcf.append(true, {}, a_origin[id]);
          for(let key in a_origin[id]){
            if (fcf.isArg(a_origin[id][key]) && a_origin[id][key].modify){
              if (!this._dataKeys[id])
                this._dataKeys[id] = {};
              this._dataKeys[id][key] = key;
              if (!this._data[id])
                this._data[id] = {};
              this._data[id][key] = this._originData[id][key];
            }
          }
        }
      }

      this.setOriginSourcesKeysData = function(a_data) {
        this._originSourcesKeys = a_data;
      }

      this.setObject = function(a_objectId, a_data) {
        this._data[a_objectId] = a_data;
        if (!this._originData[a_objectId]){
          this._originData[a_objectId] = fcf.append(true, {}, a_data);
        }
      }

      this.setOriginSourcesKeys = function(a_objectId, a_data){
        this._originSourcesKeys[a_objectId] = a_data;
      }

      this.getOriginSourcesKeys = function(a_objectId){
        return this._originSourcesKeys[a_objectId];
      }

      this.setSourceObject = function(a_objectId, a_data) {
        this._sources[a_objectId] = a_data;
        if (!(a_objectId in this._originSources)){
          this._originSources[a_objectId] = fcf.append(true, {}, a_data);
          let obj = this._originSources[a_objectId];
          for(let key in obj){
            if (fcf.isArg(obj[key]) && obj[key].modify){
              if (!this._dataKeys[a_objectId])
                this._dataKeys[a_objectId] = {};
              this._dataKeys[a_objectId][key] = key;
              if (!this._data[a_objectId])
                this._data[a_objectId] = {};
              if (!(key in this._data[a_objectId]))
                this._data[a_objectId][key] = obj[key];
            }
          }
        }
      }

      this.setItem = function(a_objectId, a_item, a_value, a_doNotChangeState) {
        var parts = fcf.parseObjectAddress(a_item);
        var argName = parts[0];
        var argSuffix = "";
        var source = fcf.application.getLocalData().getSourceItem(a_objectId, argName);
        for(var i = 1; i < parts.length; ++i)
          argSuffix += '["' + parts[i] + '"]';

        if (fcf.isArg(source) && source.type === "reference"){
          var id = source.object.id;
          if (id.indexOf("@{{") !== -1 || id.indexOf("${{") !== -1){
            var args = fcf.application.getLocalData().getObject(a_objectId);
            id = fcf.tokenize(id, {args: args})
          }
          var path = source.arg + argSuffix;
          if (path.indexOf("@{{") !== -1 || path.indexOf("${{") !== -1){
            var args = fcf.application.getLocalData().getObject(a_objectId);
            path = fcf.tokenize(path, {args: args})
          }

          return this.setItem(id, path, a_value);
        }

        if (!this._data[a_objectId])
          this._data[a_objectId] = {};

        if (!a_doNotChangeState) {
          if (!this._dataKeys[a_objectId])
            this._dataKeys[a_objectId] = {};

          if (argSuffix && !(argName in this._data[a_objectId])){
            if (!this._originData[a_objectId])
              this._originData[a_objectId] = {};
            var orgPtr = fcf.resolveEx(this._originData[a_objectId], argName, true);
            var modPtr = fcf.resolveEx(this._data[a_objectId], argName, true);
            modPtr.object[modPtr.key] = fcf.clone(orgPtr.object[orgPtr.key]);
          }
        }

        itemPtr = fcf.resolveEx(this._data[a_objectId], a_item, true);
        itemPtr.object[itemPtr.key] = a_value;

        if (!a_doNotChangeState) {
          this._dataKeys[a_objectId][argName] = argName;
        }
      }

      this.removeObject = function(a_objectId) {
        delete this._originData[a_objectId];
        delete this._data[a_objectId];
        delete this._sources[a_objectId];
        delete this._originSources[a_objectId];
        delete this._originSourcesKeys[a_objectId];
      }

      this.getObject = function(a_objectId) {
        var dataObject = {};
        fcf.append(dataObject, this._originData[a_objectId]);

        fcf.each(this._dataKeys[a_objectId], function(a_key){
          var dstPtr = fcf.resolveEx(dataObject, a_key, true);
          var dataPtr = fcf.resolveEx(self._data[a_objectId], a_key);
          if (dataPtr.object && dataPtr.key in dataPtr.object)
            dstPtr.object[dataPtr.key] = dataPtr.object[dataPtr.key];
        });

        return dataObject;
      }

      this.setModifyKeys = function(a_objectId, a_argKey){
        if (!this._dataKeys[a_objectId])
          this._dataKeys[a_objectId] = {};
        this._dataKeys[a_objectId][a_argKey] = a_argKey;
      }

      this.getModifyKeys = function(a_objectId){
        return this._dataKeys[a_objectId];
      }

      this.clearModifyKeys = function(a_objectId){
        delete this._dataKeys[a_objectId];
      }


      this.getOriginObject = function(a_objectId) {
        return this._originData[a_objectId];
      }

      this.getSourceObject = function(a_objectId) {
        return this._sources[a_objectId];
      }

      this.getOriginSourceObject = function(a_objectId) {
        return this._originSources[a_objectId];
      }

      this.resetSources = function(){
        this._sources = fcf.append(true, {}, this._originSources)
      }

      this.cleanItem = function(a_objectId, a_item) {
        if (this._data[a_objectId]) {
          var itemPtr = fcf.resolveEx(this._data[a_objectId], a_item);
          if (itemPtr.object && itemPtr.key in itemPtr.object)
            delete itemPtr.object[itemPtr.key];
          if (this._dataKeys[a_objectId])
            delete this._dataKeys[a_objectId][a_item];
        }
      }

      this.getItem = function(a_objectId, a_item) {
        var parts = fcf.parseObjectAddress(a_item);
        var argName = parts[0];
        var argSuffix = "";
        var source = fcf.application.getLocalData().getSourceItem(a_objectId, argName);
        for(var i = 1; i < parts.length; ++i)
          argSuffix += '["' + parts[i] + '"]';

        if (fcf.isArg(source) && source.type === "reference"){
          var id = source.object.id;
          if (id.indexOf("@{{") !== -1 || id.indexOf("${{") !== -1){
            var args = fcf.application.getLocalData().getObject(a_objectId);
            id = fcf.tokenize(id, {args: args})
          }
          var path = source.arg + argSuffix;
          if (path.indexOf("@{{") !== -1 || path.indexOf("${{") !== -1){
            var args = fcf.application.getLocalData().getObject(a_objectId);
            path = fcf.tokenize(path, {args: args})
          }

          return this.getItem(id, path);
        }


        if (this._data[a_objectId]) {
          var originData = undefined;
          if (this._originData[a_objectId]) {
            var itemPtr = fcf.resolveEx(this._originData[a_objectId], a_item);
            if (itemPtr.object && itemPtr.key in itemPtr.object)
              originData = itemPtr.object[itemPtr.key];
          }

          var itemPtr = fcf.resolveEx(this._data[a_objectId], a_item);
          if (itemPtr.object && itemPtr.key in itemPtr.object){
            var val = itemPtr.object[itemPtr.key];
            if (Array.isArray(val)){
              return val;
            } else if (typeof val == "object"){
              return val;
            } else {
              return val;
            }
          }
        }
        if (this._originData[a_objectId]) {
          var itemPtr = fcf.resolveEx(this._originData[a_objectId], a_item);
          if (itemPtr.object && itemPtr.key in itemPtr.object)
            return itemPtr.object[itemPtr.key];
        }
      }

      this.getOriginItem = function(a_objectId, a_item) {
        var parts = fcf.parseObjectAddress(a_item);
        var argName = parts[0];
        var argSuffix = "";
        var source = fcf.application.getLocalData().getSourceItem(a_objectId, argName);
        for(var i = 1; i < parts.length; ++i)
          argSuffix += '["' + parts[i] + '"]';

        if (fcf.isArg(source) && source.type === "reference"){
          var id = source.object.id;
          if (id.indexOf("@{{") !== -1 || id.indexOf("${{") !== -1){
            var args = fcf.application.getLocalData().getOriginObject(a_objectId);
            id = fcf.tokenize(id, {args: args})
          }
          var path = source.arg + argSuffix;
          if (path.indexOf("@{{") !== -1 || path.indexOf("${{") !== -1){
            var args = fcf.application.getLocalData().getOriginObject(a_objectId);
            path = fcf.tokenize(path, {args: args})
          }

          return this.getOriginItem(id, path);
        }

        if (this._originData[a_objectId]) {
          var itemPtr = fcf.resolveEx(this._originData[a_objectId], a_item);
          if (itemPtr.object && itemPtr.key in itemPtr.object)
            return itemPtr.object[itemPtr.key];
        }
      }

      this.getSourceItem = function(a_objectId, a_item) {
        if (!(a_objectId in this._sources))
          return;
        if (this._sources[a_objectId][a_item])
          return this._sources[a_objectId][a_item]
        if (this._originSources[a_objectId] && this._originSources[a_objectId][a_item])
          return this._originSources[a_objectId][a_item];
      }
    }

    return NClient.LocalData;
  }
});
