"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.P2P = exports.BaseP2PWorker = exports.P2pManager = void 0;
const os = __importStar(require("os"));
const logger_1 = __importDefault(require("../logger"));
const state_1 = require("../models/state");
const utils_1 = require("../utils");
const config_1 = require("./config");
class P2pManager {
    constructor({ configService = config_1.Config } = {}) {
        this.workerClasses = {};
        this.configService = configService;
        this.workers = new Array();
    }
    register(chain, network, worker) {
        this.workerClasses[chain] = this.workerClasses[chain] || {};
        this.workerClasses[chain][network] = worker;
    }
    get(chain, network) {
        return this.workerClasses[chain][network];
    }
    async stop() {
        logger_1.default.info('Stopping P2P Manager');
        for (const worker of this.workers) {
            await worker.stop();
        }
        this.workers = [];
    }
    async start() {
        if (this.configService.isDisabled('p2p')) {
            logger_1.default.info('Disabled P2P Manager');
            return;
        }
        logger_1.default.info('Starting P2P Manager');
        for (const chainNetwork of config_1.Config.chainNetworks()) {
            const { chain, network } = chainNetwork;
            const chainConfig = config_1.Config.chainConfig(chainNetwork);
            if ((chainConfig.chainSource && chainConfig.chainSource !== 'p2p') || chainConfig.disabled) {
                continue;
            }
            logger_1.default.info(`Starting P2P worker ${chain}:${network}`);
            const p2pWorker = new this.workerClasses[chain][network]({
                chain,
                network,
                chainConfig
            });
            this.workers.push(p2pWorker);
            try {
                p2pWorker.start();
            }
            catch (e) {
                logger_1.default.error('P2P Worker %o:%o died: %o', chain, network, e.stack || e.message || e);
            }
        }
    }
}
exports.P2pManager = P2pManager;
class BaseP2PWorker {
    constructor(params) {
        this.params = params;
        this.lastHeartBeat = '';
        this.queuedRegistrations = new Array();
        this.stopping = false;
        this.chain = '';
        this.network = '';
        this.isSyncingNode = false;
    }
    async start() { }
    async stop() { }
    async sync() { }
    getIsSyncingNode() {
        if (!this.lastHeartBeat) {
            return false;
        }
        const [hostname, pid, timestamp] = this.lastHeartBeat.split(':');
        const hostNameMatches = hostname === os.hostname();
        const pidMatches = pid === process.pid.toString();
        const timestampIsFresh = Date.now() - parseInt(timestamp) < 5 * 60 * 1000;
        const amSyncingNode = hostNameMatches && pidMatches && timestampIsFresh;
        return amSyncingNode;
    }
    async waitTilSync() {
        while (true) {
            if (this.isSyncingNode) {
                return;
            }
            await (0, utils_1.wait)(500);
        }
    }
    async refreshSyncingNode() {
        while (!this.stopping) {
            const wasSyncingNode = this.getIsSyncingNode();
            this.lastHeartBeat = await state_1.StateStorage.getSyncingNode({ chain: this.chain, network: this.network });
            const nowSyncingNode = this.getIsSyncingNode();
            this.isSyncingNode = nowSyncingNode;
            if (wasSyncingNode && !nowSyncingNode) {
                throw new Error('Syncing Node Renewal Failure');
            }
            if (!wasSyncingNode && nowSyncingNode) {
                logger_1.default.info(`This worker is now the syncing node for ${this.chain} ${this.network}`);
                this.sync();
            }
            if (!this.lastHeartBeat || this.getIsSyncingNode()) {
                this.registerSyncingNode({ primary: true });
            }
            else {
                logger_1.default.info('Another node is the primary syncing node');
                this.registerSyncingNode({ primary: false });
            }
            await (0, utils_1.wait)(500);
        }
    }
    async registerSyncingNode({ primary }) {
        const lastHeartBeat = this.lastHeartBeat;
        const queuedRegistration = setTimeout(() => {
            state_1.StateStorage.selfNominateSyncingNode({
                chain: this.chain,
                network: this.network,
                lastHeartBeat
            }).catch(err => logger_1.default.error('Error in selfNominateSyncingNode:', err));
        }, primary ? 0 : 5 * 60 * 1000);
        this.queuedRegistrations.push(queuedRegistration);
    }
    async unregisterSyncingNode() {
        await (0, utils_1.wait)(1000);
        try {
            this.lastHeartBeat = await state_1.StateStorage.getSyncingNode({ chain: this.chain, network: this.network });
            if (this.getIsSyncingNode()) {
                await state_1.StateStorage.selfResignSyncingNode({
                    chain: this.chain,
                    network: this.network,
                    lastHeartBeat: this.lastHeartBeat
                });
            }
        }
        catch (e) {
            logger_1.default.warn('Issue unregistering');
            logger_1.default.error('%o', e);
        }
    }
}
exports.BaseP2PWorker = BaseP2PWorker;
exports.P2P = new P2pManager();
//# sourceMappingURL=p2p.js.map