"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 (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;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const Context = __importStar(require("../p2p/Context"));
const profiler_1 = require("../utils/profiler");
const Self_1 = require("../p2p/Self");
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const NodeList = __importStar(require("../p2p/NodeList"));
const Self = __importStar(require("../p2p/Self"));
class ExitHandler {
    constructor(logDir, _memoryReporting, _nestedCounters) {
        this.exited = false;
        this.syncFuncs = new Map();
        this.asyncFuncs = new Map();
        this.memStats = _memoryReporting;
        this.counters = _nestedCounters;
        this.exitLogger = Context.logger.getLogger('exit');
        this.logDir = logDir;
        Self.emitter.once('active', () => {
            let cycles = Context.p2p.getLatestCycles(1);
            if (cycles.length > 0) {
                this.activeStartTime = cycles[0].start * 1000;
            }
        });
        Self.emitter.on('cycle_q1_start', () => {
            if (Self.isActive) {
                let rotatationIndex = NodeList.getAgeIndex();
                if (rotatationIndex.idx >= 0) {
                    this.lastRotationIndex = rotatationIndex;
                }
                let cycles = Context.p2p.getLatestCycles(1);
                if (cycles.length > 0) {
                    this.lastActiveTime = cycles[0].start * 1000;
                }
            }
            this.writeNodeProgress();
        });
        this.writeStartSummary();
    }
    // Modules can use this to register synchronous cleanup functions
    registerSync(who, func) {
        this.syncFuncs.set(who, func);
    }
    // Modules can use this to register asynchronous cleanup functions
    registerAsync(who, func) {
        this.asyncFuncs.set(who, func);
    }
    // Cleans up all modules with registered async functions
    async _cleanupAsync() {
        for (const [, func] of this.asyncFuncs) {
            await func();
        }
    }
    // Cleans up all modules with registered sync functions
    _cleanupSync() {
        for (const [, func] of this.syncFuncs) {
            func();
        }
    }
    // Exits after cleaning up with all registered functions
    async exitCleanly(exitType, message, exitProcess = true) {
        if (this.exited)
            return;
        this.exited = true;
        this._cleanupSync();
        try {
            this.runExitLog(true, exitType, message);
            await this._cleanupAsync();
        }
        catch (e) {
            console.error(e);
        }
        // eslint-disable-next-line no-process-exit
        if (exitProcess)
            process.exit();
    }
    async exitUncleanly(exitType, message) {
        if (this.exited)
            return;
        this.exited = true;
        this._cleanupSync();
        try {
            this.runExitLog(false, exitType, message);
            await this._cleanupAsync();
        }
        catch (e) {
            console.error(e);
        }
        // eslint-disable-next-line no-process-exit
        process.exit(1); // exiting with status 1 causes our modified PM2 to not restart the process
    }
    runExitLog(isCleanExit, exitType, msg) {
        this.exitLogger.fatal(`isCleanExit: ${isCleanExit}  exitType: ${exitType}  msg: ${msg}`);
        let log = [];
        const fakeStream = {
            write: (data) => {
                log.push(data);
            },
        };
        const toMB = 1 / 1000000;
        const report = process.memoryUsage();
        log.push(`System Memory Report.  Timestamp: ${Date.now()}\n`);
        log.push(`rss: ${(report.rss * toMB).toFixed(2)} MB\n`);
        log.push(`heapTotal: ${(report.heapTotal * toMB).toFixed(2)} MB\n`);
        log.push(`heapUsed: ${(report.heapUsed * toMB).toFixed(2)} MB\n`);
        log.push(`external: ${(report.external * toMB).toFixed(2)} MB\n`);
        log.push(`arrayBuffers: ${(report.arrayBuffers * toMB).toFixed(2)} MB\n\n\n`);
        this.memStats.gatherReport();
        this.memStats.reportToStream(this.memStats.report, fakeStream, 0);
        this.exitLogger.fatal(log.join(''));
        log = [];
        profiler_1.profilerInstance.scopedProfileSectionStart('counts');
        const arrayReport = this.counters.arrayitizeAndSort(this.counters.eventCounters);
        this.counters.printArrayReport(arrayReport, fakeStream, 0);
        profiler_1.profilerInstance.scopedProfileSectionEnd('counts');
        this.exitLogger.fatal(log.join(''));
        this.writeExitSummary(isCleanExit, exitType, msg);
    }
    //not exactly an exit statement but is similar to our exit report
    //will overwrite with the latest info
    writeNodeProgress() {
        let nodeProgress = {
            nodeInfo: null,
            lastRotationIndex: this.lastRotationIndex,
            activeNodes: NodeList.activeByIdOrder.length,
            lastActiveTime: this.lastActiveTime,
            totalActiveTime: 0,
        };
        if (this.activeStartTime > 0 && this.lastActiveTime > 0) {
            nodeProgress.totalActiveTime = this.lastActiveTime - this.activeStartTime;
        }
        try {
            nodeProgress.nodeInfo = (0, Self_1.getPublicNodeInfo)();
        }
        catch (er) { }
        try {
            let filePath = path_1.default.join(this.logDir, 'node-progress.json');
            let content = JSON.stringify(nodeProgress, null, 2);
            fs_1.default.writeFileSync(filePath, content, { encoding: 'utf8', flag: 'w' });
        }
        catch (er) { }
    }
    writeExitSummary(isCleanExit, exitType, msg) {
        let exitSummary = {
            status: '',
            message: msg,
            exitTime: Date.now(),
            totalActiveTime: 0,
            nodeInfo: null,
            lastRotationIndex: this.lastRotationIndex,
            activeNodes: NodeList.activeByIdOrder.length,
            lastActiveTime: this.lastActiveTime,
        };
        // a bet of logic here to sort out what is a clean vs. warning vs. error exit
        if (isCleanExit) {
            if (exitType === `Apoptosized`) {
                exitSummary.status = 'Exit with warning';
            }
            else {
                exitSummary.status = 'Exited cleanly';
            }
        }
        else {
            if (exitType === 'SIGINT') {
                exitSummary.status = 'Exit with warning';
            }
            else if (exitType === 'SIGTERM' || exitType === 'message:shutdown') {
                exitSummary.status = 'Exit with error';
            }
            else
                exitSummary.status = 'Exit with error';
        }
        if (this.activeStartTime > 0 && this.lastActiveTime > 0) {
            exitSummary.totalActiveTime = this.lastActiveTime - this.activeStartTime;
        }
        try {
            exitSummary.nodeInfo = (0, Self_1.getPublicNodeInfo)();
        }
        catch (er) { }
        try {
            exitSummary.nodeInfo = (0, Self_1.getPublicNodeInfo)();
        }
        catch (er) { }
        try {
            let filePath = path_1.default.join(this.logDir, 'exit-summary.json');
            let content = JSON.stringify(exitSummary, null, 2);
            fs_1.default.writeFileSync(filePath, content, { encoding: 'utf8', flag: 'w' });
        }
        catch (er) { }
    }
    writeStartSummary() {
        //need to figure out what else to write?
        //todo, add support for appData
        let startSummary = {
            startTime: Date.now(),
        };
        try {
            let filePath = path_1.default.join(this.logDir, 'start-summary.json');
            let content = JSON.stringify(startSummary, null, 2);
            fs_1.default.writeFileSync(filePath, content, { encoding: 'utf8', flag: 'w' });
        }
        catch (er) { }
    }
    // Used for adding event listeners for the SIGINT and SIGTERM signals
    addSigListeners(sigint = true, sigterm = true) {
        if (sigint) {
            process.on('SIGINT', async () => {
                // await this.exitCleanly()
                await this.exitUncleanly('SIGINT', 'Process exited with SIGINT');
            });
            //gracefull shutdown suppport in windows. should mirror what SIGINT does in linux
            process.on('message', async (msg) => {
                if (msg == 'shutdown') {
                    await this.exitUncleanly('message:shutdown', 'Process exited with shutdown message');
                }
            });
        }
        if (sigterm) {
            process.on('SIGTERM', async () => {
                // await this.exitCleanly()
                await this.exitUncleanly('SIGTERM', 'Process exited with SIGTERM');
            });
        }
    }
}
exports.default = ExitHandler;
//# sourceMappingURL=index.js.map