"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const kyber_1 = require("@dedis/kyber");
const byzcoin_1 = require("../byzcoin");
const instance_1 = tslib_1.__importDefault(require("../byzcoin/instance"));
const darc_1 = tslib_1.__importDefault(require("../darc/darc"));
const identity_darc_1 = tslib_1.__importDefault(require("../darc/identity-darc"));
const rules_1 = require("../darc/rules");
const proto_1 = require("./proto");
const { anon } = kyber_1.sign;
class PopPartyInstance extends instance_1.default {
    constructor(rpc, inst) {
        super(inst);
        this.rpc = rpc;
        this.tmpAttendees = [];
        if (inst.contractID.toString() !== PopPartyInstance.contractID) {
            throw new Error(`mismatch contract name: ${inst.contractID} vs ${PopPartyInstance.contractID}`);
        }
        this.popPartyStruct = proto_1.PopPartyStruct.decode(this.data);
    }
    /**
     * Getter for the final statement. It throws if the party
     * is not finalized.
     *
     * @returns the final statement
     */
    get finalStatement() {
        if (this.popPartyStruct.state !== PopPartyInstance.FINALIZED) {
            throw new Error("this party is not finalized yet");
        }
        return new proto_1.FinalStatement({
            attendees: this.popPartyStruct.attendees,
            desc: this.popPartyStruct.description,
        });
    }
    /**
     * Helper to create a PoP party darc
     *
     * @param darcIDs   Organizers darc instance IDs
     * @param desc      Description of the party
     * @returns the new darc
     */
    static preparePartyDarc(darcIDs, desc) {
        const ids = darcIDs.map((di) => new identity_darc_1.default({ id: di }));
        const darc = darc_1.default.createBasic(ids, ids, Buffer.from(desc));
        ids.forEach((id) => {
            darc.addIdentity("invoke:popParty.barrier", id, rules_1.Rule.OR);
            darc.addIdentity("invoke:popParty.finalize", id, rules_1.Rule.OR);
            darc.addIdentity("invoke:popParty.addParty", id, rules_1.Rule.OR);
        });
        return darc;
    }
    /**
     * Get a pop party from byzcoin
     *
     * @param bc    The RPC to use
     * @param iid   The instance ID of the party
     * @param waitMatch how many times to wait for a match - useful if its called just after an addTransactionAndWait.
     * @param interval how long to wait between two attempts in waitMatch.
     * @returns a promise that resolves with the party instance
     */
    static fromByzcoin(bc, iid, waitMatch = 0, interval = 1000) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new PopPartyInstance(bc, yield instance_1.default.fromByzcoin(bc, iid, waitMatch, interval));
        });
    }
    /**
     * Add an attendee to the party
     *
     * @param attendee The public key of the attendee
     */
    addAttendee(attendee) {
        if (this.popPartyStruct.state !== PopPartyInstance.SCANNING) {
            throw new Error("party is not in attendee-adding mode");
        }
        if (this.tmpAttendees.findIndex((pub) => pub.equals(attendee)) === -1) {
            this.tmpAttendees.push(attendee);
        }
    }
    /**
     * Remove an attendee from the party
     *
     * @param attendee The public key of the attendee
     */
    removeAttendee(attendee) {
        if (this.popPartyStruct.state !== PopPartyInstance.SCANNING) {
            throw new Error("party is not in attendee-adding mode");
        }
        const i = this.tmpAttendees.findIndex((pub) => pub.equals(attendee));
        if (i >= 0) {
            this.tmpAttendees.splice(i, 1);
        }
        return this.tmpAttendees.length;
    }
    /**
     * Returns a copy of the list of all attendees.
     */
    getTmpAttendees() {
        return this.tmpAttendees.slice();
    }
    /**
     * Start the party
     *
     * @param signers The list of signers for the transaction
     * @returns a promise that resolves with the state of the party
     */
    activateBarrier(signers) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (this.popPartyStruct.state !== PopPartyInstance.PRE_BARRIER) {
                throw new Error("barrier point has already been passed");
            }
            const instr = byzcoin_1.Instruction.createInvoke(this.id, PopPartyInstance.contractID, "barrier", []);
            const ctx = byzcoin_1.ClientTransaction.make(this.rpc.getProtocolVersion(), instr);
            yield ctx.updateCountersAndSign(this.rpc, [signers]);
            yield this.rpc.sendTransactionAndWait(ctx);
            yield this.update();
            return this.popPartyStruct.state;
        });
    }
    /**
     * Finalize the party
     *
     * @param signers The list of signers for the transaction
     * @returns a promise that resolves with the state of the party
     */
    finalize(signers) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (this.popPartyStruct.state !== PopPartyInstance.SCANNING) {
                throw new Error("party did not pass barrier-point yet");
            }
            this.popPartyStruct.updateAttendes(this.tmpAttendees);
            const instr = byzcoin_1.Instruction.createInvoke(this.id, PopPartyInstance.contractID, "finalize", [new byzcoin_1.Argument({ name: "attendees", value: this.popPartyStruct.attendees.toBytes() })]);
            const ctx = byzcoin_1.ClientTransaction.make(this.rpc.getProtocolVersion(), instr);
            yield ctx.updateCountersAndSign(this.rpc, [signers]);
            yield this.rpc.sendTransactionAndWait(ctx);
            yield this.update();
            return this.popPartyStruct.state;
        });
    }
    /**
     * Update the party data
     * @returns a promise that resolves with an updated instance
     */
    update() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const inst = yield instance_1.default.fromByzcoin(this.rpc, this.id);
            this.data = inst.data;
            this.popPartyStruct = proto_1.PopPartyStruct.decode(this.data);
            return this;
        });
    }
    /**
     * Mine coins for a person using either an existing coinIID, or a
     * new darc + a coin that yet has to be instantiated.
     *
     * @param secret The secret key of the miner
     * @param coinID The coin instance ID of the miner
     * @param newD A new darc that has not been instantiated yet
     */
    mine(secret, coinID, newD) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (this.popPartyStruct.state !== PopPartyInstance.FINALIZED) {
                throw new Error("cannot mine on a non-finalized party");
            }
            const keys = this.popPartyStruct.attendees.publics;
            const lrs = yield anon.sign(Buffer.from("mine"), keys, secret, this.id);
            const args = [
                new byzcoin_1.Argument({ name: "lrs", value: lrs.encode() }),
            ];
            if (coinID) {
                args.push(new byzcoin_1.Argument({ name: "coinIID", value: coinID }));
            }
            else if (newD) {
                args.push(new byzcoin_1.Argument({ name: "newDarc", value: newD.toBytes() }));
            }
            else {
                throw new Error("need to give either coinIID or newDarc");
            }
            const instr = byzcoin_1.Instruction.createInvoke(this.id, PopPartyInstance.contractID, "mine", args);
            // the transaction is not signed but there is a counter-measure against
            // replay attacks server-side
            const ctx = byzcoin_1.ClientTransaction.make(this.rpc.getProtocolVersion(), instr);
            yield this.rpc.sendTransactionAndWait(ctx);
            yield this.update();
        });
    }
}
exports.PopPartyInstance = PopPartyInstance;
PopPartyInstance.contractID = "popParty";
PopPartyInstance.PRE_BARRIER = 1;
PopPartyInstance.SCANNING = 2;
PopPartyInstance.FINALIZED = 3;
