import { AESCipherSIVNonce96 } from "../../base/cipher/aes";
import { AbstractSelfAccountKeyring } from "../../keyring/account_base";
import { PeerAccountKeyring } from "../../keyring/account_peer";
import { AbstractRoomKeyring } from "../../keyring/room_base";
import { makeInfoByteArray, ValueError } from "../../utility";
import { AbstractCryptoConfig } from "../base"
import { AccountPairDeriveKeyV1, TS_WINDOW_V1 } from "../common"
import { NanoConfigDumpArgs, NanoConfigLoadArgs } from "./args"

/**
 * Nano message (request and initial response) encryption: AES-SIV
        KEY: 32B from curve25519 scalar_mult -> 64B HKDF by context + current time-slice-window
        NONCE: 12B random
 */
export class NanoConfigV1 extends AbstractCryptoConfig<NanoConfigLoadArgs, NanoConfigDumpArgs>{

    public static TS_WINDOW = TS_WINDOW_V1;

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

    public load(args: NanoConfigLoadArgs): Promise<Uint8Array>{
        return new Promise((resolve, reject)=>{
            
            let error;
            
            let cipher = args.cipher.slice(this.version.byteLength);
            let timestampArr = NanoConfigV1.TS_WINDOW.windows(args.timestamp);

            let _load = (timestamp_index)=>{
                
                if (timestamp_index >= timestampArr.length){
                    reject(error)
                    return;
                }
                
                let timestamp_window = timestampArr[timestamp_index];
                
                this.derive_key(args.self_kr, args.peer_kr, args.room_kr.get_key(AbstractRoomKeyring.PINNED), timestamp_window).then((derive_key)=>{
                    
                    AESCipherSIVNonce96.decrypt(
                        cipher, 
                        derive_key
                    ).then((res)=>{
                        
                        resolve(res);
                    }).catch((err)=>{
                        if (err instanceof ValueError){
                            error = err;
                            _load(timestamp_index+1);
                        }
                        else{
                            reject(err);
                        }
                    })
                }).catch(reject)

            }

            _load(0);

        });
        
    }

    public load2(args: NanoConfigLoadArgs): Promise<Uint8Array>{
        
        let cipher = args.cipher.slice(this.version.byteLength);
        for (let timestamp_window in NanoConfigV1.TS_WINDOW.windows(args.timestamp)){
            
            return this.derive_key(args.self_kr, args.peer_kr, args.room_kr.get_key(AbstractRoomKeyring.PINNED), new TextEncoder().encode(timestamp_window)).then((derive_key)=>{
                return AESCipherSIVNonce96.decrypt(
                    cipher, 
                    derive_key
                )
            })
                
        }

        throw "error";
    }

    public dump(args: NanoConfigDumpArgs): Promise<Uint8Array>{
        
        return this.derive_key(args.self_kr, args.peer_kr, args.room_kr.get_key(AbstractRoomKeyring.PINNED), NanoConfigV1.TS_WINDOW.window(args.timestamp)).then((derive_key)=>{
            return AESCipherSIVNonce96.encrypt(
                args.plain,
                derive_key,
                undefined,
                undefined,
                this.version
            )
        })        
    }

    public derive_key(self_kr: AbstractSelfAccountKeyring, peer_kr: PeerAccountKeyring, room_secret: Uint8Array, ts_window: Uint8Array){
        return AccountPairDeriveKeyV1.derive_key(self_kr, peer_kr, makeInfoByteArray(["nano-", this.version, "-", room_secret, "-", ts_window]))
    }
}