"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BatchBuilder = void 0;
const rover_1 = require("./rover");
class BatchBuilder extends rover_1.Prover {
    constructor() {
        super(...arguments);
        this.zeroValue = "0x016a430aa58685aba1311244a973a3bc358859da86784be51094368e8fb6f720";
        this.zeroHex = "0x0000000000000000000000000000000000000000000000000000000000000000"; 
        this.withdrawals = [];
        this.depositTotal = "0";
        this.contractInputs = [];
        this.enqueueQueue = [];
        this.status = {
            txs_hash: "",
            tx_ids: [],
            proof: "",
            aggregation_object: []
        };
        this.fetchQueue = async () => {
            const merkleRoot = await this.momiji.contract.merkleRoot();
            const filter = this.momiji.contract.filters.TransactionSent(null, merkleRoot, null);
            const txEvents = await this.momiji.contract.queryFilter(filter);
            const txContractInputs = txEvents.map(e => this.resultToTransaction(this.toResult(e)));
            this.clearQueue();
            this.enqueueLocally(txContractInputs);
            return txContractInputs;
        };
        this.addToStatus = (update) => {
            this.status.tx_ids.push(update.id);
            this.status.txs_hash = this.keccak256('0x' + this.status.tx_ids.map(tx => tx.slice(2)).join(''));
            this.status.proof = update.proof;
            this.status.aggregation_object = update.aggregation_object;
        };
        this.publishBatch = async (data) => {
            if (!this.hasSigner())
                throw new Error(`batchBuilder.publishBatch: Signer is undefined, but is required.`);
            if (Array.isArray(data) && !this.status.proof) {
                data = data;
                console.log(`batchBuilder.publishBatch: No batch passed and no batch being generated, generating new proofs.`);
                this.publishBatch(await this.proveValidBatch(undefined, data));
            }
            let publishTx = await this.momiji.contract.publish(data);
            console.log(`batchBuilder.publishBatch: Batch published: ${publishTx.hash}.`);
            console.log(publishTx);
        };
        this.createFinalPublisherInputs = async (batch) => {
            let new_root = await this.calculateNewStateRoot(batch.transactions.map(tx => tx.tx_id), this.stateRoot.value, (await this.pedersen(this.toFixedHex(0, true))));
            let _batch = [
                this.config.transactionVkHash,
                this.stateRoot.value,
                new_root,
                await this.pedersen(this.toFixedHex(0, true)),
                ...(batch.transactions.map(tx => [
                    tx.current_root,
                    tx.deposit,
                    tx.tx_id,
                    ...tx.withdrawals,
                    ...tx.commitment_out,
                    ...tx.recipients,
                    ...tx.nullifier_hashes,
                    ...tx.proof,
                    tx.pi_contract_hash
                ])).flat()
            ];
            return {
                proof: this.status.proof,
                aggregation_object: this.status.aggregation_object,
                batch_calldata: _batch,
            };
        };
        this.createBatchPublicInputs = async (transactions) => {
            if (transactions === undefined)
                transactions = this.mempool.queue; 
            if (!Array.isArray(transactions))
                transactions = [transactions];
            let _transactions = transactions;
            return {
                key_hash: this.config.transactionVkHash,
                old_root: this.stateRoot.value,
                new_root: await this.calculateNewStateRoot(_transactions.map(tx => tx.tx_id), this.stateRoot.value, (await this.pedersen(this.toFixedHex(0, true)))),
                oracle: await this.pedersen(this.toFixedHex(0, true)),
                tx_ids: new Array(16).fill(this.zeroValue).map((_, idx, _arr) => {
                    let thisTx = (_transactions[idx] !== undefined) ? _transactions[idx].tx_id : _arr[idx];
                    return thisTx;
                })
            };
        };
        this.addProvenTransaction = async (transaction, batch) => {
            if (!batch)
                batch = await this.initBatchCircuitInputs();
            if (batch.tx_ids.filter(i => i == this.zeroValue).length >= 16) {
                console.log(`batchBuilder.addTransaction: Batch is full. Tossing tx.`);
                return batch;
            }
            if (!batch.tx_ids.includes(transaction.tx_id)) {
                batch.tx_ids[batch.tx_ids.indexOf(this.zeroValue)] = transaction.tx_id;
            }
            batch.oracle = await this.pedersen(this.toFixedHex(0, true)); 
            batch.new_root = await this.calculateNewStateRoot(batch.tx_ids, batch.old_root, batch.oracle);
            batch.pi_contract_hash = this.keccak256([
                "0x",
                batch.key_hash.slice(2),
                batch.old_root.slice(2),
                batch.new_root.slice(2),
                batch.oracle.slice(2),
                ...batch.tx_ids.map(tx => tx.slice(2))
            ].join(''));
            batch.proof = transaction.proof;
            batch.public_inputs = transaction.proof.slice(0, 1);
            const recursiveProof = await this.momiji.recursion.final.generateFinalProof(batch);
            const recursiveProofVerified = await this.momiji.recursion.final.verifyFinalProof(recursiveProof);
            if (!recursiveProofVerified) {
                console.log(`batchBuilder.addTransaction: Recursive proof failed verification on tx_id ${transaction.tx_id}. Tossing tx.`);
            }
            else {
                const proofHex = '0x' + Buffer.from(recursiveProof.proof).toString('hex');
                let publicInputsHex = [];
                if (recursiveProof.publicInputs instanceof Array) {
                    publicInputsHex = recursiveProof.publicInputs.map(pi => `0x` + Buffer.from(pi).toString('hex'));
                }
                const batchStatusUpdate = {
                    id: transaction.tx_id,
                    proof: proofHex,
                    aggregation_object: publicInputsHex.slice(1)
                };
                this.addToStatus(batchStatusUpdate);
                batch.input_aggregation_object = batchStatusUpdate.aggregation_object;
            }
            return batch;
        };
        this.calculateNewStateRoot = async (txs, old_root, oracle) => {
            let txRoot = await this.MerkleTree(this.config.tree.txDepth, txs).then(result => result.root());
            let batch = await this.pedersen([txRoot, oracle]);
            let new_root = await this.pedersen([batch, old_root]);
            return new_root;
        };
        this.proveBatch = () => {
        };
        this.proveValidBatch = async (batch, transactions) => {
            if (!Array.isArray(transactions))
                transactions = [transactions];
            if (!transactions)
                transactions = this.mempool.queue; 
            let _transactions = transactions;
            let _batch = batch || await this.createBatchPublicInputs(transactions);
            let batchCircuitInputs = await this.initBatchCircuitInputs(_batch);
            let aggregate = await new Array(_transactions.length)
                .reduce(async (acc, _, i) => await this.addProvenTransaction(_transactions[i], acc), batchCircuitInputs);
            let finals = {
                ...aggregate,
                proof: this.status.proof,
                aggregation_object: this.status.aggregation_object,
                transactions: transactions
            };
            console.log(`batchBuilder.proveValidBatch: Done proving batch.`);
            return finals;
        };
        this.initBatchCircuitInputs = async (pi) => {
            if (!pi)
                pi = (await this.createBatchPublicInputs());
            return {
                ...pi,
                pi_contract_hash: this.keccak256([
                    "0x",
                    pi.key_hash.slice(2),
                    pi.old_root.slice(2),
                    pi.new_root.slice(2),
                    pi.oracle.slice(2),
                    ...pi.tx_ids.map(tx => tx.slice(2))
                ].join('')),
                verification_key: this.config.transactionVk,
                proof: new Array(94).fill(this.toFixedHex(0, true)),
                public_inputs: new Array(1).fill(this.toFixedHex(0, true)),
                input_aggregation_object: new Array(16).fill(this.toFixedHex(0, true))
            };
        };
    }
}
exports.BatchBuilder = BatchBuilder;