"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const __1 = require("..");
const darc_1 = tslib_1.__importDefault(require("../../darc/darc"));
const log_1 = tslib_1.__importDefault(require("../../log"));
const instance_1 = tslib_1.__importDefault(require("../instance"));
class DarcInstance extends instance_1.default {
    constructor(rpc, inst) {
        super(inst);
        this.rpc = rpc;
        if (inst.contractID.toString() !== DarcInstance.contractID) {
            throw new Error(`mismatch contract name: ${inst.contractID} vs ${DarcInstance.contractID}`);
        }
        this._darc = darc_1.default.decode(inst.data);
    }
    /**
     * Initializes using an existing coinInstance from ByzCoin
     *
     * @param bc a working ByzCoin instance
     * @param iid the instance id of the darc-instance
     * @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 darc instance
     */
    static fromByzcoin(bc, iid, waitMatch = 0, interval = 1000) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new DarcInstance(bc, yield instance_1.default.fromByzcoin(bc, iid, waitMatch, interval));
        });
    }
    /**
     * spawn creates a new darc, given a darcID.
     *
     * @param rpc a working ByzCoin instance
     * @param darcID a darc that has the right to spawn new darcs
     * @param signers fulfilling the `spawn:darc` rule of the darc pointed to by darcID
     * @param newD the new darc to spawn
     */
    static spawn(rpc, darcID, signers, newD) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const di = yield DarcInstance.fromByzcoin(rpc, darcID);
            return di.spawnDarcAndWait(newD, signers, 10);
        });
    }
    /**
     * create returns a DarcInstance, given a ByzCoin and a darc. The instance must already exist on
     * ByzCoin. This method does not verify if it does or not.
     *
     * @param rpc a working ByzCoin instance
     * @param d the darc
     */
    static create(rpc, d) {
        return new DarcInstance(rpc, new instance_1.default({
            contractID: DarcInstance.contractID,
            darcID: d.getBaseID(),
            data: d.toBytes(),
            id: d.getBaseID(),
        }));
    }
    /**
     * Returns a copy of the darc.
     */
    get darc() {
        return this._darc.copy();
    }
    /**
     * Update the data of this instance
     *
     * @return a promise that resolves once the data is up-to-date
     */
    update() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const proof = yield this.rpc.getProofFromLatest(this._darc.getBaseID());
            const inst = instance_1.default.fromProof(this._darc.getBaseID(), proof);
            this._darc = darc_1.default.decode(inst.data);
            return this;
        });
    }
    /**
     * Searches for the rule that corresponds to the Darc.ruleSign action. If that rule
     * does not exist, it returns an error.
     */
    getSignerIdentities() {
        for (const rule of this._darc.rules.list) {
            if (rule.action === darc_1.default.ruleSign) {
                return rule.getIdentities();
            }
        }
        throw new Error("This darc doesn't have a sign expression");
    }
    /**
     * Returns all darcs that are stored in the signer expression. It leaves out any
     * other element of the expression.
     */
    getSignerDarcIDs() {
        const ids = this.getSignerIdentities();
        const ret = [];
        ids.forEach((e) => {
            if (e.startsWith("darc:")) {
                ret.push(Buffer.from(e.slice(5), "hex"));
            }
            else {
                log_1.default.warn("Non-darc expression in signer:", e);
            }
        });
        return ret;
    }
    /**
     * Request to evolve the existing darc using the new darc and wait for
     * the block inclusion
     *
     * @param newDarc The new darc
     * @param signers Signers for the counters
     * @param wait Number of blocks to wait for
     * @returns a promise that resolves with the new darc instance
     */
    evolveDarcAndWait(newDarc, signers, wait, unrestricted = false) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!newDarc.getBaseID().equals(this._darc.getBaseID())) {
                throw new Error("not the same base id for the darc");
            }
            if (newDarc.version.compare(this._darc.version.add(1)) !== 0) {
                throw new Error("not the right version");
            }
            if (!newDarc.prevID.equals(this._darc.id)) {
                throw new Error("doesn't point to the previous darc");
            }
            const args = [new __1.Argument({ name: DarcInstance.argumentDarc,
                    value: Buffer.from(darc_1.default.encode(newDarc).finish()) })];
            const cmd = unrestricted ? DarcInstance.commandEvolveUnrestricted : DarcInstance.commandEvolve;
            const instr = __1.Instruction.createInvoke(this._darc.getBaseID(), DarcInstance.contractID, cmd, args);
            const ctx = __1.ClientTransaction.make(this.rpc.getProtocolVersion(), instr);
            yield ctx.updateCounters(this.rpc, [signers]);
            ctx.signWith([signers]);
            yield this.rpc.sendTransactionAndWait(ctx, wait);
            return this.update();
        });
    }
    /**
     * Request to spawn an instance and wait for the inclusion
     *
     * @param d             The darc to spawn
     * @param signers       Signers for the counters
     * @param wait          Number of blocks to wait for
     * @returns a promise that resolves with the new darc instance
     */
    spawnDarcAndWait(d, signers, wait = 0) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const args = [
                new __1.Argument({
                    name: DarcInstance.argumentDarc,
                    value: Buffer.from(darc_1.default.encode(d).finish()),
                }),
            ];
            yield this.spawnInstanceAndWait(DarcInstance.contractID, args, signers, wait);
            return DarcInstance.fromByzcoin(this.rpc, d.getBaseID(), wait);
        });
    }
    /**
     * Request to spawn an instance of any contract and wait
     *
     * @param contractID    Contract name of the new instance
     * @param signers       Signers for the counters
     * @param wait          Number of blocks to wait for
     * @returns a promise that resolves with the instanceID of the new instance, which is only valid if the
     *          contract.spawn uses DeriveID.
     */
    spawnInstanceAndWait(contractID, args, signers, wait = 0) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const instr = __1.Instruction.createSpawn(this._darc.getBaseID(), DarcInstance.contractID, args);
            const ctx = __1.ClientTransaction.make(this.rpc.getProtocolVersion(), instr);
            yield ctx.updateCounters(this.rpc, [signers]);
            ctx.signWith([signers]);
            yield this.rpc.sendTransactionAndWait(ctx, wait);
            return ctx.instructions[0].deriveId();
        });
    }
    /**
     * Checks whether the given rule can be matched by a multi-signature created by all
     * signers. If the rule doesn't exist, this method silently returns 'false'.
     * Currently only Rule.OR are supported. A Rule.AND or "(" will return an error.
     * Currently only 1 signer is supported.
     *
     * @param action the action to match
     * @param signers all supposed signers for this action.
     */
    ruleMatch(action, signers) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const ids = yield this._darc.ruleMatch(action, signers, (id) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                const di = yield DarcInstance.fromByzcoin(this.rpc, id);
                return di._darc;
            }));
            return ids.length > 0;
        });
    }
}
exports.default = DarcInstance;
DarcInstance.contractID = "darc";
DarcInstance.commandEvolve = "evolve";
DarcInstance.commandEvolveUnrestricted = "evolve_unrestricted";
DarcInstance.argumentDarc = "darc";
DarcInstance.ruleEvolve = "invoke:" + DarcInstance.argumentDarc + "." + DarcInstance.commandEvolve;
DarcInstance.ruleEvolveUnrestricted = "invoke:" + DarcInstance.argumentDarc + "." +
    DarcInstance.commandEvolveUnrestricted;
