"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
/* PROMISE_DEF */
const BluebirdPromise = require("bluebird");
const log_1 = require("../log/log");
const SteemOperationNumber_1 = require("../blockchain/SteemOperationNumber");
const SetRules_1 = require("../protocol/SetRules");
const ConfirmVote_1 = require("../protocol/ConfirmVote");
const SendVoteorder_1 = require("../protocol/SendVoteorder");
class UniversalSynchronizer {
    constructor(api, protocol, callbacks) {
        this.isRunning_ = true;
        this.lastProcessedOperationNum = new SteemOperationNumber_1.SteemOperationNumber(0, 0, 0);
        this.api = api;
        this.protocol = protocol;
        this.callbacks = callbacks;
    }
    // this function only starts the loop via processBlock, which then calls processBlock(blockNum+1)
    start(since) {
        return __awaiter(this, void 0, void 0, function* () {
            log_1.Log.log().debug("SYNCHRONIZER_RUN_LOOP=" + JSON.stringify({ since: since }));
            this.lastProcessedOperationNum = since;
            this.safeCall(() => this.callbacks.onStart ? this.callbacks.onStart() : undefined);
            try {
                yield this.processBlock(since.blockNum); // the loop is started
            }
            catch (error) {
                this.safeCall(() => this.callbacks.onError ? this.callbacks.onError(error, false) : undefined);
            }
            this.safeCall(() => this.callbacks.onFinished ? this.callbacks.onFinished() : undefined);
        });
    }
    stop() {
        // when isRunning=false, continueIfRunning() will not call it's callback parameter
        this.isRunning_ = false;
    }
    isRunning() {
        return this.isRunning_;
    }
    getLastProcessedOperation() {
        return this.lastProcessedOperationNum.clone();
    }
    processBlock(blockNum) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.isRunning_) {
                return;
            }
            this.safeCall(() => this.callbacks.onBlockProcessingStart ? this.callbacks.onBlockProcessingStart(blockNum) : undefined);
            try {
                const ops = yield this.api.getAllWiseOperationsInBlock(blockNum);
                this.safeCall(() => this.callbacks.onBlockOperationsLoaded ? this.callbacks.onBlockOperationsLoaded(blockNum, ops) : undefined);
                for (let i = 0; i < ops.length; i++) {
                    const op = ops[i];
                    yield this.processOperation(op);
                }
                this.safeCall(() => this.callbacks.onBlockProcessingFinished ? this.callbacks.onBlockProcessingFinished(blockNum) : undefined);
                yield this.processBlock(blockNum + 1);
            }
            catch (error) {
                this.safeCall(() => this.callbacks.onError ? this.callbacks.onError(error, true) : undefined);
                yield BluebirdPromise.delay(UniversalSynchronizer.WAIT_ON_ERROR_MS);
                yield this.processBlock(blockNum);
            }
        });
    }
    processOperation(op) {
        return __awaiter(this, void 0, void 0, function* () {
            const currentOpNum = op.moment;
            // retry continues from last trx. There is no need to process transactions from the beginning of the block second time.
            if (currentOpNum.isGreaterThan(this.lastProcessedOperationNum)) {
                if (SetRules_1.SetRules.isSetRules(op.command)) {
                    const setRules = op.command;
                    this.safeCall(() => this.callbacks.onSetRules(setRules, op));
                }
                else if (SendVoteorder_1.SendVoteorder.isSendVoteorder(op.command)) {
                    const sendVoteorder = op.command;
                    this.safeCall(() => this.callbacks.onVoteorder(sendVoteorder, op));
                }
                else if (ConfirmVote_1.ConfirmVote.isConfirmVote(op.command)) {
                    const confirmVote = op.command;
                    this.safeCall(() => {
                        if (this.callbacks.onConfirmVote) {
                            this.callbacks.onConfirmVote(confirmVote, op);
                        }
                    });
                }
            }
            this.lastProcessedOperationNum = op.moment;
        });
    }
    safeCall(fn) {
        if (!fn)
            return;
        (() => __awaiter(this, void 0, void 0, function* () {
            try {
                yield fn();
            }
            catch (error) {
                log_1.Log.log().error("Unhandled error in UniversalSynchronizer callback: " + error);
                log_1.Log.log().exception(log_1.Log.level.error, error);
            }
        }))();
    }
}
UniversalSynchronizer.WAIT_ON_ERROR_MS = 3100;
exports.UniversalSynchronizer = UniversalSynchronizer;
//# sourceMappingURL=UniversalSynchronizer.js.map