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])
    )
  }
}
