import { concatByteArray } from '../utility';
import { AbstractKeyring, MissingKey } from './base';
import { base64 } from "rfc4648";

/**
 * The account keyring has a trust state that must be loaded, validated checked before using them.
 * It declares the key version that are used for public-key cryptography.
 */
export abstract class AbstractAccountKeyring extends AbstractKeyring{

    public static TRUSTED_NOT = 0  // verification failed, keyring *is* compromised
    public static TRUSTED_UNKNOWN = 1  // not trusted
    public static TRUSTED_FULLY = 2  // trusted and fingerprint is of the most preferred key
    public static TRUSTED_STALE = 3  // trusted, but fingerprint is of an older key (no newer is available for the account)
    public static TRUSTED_REQUIRES_UPGRADE = 4  // trusted, but fingerprint should be upgraded

    public id;
    public _public_keys;
    public trusted;

    // safe_radix32.encode_fw(0)
    public static ANONYMOUS_ID = "2222222222222"

    // This is reserved for development of the trust-version handling of the crypto-configs.
    public static DEV_RESERVED = "x"

    // real key versions
    public static CURVE25519 = "1"
    public static ED25519 = "2"

    // in order of preference (from best to oldest)
    public static TRUST_KEY_VERSIONS = [AbstractAccountKeyring.ED25519]

    public static VALID_PUBLIC_KEY_LENGTH_MAP = {[AbstractAccountKeyring.CURVE25519]: 32, [AbstractAccountKeyring.ED25519]: 32};

    //public static abstract load: Function

    constructor(account_id: string, public_keys: Object, trusted: number = AbstractAccountKeyring.TRUSTED_UNKNOWN){
        super();
        this.id = new TextEncoder().encode(account_id);
        AbstractAccountKeyring.check_key_lengths(public_keys, AbstractAccountKeyring.VALID_PUBLIC_KEY_LENGTH_MAP);
        this._public_keys = public_keys;
        this.trusted = trusted;
    }

    public get_public_key(version: string): Uint8Array{
        let key = this._public_keys[version];
        if (key) return key;
        else{
            throw new MissingKey(version+"");
        }
    }

    /**
     * The actual order is not relevant, the goal is for it to be deterministic.
     */
    public ordered_ids(other: AbstractAccountKeyring): Uint8Array{
        if (new TextDecoder().decode(this.id) < new TextDecoder().decode(other.id)){
            return concatByteArray(this.id, other.id);
        }
        return concatByteArray(other.id, this.id);
    }

}


/**
 * Add TOFU (trust-on-first-use) identity generation and storage to the peer keyring.
 */
export abstract class AbstractPeerAccountKeyring extends AbstractAccountKeyring{

    public tofu_fingerprints;

    constructor(account_id: string, public_key: Object, trusted: number = AbstractAccountKeyring.TRUSTED_UNKNOWN){
        super(account_id, public_key, trusted);
        this.tofu_fingerprints = null;
    }

    /**
     * Get the simple fingerprints of the trust keys of the keyring. These are stored on the client side
        and compared automatically when re-loading the same keyring for consistency.
     */
    public create_tofu_fingerprints(): Promise<Uint8Array>{
        this.tofu_fingerprints = {};

        let _create = (index)=>{
            let key_version = AbstractAccountKeyring.TRUST_KEY_VERSIONS[index];
            let key;

            try{
                key = this.get_public_key(key_version);
            }
            catch(e){
                if (e instanceof MissingKey){
                    return _create(index+1);
                }
            }

            return new Promise((resolve, reject)=>{
                crypto.subtle.digest("SHA-256", key).then((digest)=>{
                    this.tofu_fingerprints[key_version] = new Uint8Array(digest.slice(0,16));
                    resolve(this.tofu_fingerprints[key_version]); // TODO upgrade typescript, now digest return with PromiseLike
                }).catch(reject)
            })
        }

        return _create(0);

    }
}

/**
 * The self account can have the same public cryptographic items as a peer and their secret counterparts.
    Additionally secret symmetric keys could be here. The an account's own keyring is always trusted (see notes
    at the implementation of load()).
 */
export abstract class AbstractSelfAccountKeyring extends AbstractAccountKeyring{

    public _secret_keys;

    public static MASTER = "3";

    public static VALID_SECRET_KEY_LENGTH_MAP = Object.assign({}, AbstractAccountKeyring.VALID_PUBLIC_KEY_LENGTH_MAP, {[AbstractSelfAccountKeyring.MASTER]: 32});

    //public static abstract load: Function;

    constructor(account_id: string, public_keys: Object, secret_keys: Object){
        super(account_id, public_keys, AbstractSelfAccountKeyring.TRUSTED_FULLY);
        AbstractSelfAccountKeyring.check_key_lengths(secret_keys, AbstractSelfAccountKeyring.VALID_SECRET_KEY_LENGTH_MAP);
        this._secret_keys = secret_keys;
    }

    public static load: Function;


    public update(public_keys?: Object, secret_keys?: Object){
        if (public_keys){
            AbstractSelfAccountKeyring.check_key_lengths(public_keys, AbstractSelfAccountKeyring.VALID_PUBLIC_KEY_LENGTH_MAP);
            this._public_keys = Object.assign(this._public_keys, public_keys);
        }
        if (secret_keys){
            AbstractSelfAccountKeyring.check_key_lengths(secret_keys, AbstractSelfAccountKeyring.VALID_SECRET_KEY_LENGTH_MAP);
            this._secret_keys = Object.assign(this._secret_keys, secret_keys);
        }
    }

    public get_secret_key(version: string): Uint8Array{
        let key = this._secret_keys[version];
        if (key){
            return key;
        }
        else{
            throw new MissingKey(version+"");
        }
    }

}
