<commit_before>/**
 * options:
 *  store
 *  copyStore
 *  cacheKeys
 *  localStorage
 * **/

const utils = require('./utils');
const array = require('./array');
const object = require('./object');
const patchMethods = require('./patch');
const PathListener = require('./PathListener');
const JSON_STORE_CACHE_KEY_PREFIX = 'JSON_STORE_CACHE_KEY_PREFIX';
const emptyFunc = () => {};

function JSONDataStore(options) {
  options = options || {};
  this.initialOptions = utils.copy(options);
  let store = options.store, copyStore = options.copyStore !== false;
  this.copyStore = copyStore;
  this.store = copyStore ? utils.copy(store) : store;
  this.cacheKeys = this._getStoreKeysMap(options.cacheKeys, this.store);
  this.flashKeys = this._getStoreKeysMap(options.flashKeys, this.store);
  this.cacheKeyPrefix = options.cacheKeyPrefix || JSON_STORE_CACHE_KEY_PREFIX;
  this.localStorage = options.localStorage;
  // 'do' about attributes
  this.patches = [];
  this.relativePatches = [];
  this.backPatches = [];
  this.currentPath = [];
  this.isDoing = false;
  this.pathListener = new PathListener({ store: this.store, copyStore: copyStore, flashKeys: this.flashKeys });
  this.initialMutationActionPath = [];
}

JSONDataStore.prototype = {
  _storeUpdated: function () {
    this._updateCache(this.initialMutationActionPath[0]);
    this.pathListener.checkPath(this.initialMutationActionPath);
  },
  _getStoreKeysMap: function (keys, store) {
    let keysMap = {};
    if(utils.type(keys) === 'array'){
      keys.forEach(key => {
        if(Object.hasOwnProperty.call(store, key)){
          keysMap[key] = true;
        }
      })
    }
    return keysMap;
  },
  _getRef: function (path) {
    let ref = this.store, i = 0, len = path.length;
    for(; i < len; i ++){
      ref = ref[path[i]];
    }
    return ref;
  },
  _detectPath: function (path) {
    let detected = [], ref = this.store, i = 0, len = path.length, key, keyType, refType;
    for(; i < len; i ++){
      key = path[i];
      keyType = utils.type(key);
      refType = utils.type(ref);
      if(refType === 'object'){
        if(object.hasOwnProperty.call(key, '__value')){
          let objKey = object.getObjectKeyByValue(ref, key.__value);
          if(objKey){
            ref = ref[objKey];
            detected.push(objKey);
          }else{
            return [];
          }
        }else if(object.hasOwnProperty.call(ref, key)){
          ref = ref[key];
          detected.push(key);
        }else{
          return [];
        }
      }else if(refType === 'array'){
        if(object.hasOwnProperty.call(key, '__value')){
          let index = array.getArrayIndexByValue(ref, key.__value);
          if(index > -1){
            ref = ref[index];
            detected.push(index);
          }else{
            return [];
          }
        }else if(object.hasOwnProperty.call(ref, key)){
          ref = ref[key];
          detected.push(key);
        }else{
          return [];
        }
      }
    }
    return detected;
  },
  _formatPath: function (path, detect) {
    let pathType = utils.type(path);
    if(pathType === 'undefined' || pathType === 'null'){
      path = [];
    }else if(pathType !== 'array'){
      path = [path];
    }
    if(detect !== false){
      let detected = this._detectPath(path);
      if(detected.length === path.length){
        return detected;
      }
      return null;
    }
    return path.slice();
  },
  _moveArrayItem: function (path, moveUp) {
    let fullPath = this._getFullPath(path);
    if(!fullPath || fullPath.length < 1) return this;
    let itemIndex = fullPath.pop(),
      arr = this._getRef(fullPath);
    if(utils.type(arr) !== 'array') return this;
    let method = moveUp === true ? 'createMoveUp' : 'createMoveDown',
      reverseMethod = method === 'createMoveUp' ? 'createMoveDown' : 'createMoveUp';
    if(this.isDoing){
      this.patches.push(patchMethods[method](fullPath.concat(itemIndex)));
      this.relativePatches.push(patchMethods[method](this._getRelativePath(fullPath.concat(itemIndex))));
      if((moveUp === true && itemIndex > 0)
      || (moveUp !== true && itemIndex < arr.length - 1)){
        this.backPatches.unshift(patchMethods[reverseMethod](fullPath.concat(moveUp === true ? itemIndex - 1 : itemIndex + 1)));
      }
    }
    if(moveUp === true){
      array.moveArrayItemUp(arr, itemIndex);
    }else {
      array.moveArrayItemDown(arr, itemIndex);
    }
    this._storeUpdated();
    return this;
  },
  _getFullPath: function (path) {
    if(utils.isReferenceType(path) && path.isFull){
      return path;
    }
    let currentPath = this._formatPath(this.currentPath, false),
      fullPath = currentPath.concat(this._formatPath(path, false)),
      formattedFullPath = this._formatPath(fullPath);
    if(formattedFullPath){
      formattedFullPath.isFull = true;
    }
    return formattedFullPath;
  },
  _getRelativePath: function (fullPath) {
    return fullPath.slice(this.currentPath.length);
  },
  _composeCacheKey: function (key) {
    return this.cacheKeyPrefix + '@' + key;
  },
  _updateCache: function (key) {
    if(this.cacheKeys[key] && this.localStorage && typeof this.localStorage.setItem === 'function'){
      this.localStorage.setItem(this._composeCacheKey(key), this.get(key));
    }
  },
  registerPathListener: function (path, callback, group, check) {
    path = Array.isArray(path) ? path : [path];
    this.pathListener.registerListener(path, callback, group, check);
  },
  removeListenerByPath: function (path, callback) {
    path = Array.isArray(path) ? path : [path];
    this.pathListener.removeListenerByPath(path, callback);
  },
  removeListenerByGroup: function (group) {
    this.pathListener.removeListenerByGroup(group);
  },
  removeAllListeners: function () {
    this.pathListener.removeAllListeners();
  },
  loadCache: function (success, error) {
    error = typeof error === 'function' ? error : emptyFunc;
    if(this.localStorage && typeof this.localStorage.multiGet === 'function'){
      let cacheKeys = this.initialOptions.cacheKeys || [];
      let composedKeys = cacheKeys.map(key => this._composeCacheKey(key));
      this.localStorage.multiGet(composedKeys, cache => {
        let parsedCache = {};
        composedKeys.forEach((composedKey, index) => {
          let key = cacheKeys[index];
          let cachedValue = cache[composedKey];
          this.set(key, cachedValue === null ? this.get(key) : cachedValue);
          parsedCache[key] = cache[composedKey];
        });
        success(parsedCache);
      }, error);
    }else{
      error('localStorage is undefined');
    }
  },
  reInit: function (options) {
    JSONDataStore.call(this, options || this.initialOptions);
    return this;
  },
  goTo: function (path, addUp) {
    if(!this.isDoing){<commit_msg>expose registerStart and registerEnd<commit_after>/**
 * options:
 *  store
 *  copyStore
 *  cacheKeys
 *  localStorage
 * **/

const utils = require('./utils');
const array = require('./array');
const object = require('./object');
const patchMethods = require('./patch');
const PathListener = require('./PathListener');
const JSON_STORE_CACHE_KEY_PREFIX = 'JSON_STORE_CACHE_KEY_PREFIX';
const emptyFunc = () => {};

function JSONDataStore(options) {
  options = options || {};
  this.initialOptions = utils.copy(options);
  let store = options.store, copyStore = options.copyStore !== false;
  this.copyStore = copyStore;
  this.store = copyStore ? utils.copy(store) : store;
  this.cacheKeys = this._getStoreKeysMap(options.cacheKeys, this.store);
  this.flashKeys = this._getStoreKeysMap(options.flashKeys, this.store);
  this.cacheKeyPrefix = options.cacheKeyPrefix || JSON_STORE_CACHE_KEY_PREFIX;
  this.localStorage = options.localStorage;
  // 'do' about attributes
  this.patches = [];
  this.relativePatches = [];
  this.backPatches = [];
  this.currentPath = [];
  this.isDoing = false;
  this.pathListener = new PathListener({ store: this.store, copyStore: copyStore, flashKeys: this.flashKeys });
  this.initialMutationActionPath = [];
}

JSONDataStore.prototype = {
  _storeUpdated: function () {
    this._updateCache(this.initialMutationActionPath[0]);
    this.pathListener.checkPath(this.initialMutationActionPath);
  },
  _getStoreKeysMap: function (keys, store) {
    let keysMap = {};
    if(utils.type(keys) === 'array'){
      keys.forEach(key => {
        if(Object.hasOwnProperty.call(store, key)){
          keysMap[key] = true;
        }
      })
    }
    return keysMap;
  },
  _getRef: function (path) {
    let ref = this.store, i = 0, len = path.length;
    for(; i < len; i ++){
      ref = ref[path[i]];
    }
    return ref;
  },
  _detectPath: function (path) {
    let detected = [], ref = this.store, i = 0, len = path.length, key, keyType, refType;
    for(; i < len; i ++){
      key = path[i];
      keyType = utils.type(key);
      refType = utils.type(ref);
      if(refType === 'object'){
        if(object.hasOwnProperty.call(key, '__value')){
          let objKey = object.getObjectKeyByValue(ref, key.__value);
          if(objKey){
            ref = ref[objKey];
            detected.push(objKey);
          }else{
            return [];
          }
        }else if(object.hasOwnProperty.call(ref, key)){
          ref = ref[key];
          detected.push(key);
        }else{
          return [];
        }
      }else if(refType === 'array'){
        if(object.hasOwnProperty.call(key, '__value')){
          let index = array.getArrayIndexByValue(ref, key.__value);
          if(index > -1){
            ref = ref[index];
            detected.push(index);
          }else{
            return [];
          }
        }else if(object.hasOwnProperty.call(ref, key)){
          ref = ref[key];
          detected.push(key);
        }else{
          return [];
        }
      }
    }
    return detected;
  },
  _formatPath: function (path, detect) {
    let pathType = utils.type(path);
    if(pathType === 'undefined' || pathType === 'null'){
      path = [];
    }else if(pathType !== 'array'){
      path = [path];
    }
    if(detect !== false){
      let detected = this._detectPath(path);
      if(detected.length === path.length){
        return detected;
      }
      return null;
    }
    return path.slice();
  },
  _moveArrayItem: function (path, moveUp) {
    let fullPath = this._getFullPath(path);
    if(!fullPath || fullPath.length < 1) return this;
    let itemIndex = fullPath.pop(),
      arr = this._getRef(fullPath);
    if(utils.type(arr) !== 'array') return this;
    let method = moveUp === true ? 'createMoveUp' : 'createMoveDown',
      reverseMethod = method === 'createMoveUp' ? 'createMoveDown' : 'createMoveUp';
    if(this.isDoing){
      this.patches.push(patchMethods[method](fullPath.concat(itemIndex)));
      this.relativePatches.push(patchMethods[method](this._getRelativePath(fullPath.concat(itemIndex))));
      if((moveUp === true && itemIndex > 0)
      || (moveUp !== true && itemIndex < arr.length - 1)){
        this.backPatches.unshift(patchMethods[reverseMethod](fullPath.concat(moveUp === true ? itemIndex - 1 : itemIndex + 1)));
      }
    }
    if(moveUp === true){
      array.moveArrayItemUp(arr, itemIndex);
    }else {
      array.moveArrayItemDown(arr, itemIndex);
    }
    this._storeUpdated();
    return this;
  },
  _getFullPath: function (path) {
    if(utils.isReferenceType(path) && path.isFull){
      return path;
    }
    let currentPath = this._formatPath(this.currentPath, false),
      fullPath = currentPath.concat(this._formatPath(path, false)),
      formattedFullPath = this._formatPath(fullPath);
    if(formattedFullPath){
      formattedFullPath.isFull = true;
    }
    return formattedFullPath;
  },
  _getRelativePath: function (fullPath) {
    return fullPath.slice(this.currentPath.length);
  },
  _composeCacheKey: function (key) {
    return this.cacheKeyPrefix + '@' + key;
  },
  _updateCache: function (key) {
    if(this.cacheKeys[key] && this.localStorage && typeof this.localStorage.setItem === 'function'){
      this.localStorage.setItem(this._composeCacheKey(key), this.get(key));
    }
  },
  registerPathListener: function (path, callback, group, check) {
    path = Array.isArray(path) ? path : [path];
    this.pathListener.registerListener(path, callback, group, check);
  },
  removeListenerByPath: function (path, callback) {
    path = Array.isArray(path) ? path : [path];
    this.pathListener.removeListenerByPath(path, callback);
  },
  removeListenerByGroup: function (group) {
    this.pathListener.removeListenerByGroup(group);
  },
  removeAllListeners: function () {
    this.pathListener.removeAllListeners();
  },
  registerStart: function () {
    this.pathListener.registerStart();
  },
  registerEnd: function (cb) {
    this.pathListener.registerEnd(cb);
  },
  loadCache: function (success, error) {
    error = typeof error === 'function' ? error : emptyFunc;
    if(this.localStorage && typeof this.localStorage.multiGet === 'function'){
      let cacheKeys = this.initialOptions.cacheKeys || [];
      let composedKeys = cacheKeys.map(key => this._composeCacheKey(key));
      this.localStorage.multiGet(composedKeys, cache => {
        let parsedCache = {};
        composedKeys.forEach((composedKey, index) => {
          let key = cacheKeys[index];
          let cachedValue = cache[composedKey];
          this.set(key, cachedValue === null ? this.get(key) : cachedValue);
          parsedCache[key] = cache[composedKey];
        });
        success(parsedCache);
      }, error);
    }else{
      error('localStorage is undefined');
    }
  },
  reInit: function (options) {
    JSONDataStore.call(this, options || this.initialOptions);
    return this;
  },
  goTo: function (path, addUp) {
    if(!this.isDoing){
{'fraction_non_alphanumeric': 0.1469245816126929, 'fraction_numerical': 0.0013040643338404696, 'mean_word_length': 3.0011594202898553, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '739219', 'n_tokens_mistral': 4241, 'n_tokens_neox': 4266, 'n_words': 821}