"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.TransactionBuilder = void 0;
const rover_1 = require("./rover");
const interfaces = __importStar(require("./interfaces"));
class TransactionBuilder extends rover_1.Prover {
    constructor() {
        super(...arguments);
        this.zeroValue = "0x016a430aa58685aba1311244a973a3bc358859da86784be51094368e8fb6f720";
        this.zeroHex = "0x0000000000000000000000000000000000000000000000000000000000000000"; 
        this.withdrawals = [];
        this.utxoproofs = []; 
        this.utxos = []; 
        this.depositTotal = "0";
        this.contractInputs = [];
        this.getOracle = async (old_root) => await this.pedersen(this.toFixedHex(0, true));
        this.fetchSiblings = async (utxo) => {
            const utxoId = await this.pedersen([utxo.owner, utxo.amount, utxo.assetType]);
            const merkleRoot = await this.momiji.contract.getRootFromUtxo(utxoId);
            const filter = this.momiji.contract.filters.TransactionSent(null, merkleRoot, null);
            const txEvents = await this.momiji.contract.queryFilter(filter);
            let siblings = [];
            for (let i = 0; i < txEvents.length; i++) {
                let commitments = [];
                let event = txEvents[i];
                let args = event.topics; 
                for (let j = 19; j < 35; j++) {
                    if (args[0][j] !== this.toFixedHex(0, true))
                        commitments.push(args[0][j]);
                    else
                        commitments.push("0x016a430aa58685aba1311244a973a3bc358859da86784be51094368e8fb6f720");
                }
                if (commitments.includes(utxoId))
                    siblings = commitments;
            }
            return siblings;
        };
        this.makeUTXOTree = async (utxo) => {
            if (!utxo)
                utxo = this.utxos;
            let commitments = [];
            for (let i = 0; i < utxo.length; i++)
                commitments[i] = await this.pedersen([utxo[i].owner, utxo[i].amount, utxo[i].assetType]);
            let utxoTree = new interfaces.MerkleTree(this.config.tree.utxoDepth, this.momiji.api, commitments);
            await utxoTree.init();
            return utxoTree;
        };
        this.proveTransaction = async (transaction, public_inputs) => {
            const _contract = new interfaces.Contract(this.config.stateContract.address, JSON.stringify(this.config.stateContract.abi), this.signer);
            let txWitness = await this.momiji.transaction.final.execute(transaction);
            let txProof = await this.momiji.transaction.intermediate.generateIntermediateProof(txWitness.witness);
            let txProofArtifacts = await this.momiji.transaction.intermediate.generateIntermediateProofArtifacts(txProof, 1); 
            const pi = public_inputs;
            const _contract_inputs = [
                pi.current_root,
                pi.deposit,
                pi.tx_id,
                pi.withdrawals,
                pi.commitment_out,
                pi.recipients,
                pi.nullifier_hashes,
                txProofArtifacts.proofAsFields,
                transaction.pi_contract_hash
            ].flat();
            _contract.enqueue.send(_contract_inputs).then((_tx) => console.log(_tx), (_r) => console.log(_r));
            return {
                proof: txProofArtifacts.proofAsFields,
                pi_contract_hash: transaction.pi_contract_hash,
                ...public_inputs
            };
        };
        this.hashPublicInputs = (public_inputs) => {
            const inputsForHashing = [
                "0x",
                public_inputs.current_root.slice(2),
                public_inputs.deposit.slice(2),
                public_inputs.tx_id.slice(2),
                [...public_inputs.withdrawals.map(w => w.slice(2))].join(''),
                [...public_inputs.commitment_out.map(w => w.slice(2))].join(''),
                [...public_inputs.recipients.map(w => w.slice(2))].join(''),
                [...public_inputs.nullifier_hashes.map(w => w.slice(2))].join(''),
            ].join('');
            return this.keccak256(inputsForHashing);
        };
        this.deposit = (amount) => this.depositTotal = this.toFixedHex(amount, true);
        this.send = async (sends) => {
            let utxos = await this.createUTXO(sends);
            for (let i = 0; i < utxos.length; i++)
                this.utxos.push(utxos[i]);
            return utxos;
        };
        this.withdraw = (withdrawals) => Array.isArray(withdrawals) ? withdrawals.forEach(w => this.withdrawals.push(w)) : this.withdrawals.push(withdrawals);
        this.createTransactions = async (transactions) => {
            if (transactions === undefined) {
                const _nullifier_hashes = new Array(16).fill(this.zeroValue);
                for (let i = 0; i < this.utxoproofs.length; i++) {
                    let _hash = await this.pedersen([this.utxoproofs[i].secret, this.utxoproofs[i].secret]);
                    _nullifier_hashes[i] = _hash;
                }
                const _withdrawals = this.withdrawals;
                const _utxoProofs = this.utxoproofs;
                const _utxoOut = this.utxos;
                const _commitmentOut = new Array(16).fill(this.zeroValue);
                for (let u = 0; u < _utxoOut.length; u++) {
                    _commitmentOut[u] = await this.pedersen([_utxoOut[u].owner, _utxoOut[u].amount, _utxoOut[u].assetType]);
                }
                let utxoTree = await this.makeUTXOTree();
                transactions = [{
                        current_root: this.momiji.history.root(),
                        deposit: this.depositTotal,
                        tx_id: utxoTree.root(),
                        withdrawals: new Array(16).fill(this.zeroHex).map(function (_, i, _w) {
                            if (_withdrawals[i] !== undefined)
                                return _withdrawals[i].amount;
                            else
                                return _w[i];
                        }),
                        commitment_out: _commitmentOut,
                        recipients: new Array(16).fill(this.zeroHex).map(function (_, i, _w) {
                            if (_withdrawals[i] !== undefined)
                                return _withdrawals[i].recipient;
                            else
                                return _w[i];
                        }),
                        nullifier_hashes: _nullifier_hashes,
                        utxo_in: new Array(16).fill(this.zeroHex).map(function (_, i, _ui) {
                            if (_utxoProofs[i] !== undefined)
                                return _utxoProofs[i];
                            else
                                return {
                                    owner: "0x0000000000000000000000000000000000000000000000000000000000000000",
                                    amount: "0x0000000000000000000000000000000000000000000000000000000000000000",
                                    assetType: "0x0000000000000000000000000000000000000000000000000000000000000000"
                                };
                        }),
                        utxo_out: new Array(16).fill(this.zeroHex).map((_, i) => (_utxoOut[i] !== undefined)
                            ? _utxoOut[i]
                            : _utxoOut[i])
                    }];
            }
            else {
                if (!Array.isArray(transactions))
                    transactions = [transactions];
            }
            let provingQueue = Promise.resolve();
            for (let i = 0; i < transactions.length; i++) {
                let thisTx = transactions[i];
                let public_inputs = thisTx;
                let transaction = {
                    pi_contract_hash: this.hashPublicInputs(public_inputs),
                    current_root: thisTx.current_root,
                    deposit: thisTx.deposit,
                    withdrawals: thisTx.withdrawals,
                    commitment_out: thisTx.commitment_out,
                    recipients: thisTx.recipients,
                    nullifier_hashes: thisTx.nullifier_hashes,
                    tx_id: thisTx.tx_id,
                    oracle: new Array(2 ** this.momiji.tree.utxoDepth).fill(this.zeroHex)
                        .map((_, j, _oracle) => (thisTx.utxo_in[j].oracle !== undefined)
                        ? thisTx.utxo_in[j].oracle
                        : _oracle[j]),
                    old_root_proof: new Array(2 ** this.momiji.tree.utxoDepth).fill(this.zeroHex)
                        .map((_, j, _old_root_proof) => (thisTx.utxo_in[j].old_root_proof !== undefined)
                        ? thisTx.utxo_in[j].old_root_proof
                        : _old_root_proof[j]),
                    secrets: new Array(2 ** this.momiji.tree.utxoDepth).fill(this.zeroHex)
                        .map((_, j, _secrets) => thisTx.utxo_in[j].secret !== undefined ? thisTx.utxo_in[j].secret : _secrets[j]),
                    utxo_in: new Array(2 ** this.momiji.tree.utxoDepth).fill(this.zeroHex) 
                        .map((_, j, _utxo_in) => [
                        thisTx.utxo_in[j].owner,
                        thisTx.utxo_in[j].amount,
                        thisTx.utxo_in[j].assetType
                    ])
                        .flat(),
                    utxo_out: (new Array(2 ** this.momiji.tree.utxoDepth).fill(this.zeroHex) 
                        .map((_, j) => thisTx.utxo_out[j] ? 
                        [thisTx.utxo_out[j].owner, thisTx.utxo_out[j].amount, thisTx.utxo_out[j].assetType] : 
                        new Array(3).fill(this.zeroHex))) 
                        .flat(),
                    indexes: (new Array(2 ** this.momiji.tree.utxoDepth).fill(this.zeroHex) 
                        .map((_, j) => thisTx.utxo_in[j].indexes ? 
                        thisTx.utxo_in[j].indexes : 
                        new Array(3).fill(this.zeroHex))) 
                        .flat(),
                    hash_path: (new Array(2 ** this.momiji.tree.utxoDepth).fill(this.zeroHex) 
                        .map((_, j) => thisTx.utxo_in[j].hash_path ? 
                        thisTx.utxo_in[j].hash_path : 
                        new Array(17).fill(this.zeroHex))) 
                        .flat(), 
                };
                provingQueue.then(async () => {
                    let transactionProof = await this.proveTransaction(transaction, public_inputs); 
                    this.contractInputs.push(transactionProof); 
                });
                await provingQueue; 
            }
            console.log(this.contractInputs);
            return this.contractInputs;
        };
    }
    async spendUTXO(utxo) {
        let provenUTXOs = await this.makeUTXOProof(utxo);
        for (let i = 0; i < provenUTXOs.length; i++)
            this.utxoproofs.push(provenUTXOs[i]);
        return provenUTXOs;
    }
    async makeUTXOProof(utxo) {
        if (!Array.isArray(utxo))
            utxo = [utxo];
        let utxoProofList = [];
        for (let i = 0; i < utxo.length; i++) {
            const siblings = await this.fetchSiblings(utxo[i]);
            const commitment = await this.pedersen([utxo[i].owner, utxo[i].amount, utxo[i].assetType]);
            const oldRoot = await this.momiji.contract.getRootFromUtxo(commitment);
            const txBatch = await this.momiji.contract.getTxIds(oldRoot);
            let trees = {
                utxo_tree: await this.MerkleTree(this.momiji.tree.utxoDepth, siblings),
                tx_tree: await this.MerkleTree(this.momiji.tree.txDepth, txBatch),
                historic_tree: this.momiji.history,
            };
            await Promise.all([
                trees.utxo_tree.init(),
                trees.tx_tree.init(),
                trees.historic_tree.init()
            ]);
            let txId = trees.utxo_tree.root();
            let txRoot = trees.tx_tree.root();
            let oracle = await this.getOracle(oldRoot);
            let batch = await this.pedersen([txRoot, oracle]);
            let new_root = await this.pedersen([batch, oldRoot]);
            const utxoMerkleIndex = trees.utxo_tree.getIndex(commitment);
            const txMerkleIndex = trees.tx_tree.getIndex(txId);
            const historicMerkleIndex = trees.historic_tree.getIndex(new_root);
            const utxoMerkleProof = await trees.utxo_tree.proof(utxoMerkleIndex);
            const txMerkleProof = await trees.utxo_tree.proof(txMerkleIndex);
            const historicMerkleProof = await trees.utxo_tree.proof(historicMerkleIndex);
            const utxoProof = {
                secret: utxo[i].secret,
                owner: utxo[i].owner,
                amount: utxo[i].amount,
                assetType: utxo[i].assetType,
                nullifier_hash: await this.pedersen([utxo[i].secret, utxo[i].secret]),
                oracle: oracle,
                old_root_proof: oldRoot,
                current_root: trees.historic_tree.root(),
                indexes: [utxoMerkleIndex, txMerkleIndex, historicMerkleIndex].map(i => this.toFixedHex(i, true)),
                hash_path: [
                    ...utxoMerkleProof.pathElements,
                    ...txMerkleProof.pathElements,
                    ...historicMerkleProof.pathElements
                ]
            };
            utxoProofList.push(utxoProof);
        }
        return utxoProofList;
    }
    async createUTXO(utxo) {
        if (!Array.isArray(utxo))
            utxo = [utxo];
        const utxoSet = [];
        await Promise.all(utxo.map(async (utxo) => {
            utxo.owner = (utxo.secret ? await this.pedersen(utxo.secret) : undefined);
            if (utxo.owner)
                utxoSet.push(utxo);
        }));
        return utxoSet;
    }
    viewBalance(assetType, token) {
        if (token) {
        }
        if (assetType) {
        }
    }
}
exports.TransactionBuilder = TransactionBuilder;