import { AbstractAccountKeyring, AbstractSelfAccountKeyring } from "./account_base";
import { AbstractRoomKeyring } from "./room_base";
import { ACCOUNT_ROOM_KEY_CRYPTO_CONTEXT } from "../context/account_key/room/__init__";
import { AccountRoomKeyConfigDumpArgs, AccountRoomKeyConfigLoadArgs } from "../context/account_key/room/args";
import { ROOM_KEY_CRYPTO_CONTEXT } from "../context/room_key/__init__";
import { RoomKeyConfigDumpArgs, RoomKeyConfigLoadArgs } from "../context/room_key/args";
import { RandomKeyGenerator256 } from "../base/key/random";
import { MissingKey } from "./base";
import { base64 } from "rfc4648";

/**
 * This is a separate module to resolve a cyclic import.
 */
export class RoomKeyring extends AbstractRoomKeyring{

    public static load(
        self_kr: AbstractSelfAccountKeyring,
        peer_kr: AbstractAccountKeyring,
        room_id: string,
        saved_key?: Uint8Array,
        pinned_key?: Uint8Array,
    ): Promise<RoomKeyring>{
        if (!saved_key && !pinned_key){
            throw new MissingKey("MissingKey Pinned key is missing");
        }
        let room_kr = new RoomKeyring(room_id, {});
        
        let keys = {};

        let p: Promise<any>; // sync crypto load
        if (saved_key){
            p = ACCOUNT_ROOM_KEY_CRYPTO_CONTEXT.load(
                new AccountRoomKeyConfigLoadArgs(saved_key, self_kr, RoomKeyring.PINNED, room_kr)
            ).then((k)=>{
                keys[RoomKeyring.PINNED] = k;
                room_kr.update(keys);
                return room_kr;
            })
        }
        else{
            p = Promise.resolve();
        }

        return p.then(()=>{

            let p2: Promise<Uint8Array>;

            
            if (base64.stringify(self_kr['id']) == base64.stringify(peer_kr['id'])){
                if (!keys.hasOwnProperty(RoomKeyring.PINNED)){
                    p2 = ACCOUNT_ROOM_KEY_CRYPTO_CONTEXT.load(
                        new AccountRoomKeyConfigLoadArgs(pinned_key, self_kr, RoomKeyring.PINNED, room_kr)
                    )
                }
            }
            else{
                if (!keys.hasOwnProperty(RoomKeyring.PINNED)){
                    p2 = ROOM_KEY_CRYPTO_CONTEXT.load(
                        new RoomKeyConfigLoadArgs(pinned_key, self_kr, peer_kr, room_kr, RoomKeyring.PINNED)
                    )
                }
            }

            if (!p2) return Promise.resolve(room_kr);
            
            return p2.then((k)=>{
                keys[RoomKeyring.PINNED] = k;
                room_kr.update(keys);
                return room_kr;
            })
        })
        
        
    }

    public static loadRaw(room_id: string, pinned_key: Uint8Array): Promise<RoomKeyring>{
        return Promise.resolve(new RoomKeyring(room_id, {
            [RoomKeyring.PINNED] : pinned_key
        }));
    }

    public dump(self_kr: AbstractSelfAccountKeyring, peer_kr: AbstractAccountKeyring): Promise<Object>{
        let keys = {};
        if (base64.stringify(self_kr['id']) == base64.stringify(peer_kr['id'])){ // TODO test this
            return ACCOUNT_ROOM_KEY_CRYPTO_CONTEXT.dump(
                new AccountRoomKeyConfigDumpArgs(self_kr, RoomKeyring.PINNED, this)
            ).then((k)=>{
                keys[RoomKeyring.PINNED] = k;
                return keys;
            })
        }
        else{
            return ROOM_KEY_CRYPTO_CONTEXT.dump(
                new RoomKeyConfigDumpArgs(self_kr, peer_kr, this, RoomKeyring.PINNED)
            ).then((k)=>{
                keys[RoomKeyring.PINNED] = k;
                return keys;
            })
        }
    }

    public static generate(room_id: string){
        return new RoomKeyring(room_id, {
            [RoomKeyring.PINNED]: RandomKeyGenerator256.new_key()
        });
    }

}