import { AESCipherSIVNonce96 } from '../../base/cipher/aes';
import { RoomKeyring } from '../../keyring/room';
import { AbstractCryptoConfig } from '../base';
import { RoomPinnedDeriveKeyV1, TS_WINDOW_V1 } from '../common';
import { RoomConfigDumpArgs, RoomConfigLoadArgs } from './args';
import { makeInfoByteArray, ValueError } from '../../utility';

/**
 * Group message encryption: AES-SIV
        KEY: 32B pre-shared key -> 64B HKDF by context + current time-slice-window
        NONCE: 12B random
 */
export class RoomConfigV1 extends AbstractCryptoConfig<RoomConfigLoadArgs, RoomConfigDumpArgs> {
  public static TS_WINDOW = TS_WINDOW_V1;

  constructor(never_dump: boolean = false) {
    super(1, never_dump);
  }

  public load(args: RoomConfigLoadArgs): Promise<Uint8Array> {
    return new Promise((resolve, reject) => {
      let error;

      let cipher = args.cipher.slice(this.version.byteLength);
      let timestampArr = RoomConfigV1.TS_WINDOW.windows(args.timestamp);

      let _load = (timestamp_index) => {
        if (timestamp_index >= timestampArr.length) {
          reject(error);
          return;
        }

        let timestamp_window = timestampArr[timestamp_index];

        let now = performance.now();
        return this.derive_key(args.room_kr, timestamp_window)
          .then((derive_key) => {
            return AESCipherSIVNonce96.decrypt(cipher, derive_key)
              .then((res) => {
                resolve(res);

                return;
              })
              .catch((err) => {
                if (err instanceof ValueError) {
                  error = err;
                  _load(timestamp_index + 1);
                } else {
                  reject(err);
                }
              });
          })
          .catch(reject);
      };

      _load(0);
    });
  }

  public dump(args: RoomConfigDumpArgs): Promise<Uint8Array> {
    return this.derive_key(args.room_kr, RoomConfigV1.TS_WINDOW.window(args.timestamp)).then(
      (derive_key) => {
        return AESCipherSIVNonce96.encrypt(
          args.plain,
          derive_key,
          undefined,
          undefined,
          this.version
        );
      }
    );
  }

  public derive_key(room_kr: RoomKeyring, timestamp_window: Uint8Array): Promise<Uint8Array> {
    return RoomPinnedDeriveKeyV1.derive_key(
      room_kr,
      makeInfoByteArray(['room-', this.version, '-', timestamp_window])
    );
  }
}
