"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const byzcoin_1 = require("../../../byzcoin");
const darc_1 = require("../../../darc");
const identity_darc_1 = tslib_1.__importDefault(require("../../../darc/identity-darc"));
const log_1 = tslib_1.__importDefault(require("../../../log"));
const __1 = require("..");
const _1 = require("./");
/**
 * DarcStruct holds a darc together with the corresponding instance. This instance can be used to
 * get the version.
 */
class DarcStruct extends darc_1.Darc {
    constructor(inst) {
        super(darc_1.Darc.decode(inst.data));
        this.inst = inst;
    }
}
exports.DarcStruct = DarcStruct;
/**
 * Holds a list of DARCs that will be updated individually, and whenever the list changes.
 */
class DarcInsts extends rxjs_1.BehaviorSubject {
    /**
     * Retrieves an eventually changing list of darcs from ByzCoin.
     *
     * @param bc of an initialized ByzCoinRPC instance
     * @param idsBS
     */
    static retrieve(bc, idsBS) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            log_1.default.lvl3("getting darcsBS");
            if (idsBS instanceof Array) {
                idsBS = new rxjs_1.BehaviorSubject(idsBS);
            }
            const darcs = yield __1.ObservableToBS(idsBS.pipe(operators_1.flatMap((ais) => Promise.all(ais
                .map((iid) => DarcInst.retrieve(bc, iid)))), operators_1.map((dbs) => dbs.filter((db) => db !== undefined))));
            return new DarcInsts(darcs);
        });
    }
    constructor(sbs) {
        super(sbs.getValue());
        sbs.subscribe(this);
    }
}
exports.DarcInsts = DarcInsts;
/**
 * A DarcBS class represents a darc on byzcoin. It has methods to modify the darc by
 * adding and removing rules, as well as to change the description.
 */
class DarcInst extends rxjs_1.BehaviorSubject {
    /**
     * Retrieves a DarcBS from ByzCoin given an InstanceID.
     *
     * @param bc of an initialized ByzCoinRPC instance
     * @param darcID a fixed InstanceID representing the baseID of the darc to retrieve
     * @return a DarcBS or undefined if something went wrong (no Darc at that ID)
     */
    static retrieve(bc, darcID) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            log_1.default.lvl3("getting darcBS");
            const instObs = (yield bc.instanceObservable(darcID)).pipe(operators_1.map((proof) => (proof && proof.value && proof.value.length > 0) ?
                new DarcStruct(byzcoin_1.Instance.fromProof(darcID, proof)) : undefined));
            const bsDarc = yield __1.ObservableToBS(instObs);
            if (bsDarc.getValue() === undefined) {
                throw new Error("this darc doesn't exist");
            }
            return new DarcInst(bsDarc);
        });
    }
    constructor(darc) {
        super(darc.getValue());
        darc.subscribe(this);
    }
    /**
     * Creates an instruction in the transaction with either an update of the description and/or an update
     * of the rules.
     *
     * @param tx where the instruction will be appended to
     * @param updates contains a description update and/or rules to be merged
     * @param unrestricted if true, will create an unrestricted evolve that allows to create new rules
     * @return the new DARC as it will appear on ByzCoin if the transaction is accepted
     */
    evolve(tx, updates, unrestricted = false) {
        return _1.DarcContract.evolve(tx, this.getValue(), updates, unrestricted);
    }
    /**
     * Sets the description of the DARC.
     *
     * @param tx where the instruction will be appended to
     * @param description of the new DARC
     * @return the new DARC as it will appear on ByzCoin if the transaction is accepted
     */
    setDescription(tx, description) {
        return this.evolve(tx, { description });
    }
    /**
     * Creates a new darc by overwriting existing rules.
     * A darc with a rule for `invoke:darc.evolve_unrestricted` can also accept new rules
     *
     * @param tx where the instruction will be appended to
     * @param newRules is a map of action to expression
     * @return the new DARC as it will appear on ByzCoin if the transaction is accepted
     */
    setRules(tx, ...newRules) {
        const rules = this.getValue().rules.clone();
        newRules.forEach(([action, expression]) => rules.setRule(action, toIId(expression)));
        return this.evolve(tx, { rules });
    }
    /**
     * Adds a sign and evolve element to the DARC with an OR expression
     *
     * @param tx where the instruction will be appended to
     * @param addRules is a map of actions to identities that will be ORed with the existing expression.
     * @return the new DARC as it will appear on ByzCoin if the transaction is accepted
     */
    addToRules(tx, ...addRules) {
        const rules = this.getValue().rules.clone();
        addRules.forEach(([action, expression]) => rules.appendToRule(action, toIId(expression), darc_1.Rule.OR));
        return this.evolve(tx, { rules });
    }
    /**
     * Removes an identity in the sign and/or evolve expression. The expressions need to be pure
     * OR expressions, else this will fail.
     *
     * @param tx where the instruction will be appended to
     * @param rmRules is a map of actions to identities that will be removed from existing rules.
     * @return the new DARC as it will appear on ByzCoin if the transaction is accepted
     */
    rmFromRules(tx, ...rmRules) {
        const rules = this.getValue().rules.clone();
        for (const [action, expression] of rmRules) {
            try {
                rules.getRule(action).remove(toIId(expression).toString());
            }
            catch (e) {
                log_1.default.warn("while removing identity from ", action, ":", e);
            }
        }
        return this.evolve(tx, { rules });
    }
    /**
     * Creates the instruction necessary to spawn a new coin using this darc.
     *
     * @param tx where the instruction will be appended to
     * @param name of the coin
     * @param preHash used to calculate the ID of the coin, if given
     */
    spawnCoin(tx, name, preHash) {
        return _1.DarcContract.spawnCoin(tx, this.getValue().getBaseID(), name, preHash);
    }
    /**
     * Spawns a new darc. The current darc must have a `spawn:darc` rule.
     *
     * @param tx where the instruction will be appended to
     * @param newDarc to be spawned
     */
    spawnDarc(tx, newDarc) {
        tx.spawn(this.getValue().getBaseID(), _1.DarcContract.contractID, [new byzcoin_1.Argument({ name: _1.DarcContract.argumentDarc, value: newDarc.toBytes() })]);
    }
}
exports.DarcInst = DarcInst;
function toIId(id) {
    if (Buffer.isBuffer(id)) {
        return new identity_darc_1.default({ id });
    }
    return id;
}
