"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const crypto_browserify_1 = require("crypto-browserify");
const long_1 = tslib_1.__importDefault(require("long"));
const light_1 = require("protobufjs/light");
const protobuf_1 = require("../protobuf");
const identity_wrapper_1 = tslib_1.__importDefault(require("./identity-wrapper"));
const rules_1 = tslib_1.__importStar(require("./rules"));
/**
 * Distributed Access Right Controls
 */
class Darc extends light_1.Message {
    constructor(properties) {
        super(properties);
        this.description = Buffer.from(this.description || protobuf_1.EMPTY_BUFFER);
        this.baseID = Buffer.from(this.baseID || protobuf_1.EMPTY_BUFFER);
        this.prevID = Buffer.from(this.prevID || protobuf_1.EMPTY_BUFFER);
        this.rules = this.rules || new rules_1.default();
        /* Protobuf aliases */
        Object.defineProperty(this, "baseid", {
            get() {
                return this.baseID;
            },
            set(value) {
                this.baseID = value;
            },
        });
        Object.defineProperty(this, "previd", {
            get() {
                return this.prevID;
            },
            set(value) {
                this.prevID = value;
            },
        });
    }
    /**
     * Get the id of the darc
     * @returns the id as a buffer
     */
    get id() {
        const h = crypto_browserify_1.createHash("sha256");
        const versionBuf = Buffer.from(this.version.toBytesLE());
        h.update(versionBuf);
        h.update(this.description);
        if (this.baseID.length > 0) {
            h.update(this.baseID);
        }
        if (this.prevID.length > 0) {
            h.update(this.prevID);
        }
        this.rules.list.forEach((r) => {
            h.update(r.action);
            h.update(r.getExpr());
        });
        return h.digest();
    }
    /**
     * Create a list of rules with basic permissions for owners and signers
     * @param owners those allow to evolve the darc
     * @param signers those allow to sign
     * @returns the list of rules
     */
    static initRules(owners, signers) {
        const rules = new rules_1.default();
        owners.forEach((o) => rules.appendToRule("invoke:darc.evolve", o, rules_1.Rule.AND));
        signers.forEach((s) => rules.appendToRule(Darc.ruleSign, s, rules_1.Rule.OR));
        return rules;
    }
    /**
     * Creates a basic darc using the owners and signers to populate the
     * rules.
     * @param owners    those you can evolve the darc
     * @param signers   those you can sign
     * @param desc      the description of the darc
     * @returns the new darc
     */
    static createBasic(owners, signers, desc, rules) {
        const darc = new Darc({
            baseID: Buffer.from([]),
            description: desc,
            // Assure that two otherwise identical darcs get another base-id by using a random prevID,
            // even for a genesis-darc.
            prevID: crypto_browserify_1.randomBytes(32),
            rules: this.initRules(owners, signers),
            version: long_1.default.fromNumber(0, true),
        });
        if (rules) {
            rules.forEach((r) => {
                signers.forEach((s) => {
                    darc.rules.appendToRule(r, s, "|");
                });
            });
        }
        return darc;
    }
    /**
     * @see README#Message classes
     */
    static register() {
        protobuf_1.registerMessage("Darc", Darc, rules_1.default);
    }
    /**
     * Get the id of the genesis darc
     * @returns the id as a buffer
     */
    getBaseID() {
        if (this.version.eq(0)) {
            return this.id;
        }
        else {
            return this.baseID;
        }
    }
    /**
     * Append an identity to a rule using the given operator when
     * it already exists
     * @param rule      the name of the rule
     * @param identity  the identity to append to the rule
     * @param op        the operator to use if necessary
     */
    addIdentity(rule, identity, op) {
        this.rules.appendToRule(rule, identity, op);
    }
    /**
     * Copy and evolve the darc to the next version so that it can be
     * changed and proposed to byzcoin.
     * @returns a new darc
     */
    evolve() {
        return new Darc({
            baseID: this.getBaseID(),
            description: this.description,
            prevID: this.id,
            rules: this.rules.clone(),
            version: this.version.add(1),
        });
    }
    /**
     * Get a string representation of the darc
     * @returns the string representation
     */
    toString() {
        return "ID: " + this.id.toString("hex") + "\n" +
            "Base: " + this.baseID.toString("hex") + "\n" +
            "Prev: " + this.prevID.toString("hex") + "\n" +
            "Version: " + this.version + "\n" +
            "Rules: " + this.rules;
    }
    /**
     * Helper to encode the darc using protobuf
     * @returns encoded darc as a buffer
     */
    toBytes() {
        return Buffer.from(Darc.encode(this).finish());
    }
    /**
     * Returns a deep copy of the darc.
     */
    copy() {
        return Darc.decode(this.toBytes());
    }
    /**
     * Checks whether the given rule can be matched by a multi-signature created by all
     * signers. If the rule doesn't exist, it throws an error.
     * Currently restrictions:
     *  - only Rule.OR are supported. A Rule.AND or "(" will return an error.
     *  - only one identity can be checked. If more identities are given, the function
     *  returns an error.
     *
     * @param action the action to match
     * @param signers all supposed signers for this action.
     * @return the set of identities that match the rule.
     */
    ruleMatch(action, signers, getDarc) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const rule = this.rules.getRule(action);
            if (!rule) {
                throw new Error("This rule doesn't exist");
            }
            if (signers.length !== 1) {
                throw new Error("Currently only supports checking 1 identity");
            }
            const ids = rule.getIdentities();
            for (const idStr of ids) {
                const id = identity_wrapper_1.default.fromString(idStr);
                if (id.toString() === signers[0].toString()) {
                    return signers;
                }
                if (id.darc) {
                    const d = yield getDarc(id.darc.id);
                    if ((yield d.ruleMatch(Darc.ruleSign, signers, getDarc)).length === 1) {
                        return signers;
                    }
                }
            }
            return [];
        });
    }
}
exports.default = Darc;
Darc.ruleSign = "_sign";
Darc.register();
