"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const crypto_browserify_1 = require("crypto-browserify");
const light_1 = require("protobufjs/light");
const __1 = require("..");
const protobuf_1 = require("../../protobuf");
const byzcoin_rpc_1 = require("../byzcoin-rpc");
const instance_1 = tslib_1.__importDefault(require("../instance"));
class CoinInstance extends instance_1.default {
    /**
     * Constructs a new CoinInstance. If the instance is not of type CoinInstance,
     * an error will be thrown.
     *
     * @param rpc a working RPC instance
     * @param inst an instance representing a CoinInstance
     */
    constructor(rpc, inst) {
        super(inst);
        this.rpc = rpc;
        if (inst.contractID.toString() !== CoinInstance.contractID) {
            throw new Error(`mismatch contract name: ${inst.contractID} vs ${CoinInstance.contractID}`);
        }
        this._coin = Coin.decode(inst.data);
    }
    /**
     * Generate the coin instance ID for a given darc ID
     *
     * @param buf Any buffer that is known to the caller
     * @returns the id as a buffer
     */
    static coinIID(buf) {
        const h = crypto_browserify_1.createHash("sha256");
        h.update(Buffer.from(CoinInstance.contractID));
        h.update(buf);
        return h.digest();
    }
    /**
     * Spawn a coin instance from a darc id
     *
     * @param bc        The RPC to use
     * @param darcID    The darc instance ID
     * @param signers   The list of signers for the transaction
     * @param type      The coin instance type
     * @param preID     If given, the InstanceID of the coin will be sha256("coin" | preID)
     * @returns a promise that resolves with the new instance
     */
    static spawn(bc, darcID, signers, type, preID) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const inst = __1.Instruction.createSpawn(darcID, CoinInstance.contractID, [new __1.Argument({ name: CoinInstance.argumentType, value: type })]);
            if (preID && bc.getProtocolVersion() >= byzcoin_rpc_1.versionPreID) {
                inst.spawn.args.push(new __1.Argument({ name: "preID", value: preID }));
            }
            yield inst.updateCounters(bc, signers);
            const ctx = __1.ClientTransaction.make(bc.getProtocolVersion(), inst);
            ctx.signWith([signers]);
            yield bc.sendTransactionAndWait(ctx, 10);
            return CoinInstance.fromByzcoin(bc, ctx.instructions[0].deriveIdArg(), 1);
        });
    }
    /**
     * Create returns a CoinInstance from the given parameters.
     * @param bc
     * @param coinID
     * @param darcID
     * @param coin
     */
    static create(bc, coinID, darcID, coin) {
        return new CoinInstance(bc, new instance_1.default({
            contractID: CoinInstance.contractID,
            darcID,
            data: coin.toBytes(),
            id: coinID,
        }));
    }
    /**
     * Initializes using an existing coinInstance from ByzCoin
     * @param bc    The RPC to use
     * @param iid   The instance ID
     * @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 coin instance
     */
    static fromByzcoin(bc, iid, waitMatch = 0, interval = 1000) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new CoinInstance(bc, yield instance_1.default.fromByzcoin(bc, iid, waitMatch, interval));
        });
    }
    /**
     * @return value of the coin.
     */
    get value() {
        return this._coin.value;
    }
    /**
     * @return the name of the coin, which is a 32-byte Buffer.
     */
    get name() {
        return this._coin.name;
    }
    /**
     * Transfer a certain amount of coin to another account.
     *
     * @param coins     the amount
     * @param to        the destination account (must be a coin contract instance id)
     * @param signers   the signers (of the giver account)
     */
    transfer(coins, to, signers) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const args = [
                new __1.Argument({ name: CoinInstance.argumentCoins, value: Buffer.from(coins.toBytesLE()) }),
                new __1.Argument({ name: CoinInstance.argumentDestination, value: to }),
            ];
            const inst = __1.Instruction.createInvoke(this.id, CoinInstance.contractID, CoinInstance.commandTransfer, args);
            yield inst.updateCounters(this.rpc, signers);
            const ctx = __1.ClientTransaction.make(this.rpc.getProtocolVersion(), inst);
            ctx.signWith([signers]);
            yield this.rpc.sendTransactionAndWait(ctx, 10);
        });
    }
    /**
     * Mine a given amount of coins
     *
     * @param signers   The list of signers for the transaction
     * @param amount    The amount to add to the coin instance
     * @param wait      Number of blocks to wait for inclusion
     */
    mint(signers, amount, wait) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const inst = __1.Instruction.createInvoke(this.id, CoinInstance.contractID, CoinInstance.commandMint, [new __1.Argument({ name: CoinInstance.argumentCoins, value: Buffer.from(amount.toBytesLE()) })]);
            yield inst.updateCounters(this.rpc, signers);
            const ctx = __1.ClientTransaction.make(this.rpc.getProtocolVersion(), inst);
            ctx.signWith([signers]);
            yield this.rpc.sendTransactionAndWait(ctx, wait);
        });
    }
    /**
     * Update the data of this instance
     *
     * @returns the updated instance
     */
    update() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const p = yield this.rpc.getProofFromLatest(this.id);
            if (!p.exists(this.id)) {
                throw new Error("fail to get a matching proof");
            }
            this._coin = Coin.decode(p.value);
            return this;
        });
    }
}
exports.default = CoinInstance;
CoinInstance.contractID = "coin";
CoinInstance.commandMint = "mint";
CoinInstance.commandFetch = "fetch";
CoinInstance.commandTransfer = "transfer";
CoinInstance.commandStore = "store";
CoinInstance.argumentCoinID = "coinID";
CoinInstance.argumentDarcID = "darcID";
CoinInstance.argumentType = "type";
CoinInstance.argumentCoins = "coins";
CoinInstance.argumentDestination = "destination";
class Coin extends light_1.Message {
    constructor(props) {
        super(props);
        this.name = Buffer.from(this.name || protobuf_1.EMPTY_BUFFER);
    }
    /**
     * @see README#Message classes
     */
    static register() {
        protobuf_1.registerMessage("byzcoin.Coin", Coin);
    }
    toBytes() {
        return Buffer.from(Coin.encode(this).finish());
    }
}
exports.Coin = Coin;
Coin.register();
