function SignalProtocolStore() {
  this.store = {};
}

SignalProtocolStore.prototype = {
  Direction: { SENDING: 1, RECEIVING: 2 },
  getIdentityKeyPair() {
    return Promise.resolve(this.get('identityKey'));
  },
  getLocalRegistrationId() {
    return Promise.resolve(this.get('registrationId'));
  },
  put(key, value) {
    if (
      key === undefined ||
      value === undefined ||
      key === null ||
      value === null
    )
      throw new Error('Tried to store undefined/null');
    this.store[key] = value;
  },
  get(key, defaultValue) {
    if (key === null || key === undefined)
      throw new Error('Tried to get value for undefined/null key');
    if (key in this.store) {
      return this.store[key];
    }
    return defaultValue;
  },
  remove(key) {
    if (key === null || key === undefined)
      throw new Error('Tried to remove value for undefined/null key');
    delete this.store[key];
  },

  isTrustedIdentity(identifier, identityKey) {
    if (identifier === null || identifier === undefined) {
      throw new Error('tried to check identity key for undefined/null key');
    }
    if (!(identityKey instanceof ArrayBuffer)) {
      throw new Error('Expected identityKey to be an ArrayBuffer');
    }
    const trusted = this.get(`identityKey${identifier}`);
    if (trusted === undefined) {
      return Promise.resolve(true);
    }
    return Promise.resolve(identityKey === trusted);
  },
  loadIdentityKey(identifier) {
    if (identifier === null || identifier === undefined)
      throw new Error('Tried to get identity key for undefined/null key');
    return new Promise(resolve => {
      resolve(this.get(`identityKey${identifier}`));
    });
  },
  saveIdentity(identifier, identityKey) {
    if (identifier === null || identifier === undefined)
      throw new Error('Tried to put identity key for undefined/null key');
    return new Promise(resolve => {
      const existing = this.get(`identityKey${identifier}`);
      this.put(`identityKey${identifier}`, identityKey);
      if (existing && existing !== identityKey) {
        resolve(true);
      } else {
        resolve(false);
      }
    });
  },

  /* Returns a prekeypair object or undefined */
  loadPreKey(keyId) {
    return new Promise(resolve => {
      const res = this.get(`25519KeypreKey${keyId}`);
      resolve(res);
    });
  },
  storePreKey(keyId, keyPair, contactPubKey = null) {
    if (contactPubKey) {
      const data = {
        id: keyId,
        publicKey: keyPair.pubKey,
        privateKey: keyPair.privKey,
        recipient: contactPubKey,
      };
      return new Promise(resolve => {
        resolve(this.put(`25519KeypreKey${contactPubKey}`, data));
      });
    }
    return new Promise(resolve => {
      resolve(this.put(`25519KeypreKey${keyId}`, keyPair));
    });
  },
  removePreKey(keyId) {
    return new Promise(resolve => {
      resolve(this.remove(`25519KeypreKey${keyId}`));
    });
  },

  /* Returns a signed keypair object or undefined */
  loadSignedPreKey(keyId) {
    return new Promise(resolve => {
      const res = this.get(`25519KeysignedKey${keyId}`);
      resolve(res);
    });
  },
  loadSignedPreKeys() {
    return new Promise(resolve => {
      const res = [];
      const keys = Object.keys(this.store);
      for (let i = 0, max = keys.length; i < max; i += 1) {
        const key = keys[i];
        if (key.startsWith('25519KeysignedKey')) {
          res.push(this.store[key]);
        }
      }
      resolve(res);
    });
  },
  storeSignedPreKey(keyId, keyPair) {
    return new Promise(resolve => {
      resolve(this.put(`25519KeysignedKey${keyId}`, keyPair));
    });
  },
  removeSignedPreKey(keyId) {
    return new Promise(resolve => {
      resolve(this.remove(`25519KeysignedKey${keyId}`));
    });
  },

  loadSession(identifier) {
    return new Promise(resolve => {
      resolve(this.get(`session${identifier}`));
    });
  },
  storeSession(identifier, record) {
    return new Promise(resolve => {
      resolve(this.put(`session${identifier}`, record));
    });
  },
  removeAllSessions(identifier) {
    return new Promise(resolve => {
      const keys = Object.keys(this.store);
      for (let i = 0, max = keys.length; i < max; i += 1) {
        const key = keys[i];
        if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) {
          delete this.store[key];
        }
      }
      resolve();
    });
  },
  getDeviceIds(identifier) {
    return new Promise(resolve => {
      const deviceIds = [];
      const keys = Object.keys(this.store);
      for (let i = 0, max = keys.length; i < max; i += 1) {
        const key = keys[i];
        if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) {
          deviceIds.push(parseInt(key.split('.')[1], 10));
        }
      }
      resolve(deviceIds);
    });
  },
  async loadPreKeyForContact(contactPubKey) {
    return new Promise(resolve => {
      const key = this.get(`25519KeypreKey${contactPubKey}`);
      if (!key) resolve(undefined);
      resolve({
        pubKey: key.publicKey,
        privKey: key.privateKey,
        keyId: key.id,
        recipient: key.recipient,
      });
    });
  },
  async storeContactSignedPreKey(pubKey, signedPreKey) {
    const key = {
      identityKeyString: pubKey,
      keyId: signedPreKey.keyId,
      publicKey: signedPreKey.publicKey,
      signature: signedPreKey.signature,
      created_at: Date.now(),
      confirmed: false,
    };
    this.put(`contactSignedPreKey${pubKey}`, key);
  },
  async loadContactSignedPreKey(pubKey) {
    const preKey = this.get(`contactSignedPreKey${pubKey}`);
    if (preKey) {
      return {
        id: preKey.id,
        identityKeyString: preKey.identityKeyString,
        publicKey: preKey.publicKey,
        signature: preKey.signature,
        created_at: preKey.created_at,
        keyId: preKey.keyId,
        confirmed: preKey.confirmed,
      };
    }
    window.log.warn('Failed to fetch contact signed prekey:', pubKey);
    return undefined;
  },
};
