const _localStorage = (() => {
  const _db = window.localStorage;
  return {
    add({ key, value }) {
      _db && _db.setItem(key, value);
    },
    remove({ key }) {
      _db && _db.removeItem(key);
    },
    get({ key }) {
      return _db ? _db.getItem(key) : null;
    },
    clear() {
      _db && _db.clear();
    }
  };
})();

const _sessionStorage = (() => {
  const _db = window.sessionStorage;
  return {
    add({ key, value }) {
      _db && _db.setItem(key, value);
    },
    remove({ key }) {
      _db && _db.removeItem(key);
    },
    get({ key }) {
      return _db ? _db.getItem(key) : null;
    },
    clear() {
      _db && _db.clear();
    }
  };
})();

const _objectStorage = (() => {
  let _db = {};
  return {
    add({ key, value }) {
      _db[key] = value;
    },
    remove({ key }) {
      delete _db[key];
    },
    get({ key }) {
      return _db[key];
    },
    clear() {
      _db = {};
    }
  };
})();

const _is = function(type, obj) {
  let _type = Object.prototype.toString
    .call(obj)
    .slice(8, -1)
    .toLowerCase();
  return obj !== undefined && obj !== null && _type === type.toLowerCase();
};

const STORE = {
  LOCALSTORAGE: 0,
  SESSIONSTORAGE: 1,
  OBJECTSTORAGE: 2,
}

const _DB = {
  _decode(str, pwd) {
    let prand = '';
    for (let i = 0; i < pwd.length; i++) {
      prand += pwd.charCodeAt(i).toString();
    }
    let sPos = Math.floor(prand.length / 5);
    let mult = parseInt(
      prand.charAt(sPos) +
        prand.charAt(sPos * 2) +
        prand.charAt(sPos * 3) +
        prand.charAt(sPos * 4) +
        prand.charAt(sPos * 5)
    );
    let incr = Math.ceil(pwd.length / 2);
    let modu = Math.pow(2, 31) - 1;
    prand = ((mult * prand + incr) % modu) + '';
    let last = '';
    for (let i = 0; i < str.length; i++) {
      let key = prand.charCodeAt(i % prand.length);
      let text = str.charCodeAt(i) ^ key;
      last += String.fromCharCode(text);
    }
    return last;
  },
  _get({ db, domain, encrypt }) {
    let _val = db.get({
      key: domain
    });
    let _obj;
    try {
      _obj = !_val
        ? {}
        : JSON.parse(encrypt ? this._decode(_val, domain) : _val);
    } catch ($$) {
      _obj = {};
    }
    return _obj;
  },
  _set(obj, { db, domain, encrypt }) {
    if (!obj) return false;
    if (_is('object', obj)) {
      obj = JSON.stringify(obj);
    }
    db.add({
      key: domain,
      value: encrypt ? this._decode(obj, domain) : obj
    }); // obj
    return true;
  },
  _remove({ db, domain }) {
    db.remove({
      key: domain
    });
  }
};

class DB {
  constructor({ domain, encrypt = true, type = STORE.OBJECTSTORAGE }) {
    this.source = {};
    this.option = {
      domain,
      encrypt: false,
      db: type === STORE.LOCALSTORAGE ? _localStorage : type === STORE.SESSIONSTORAGE ? _sessionStorage : _objectStorage,
    };

    this.backup = type !== STORE.OBJECTSTORAGE;

    if (this.backup) {
      this.source = _DB._get(this.option);
    }
  }

  get(key) {
    // const _obj = _DB._get(this.option);
    // return key ? _obj[key] : _obj;
    return key ? this.source[key] : this.source;
  }
  set(key, val) {
    if (!key) return '';
    let _o = {};
    _o[key] = val;
    this.source[key] = val;
    // let _obj = $.extend({},_get(domain), _o);
    let _obj = Object.assign({}, _DB._get(this.option), _o);

    return _DB._set(_obj, this.option);
  }
  assign(obj, force = true) {
    if (!_is('object', obj)) {
      console.log('value must be object');
    }
    // const _obj = Object.assign({}, _DB._get(this.option), obj);

    this.source = obj;

    return _DB._set(obj, this.option);
  }
  remove(key) {
    if (!key) {
      this.source = {};
      _DB._remove(this.option);
      return;
    }
    let _obj = _DB._get(this.option);
    let __obj = {};
    for (let index in _obj) {
      if (index !== key) {
        __obj[index] = _obj[index];
      }
    }
    this.source.key = undefined;
    // this.source = __obj;

    return _DB._set(__obj, this.option);
  }
  clear() {
    this.source = {};

    return this.remove();
  }
}

export default (function() {
  let store = {};
  let index = 0;
  const _func = function(domain, { type, encrypt } = {}) {
    if (!domain) {
      domain = +new Date() + '-' + index;
      index++;
    }
    if (!_is('string', domain)) {
      throw new Error(
        `Expect namespace to be string, but accept type ${typeof domain}`
      );
    }
    if (!store[domain]) {
      store[domain] = new DB({ domain, type, encrypt });
    }
    return store[domain];
  };
  _func.clear = function(domain) {
    if (!domain) {
      _localStorage.clear();
      _sessionStorage.clear();
      _objectStorage.clear();
      store = {};
    } else {
      store[domain] && (store[domain].clear(), (store[domain] = null));
    }
  };
  return _func;
})();

export {
  STORE,
}
