"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.Publisher = void 0;
const interfaces = __importStar(require("../lib/utils/interfaces"));
const rover_1 = require("../lib/utils/rover");
const batchBuilder_1 = require("../lib/utils/batchBuilder");
class Publisher {
    constructor(config, signer) {
        this.proofsStarted = 0;
        this.proofsCompleted = 0;
        this.initialize = async () => {
            await this.prover.initialize();
            await this.batchBuilder.initialize();
            this.provingQueue = this.batchBuilder.initBatchCircuitInputs();
            // Start watching for events
            this.prover.momiji.contract.on('TransactionSent', async (res) => {
                let tx = this.prover.resultToTransaction(res);
                console.log(`🟢 Transaction enqueued: ${tx.tx_id}.`);
                // This proves 1 tx and updates prover.status
                this.queueToProve(tx);
            });
            this.prover.momiji.contract.on('BatchPublish', async (event) => {
                // Someone else published (or we did? - check)
                //console.log(`🎯 Batch published: ${event}.`);
                // reset proving queue and mempool.queue
                this.resetCounters();
                this.batchBuilder.status = {
                    txs_hash: "",
                    tx_ids: [],
                    proof: "",
                    aggregation_object: []
                };
                await this.batchBuilder.fetchQueue();
                await this.batchBuilder.refreshState();
                this.provingQueue = this.batchBuilder.initBatchCircuitInputs();
                this.printRoot();
                this.publishTimeout = setInterval(this.publish, Number(process.env.TIMEOUT)); // every minute, publish (if there's a batch to publish)
                return;
            });
            console.log(`⌚ Event watcher started.`);
            this.printRoot();
            console.log(`🟣 Welcome to the Offshift Prover Network.`);
            this.signer && console.log(`🔑 Publisher Address: ${await this.signer.getAddress()}.`);
            const _thisQueue = await this.prover.fetchQueue();
            this.publishTimeout = setInterval(this.publish, Number(process.env.TIMEOUT)); // every minute, publish (if there's a batch to publish)
            // for each item in queue, add it to the proving queue
            if (_thisQueue.length > 0) {
                console.log(`📃 Fetched ${_thisQueue.length} unpublished transactions from queue.`);
                for (let i = 0; i < _thisQueue.length; i++) {
                    this.queueToProve(_thisQueue[i]); // queue every queued tx for proving
                }
            }
        };
        this.printRoot = () => console.log(`🌳 Current State Root: ${this.prover.stateRoot.value}`);
        // compare this.batchBuilder.status to the state contract and see if the last non-zero tx_id matches
        this.stillProving = () => this.proofsCompleted < this.proofsStarted;
        this.queueToProve = async (tx) => {
            this.proofsStarted++;
            this.provingQueue = this.provingQueue.then(async (data) => {
                return this.prove(data, tx);
            }).catch(async (e) => {
                console.log(e);
                console.log(`❌ An error occured! Batch proofs reset.`); // TODO: Gracefully restart proving with the queue when this happens
                return await this.batchBuilder.initBatchCircuitInputs(); // reset the batch
            });
        };
        this.prove = async (d, tx) => {
            return new Promise(async (res, rej) => {
                // check to make sure a batch wasn't published during the time, and kick out if it was
                if (await this.prover.momiji.contract.merkleRoot() != this.prover.stateRoot.value) {
                    // make batchBuilder dump the queue and start over
                    this.batchBuilder.clearQueue();
                    this.provingQueue = this.batchBuilder.initBatchCircuitInputs();
                    await this.provingQueue;
                    this.resetCounters();
                    return res(this.provingQueue);
                }
                console.log(`⏳ Proving ${tx.tx_id}.`);
                const proof = await this.batchBuilder.addProvenTransaction(tx, d);
                console.log(`✔️ Proved ${tx.tx_id}.`);
                this.proofsCompleted++;
                return res(proof);
            });
        };
        this.publish = async () => {
            if (this.stillProving()) {
                //console.log(`⚠️ Still proving...`)
                return; // If we're still proving something, don't publish right now
            }
            if (this.batchBuilder.status.proof === "") {
                //console.log(`⚠️ No batch to publish...`)
                return;
            }
            if (this.proofsCompleted == 0)
                return; // nothing to submit
            clearInterval(this.publishTimeout);
            console.log(`🗞️ Publishing batch...`);
            // Publish to chain
            const merkleRoot = await this.prover.momiji.contract.merkleRoot();
            const filter = this.prover.momiji.contract.filters.TransactionSent(null, merkleRoot, null);
            const txEvents = await this.prover.momiji.contract.queryFilter(filter);
            const txContractInputs = txEvents.map(e => this.prover.resultToTransaction(this.prover.toResult(e)));
            let finals = await this.batchBuilder.createFinalPublisherInputs({
                ...(await this.provingQueue),
                proof: this.batchBuilder.status.proof,
                aggregation_object: this.batchBuilder.status.aggregation_object,
                transactions: txContractInputs
            });
            const _contract = new interfaces.Contract(this.prover.config.stateContract.address, JSON.stringify(this.prover.config.stateContract.abi), this.signer);
            await _contract.publish(finals.proof, finals.aggregation_object, finals.batch_calldata)
                .then((tx) => console.log(`📡 Batch published: ${tx.hash}.`), (error) => console.log(error.message));
            return;
        };
        this.resetCounters = () => {
            // reset proof counters
            this.proofsStarted = 0;
            this.proofsCompleted = 0;
        };
        this.publishTimeout = {};
        this.provingQueue = {};
        this.prover = new rover_1.Prover(config);
        this.batchBuilder = new batchBuilder_1.BatchBuilder(config);
        this.signer = signer;
        if (!this.signer)
            throw new Error("momiji.constructor: Signer is undefined, but is required");
        const address = config.stateContract.address;
        if (!address)
            throw new Error("momiji.constructor: State Contract address is not set, but is required.");
    }
}
exports.Publisher = Publisher;