"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Prover = void 0;
const buffer_1 = require("buffer");
const os = __importStar(require("os"));
const interfaces = __importStar(require("./interfaces"));
class Prover {
    constructor(config, signer) {
        this.initialized = false;
        this.momiji = {};
        this.mempool = {
            queue: [],
            gossip: {}
        };
        this.stateRoot = {
            value: "0x00",
            index: 0
        };
        this.initialize = async () => {
            const txCircuit = this.txCircuit;
            const recCircuit = this.recCircuit;
            this.momiji.api = await interfaces.Barretenberg.new(this.threads);
            let _intermTx = new interfaces.BarretenbergBackend(this.txCircuit, { threads: this.threads });
            let _intermRec = new interfaces.BarretenbergBackend(this.recCircuit, { threads: this.threads });
            this.momiji = {
                api: this.momiji.api,
                contract: new interfaces.Contract(this.config.stateContract.address, JSON.stringify(this.config.stateContract.abi), this.provider),
                tree: this.config.tree,
                history: await this.MerkleTree(this.config.tree.stateDepth),
                transaction: {
                    circuit: this.txCircuit,
                    intermediate: _intermTx,
                    final: new interfaces.Noir(this.txCircuit, _intermTx)
                },
                recursion: {
                    verifier: new interfaces.Contract(this.config.stateContract.address, JSON.stringify(this.config.recursionVerifier.abi), this.provider),
                    circuit: this.recCircuit,
                    intermediate: _intermRec,
                    final: new interfaces.Noir(this.recCircuit, _intermRec)
                }
            };
            await this.refreshState(); 
            this.momiji.contract.on('TransactionSent', async (e) => {
                let thisTx = this.resultToTransaction(e);
                if (!(thisTx.current_root == this.momiji.history.root()))
                    await this.fetchQueue();
                this.enqueueLocally(thisTx);
            });
            this.momiji.contract.on('BatchPublish', (event) => {
                this.clearQueue();
            });
            this.initialized = true;
        };
        this.pedersen = async (input) => {
            const _bb = await interfaces.Barretenberg.new(32);
            if (!Array.isArray(input))
                input = [input];
            const _input = input;
            const _inputFr = _input.map(i => interfaces.Fr.fromBufferReduce(buffer_1.Buffer.from(i.slice(2), 'hex')));
            let hash = await _bb.pedersenHashWithHashIndex(_inputFr, 0)
                .then((response) => response.toString());
            await _bb.destroy();
            return hash;
        };
        this.randomBytesFr = (numBytes) => {
            return interfaces.Fr.fromBufferReduce(interfaces.randomBytes(numBytes));
        };
        this.toFixedHex = (number, pad0x, length = 32) => {
            let hexString = number.toString(16).padStart(length * 2, '0');
            return (pad0x ? `0x` + hexString : hexString);
        };
        this.resultToTransaction = (event) => {
            if (event) {
                return {
                    current_root: event[0],
                    deposit: event[1],
                    tx_id: event[2],
                    withdrawals: event.slice(3, 19),
                    commitment_out: event.slice(19, 35),
                    recipients: event.slice(35, 51),
                    nullifier_hashes: event.slice(51, 67),
                    proof: event.slice(67, 161),
                    pi_contract_hash: event[161]
                };
            }
            else {
                return {
                    current_root: this.toFixedHex(0, true),
                    deposit: this.toFixedHex(0, true),
                    tx_id: this.toFixedHex(0, true),
                    withdrawals: new Array(16).fill(this.toFixedHex(0, true)),
                    commitment_out: new Array(16).fill(this.toFixedHex(0, true)),
                    recipients: new Array(16).fill(this.toFixedHex(0, true)),
                    nullifier_hashes: new Array(16).fill(this.toFixedHex(0, true)),
                    proof: new Array(94).fill(this.toFixedHex(0, true)),
                    pi_contract_hash: this.toFixedHex(0, true)
                };
            }
        };
        this.hasSigner = () => this.signer !== undefined;
        this.keccak256 = (data) => {
            let _dataBytes = new Uint8Array(buffer_1.Buffer.from(data.slice(2), 'hex'));
            let _hashModulo = interfaces.Fr.fromBufferReduce(buffer_1.Buffer.from(interfaces.ethers.keccak256(_dataBytes).slice(2), 'hex'));
            return _hashModulo.toString();
        };
        this.generateSecret = () => this.randomBytesFr(32);
        this.MerkleTree = async (depth, leaves) => {
            const _bb = await interfaces.Barretenberg.new(32);
            const tree = new interfaces.MerkleTree(depth, _bb, leaves);
            await tree.init();
            await _bb.destroy();
            return tree;
        };
        this.toResult = (e) => { return e.args[0]; };
        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.refreshState = async () => {
            let historic_roots = (await this.momiji.contract.getValidRoots());
            this.stateRoot.value = (await this.momiji.contract.merkleRoot());
            this.stateRoot.index = historic_roots.length - 1;
            this.momiji.history = (await this.MerkleTree(this.config.tree.stateDepth, historic_roots));
            await this.fetchQueue();
        };
        this.clearQueue = () => {
            this.mempool.queue = [];
            this.refreshState();
        };
        this.enqueue = async (transaction) => {
            this.enqueueLocally(transaction);
            if (this.signer)
                for (let transaction of this.mempool.queue)
                    await this.momiji.contract.enqueue(transaction);
        };
        this.enqueueLocally = async (transaction) => {
            if (!Array.isArray(transaction))
                transaction = [transaction];
            this.mempool.queue.push(...transaction);
        };
        if (!signer) {
            if (config.PRIVATE_KEY)
                this.signer = new interfaces.Wallet(config.PRIVATE_KEY, new interfaces.JsonRpcProvider(config.provider));
            else
                this.signer = undefined;
        }
        else {
            this.signer = signer;
        }
        this.provider = new interfaces.JsonRpcProvider(config.provider);
        this.config = config;
        this.txCircuit = config.transactionCircuit;
        this.recCircuit = config.recursionCircuit;
        this.config.tree.treeSum = [this.config.tree.utxoDepth, this.config.tree.txDepth, this.config.tree.stateDepth].reduce((a, b) => a + b);
        this.abi = interfaces.AbiCoder.defaultAbiCoder();
        this.threads = os.cpus().length - 2; 
    }
    keccakHashArray(data) {
    }
}
exports.Prover = Prover;