"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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.nestedCountersInstance = void 0;
const profiler_1 = require("./profiler");
const Context = __importStar(require("../p2p/Context"));
const utils = __importStar(require("../utils"));
const debugMiddleware_1 = require("../network/debugMiddleware");
const network_1 = require("../network");
class NestedCounters {
    constructor() {
        this.eventCounters = new Map();
        this.rareEventCounters = new Map();
        this.crypto = null;
        this.infLoopDebug = false;
    }
    registerEndpoints() {
        Context.network.registerExternalGet('counts', debugMiddleware_1.isDebugModeMiddlewareLow, (req, res) => {
            profiler_1.profilerInstance.scopedProfileSectionStart('counts');
            const arrayReport = this.arrayitizeAndSort(this.eventCounters);
            if (req.headers.accept === 'application/json') {
                // Send JSON response
                res.setHeader('Content-Type', 'application/json');
                res.json({
                    timestamp: (0, network_1.shardusGetTime)(),
                    report: arrayReport,
                });
            }
            else {
                // TODO: This doesn't return the counts to the caller
                res.write(`Counts at time: ${(0, network_1.shardusGetTime)()} offset: ${(0, network_1.getNetworkTimeOffset)()}\n`);
                this.printArrayReport(arrayReport, res, 0);
                res.end();
            }
            profiler_1.profilerInstance.scopedProfileSectionEnd('counts');
        });
        Context.network.registerExternalGet('counts-reset', debugMiddleware_1.isDebugModeMiddlewareLow, (req, res) => {
            this.eventCounters = new Map();
            res.write(`counts reset ${(0, network_1.shardusGetTime)()} offset: ${(0, network_1.getNetworkTimeOffset)()}`);
            res.end();
        });
        Context.network.registerExternalGet('rare-counts-reset', debugMiddleware_1.isDebugModeMiddleware, (req, res) => {
            this.rareEventCounters = new Map();
            res.write(`Rare counts reset ${(0, network_1.shardusGetTime)()} offset: ${(0, network_1.getNetworkTimeOffset)()}`);
            res.end();
        });
        Context.network.registerExternalGet('debug-inf-loop', debugMiddleware_1.isDebugModeMiddleware, (req, res) => {
            res.write('starting inf loop, goodbye');
            res.end();
            this.infLoopDebug = true;
            while (this.infLoopDebug) {
                const s = 'asdf';
                const s2 = utils.stringifyReduce({ test: [s, s, s, s, s, s, s] });
                const s3 = utils.stringifyReduce({ test: [s2, s2, s2, s2, s2, s2, s2] });
                if (this.crypto != null) {
                    this.crypto.hash(s3);
                }
            }
        });
        Context.network.registerExternalGet('debug-inf-loop-off', debugMiddleware_1.isDebugModeMiddleware, (req, res) => {
            this.infLoopDebug = false;
            res.write('stopping inf loop, who knows if this is possible');
            res.end();
        });
    }
    /**
     * Increments event counter map for a specified category and sub category
     * @param category1 Primary category to be updated
     * @param category2 Sub counter category
     * @param count Amount to increment primary and sub counter by. Defaults to 1
     */
    countEvent(category1, category2, count = 1) {
        let counterMap = this.eventCounters;
        let nextNode = null;
        if (counterMap.has(category1) === false) {
            nextNode = { count: 0, subCounters: new Map() };
            counterMap.set(category1, nextNode);
        }
        else {
            nextNode = counterMap.get(category1);
        }
        nextNode.count += count;
        counterMap = nextNode.subCounters;
        //unrolled loop to avoid memory alloc
        category1 = category2;
        if (counterMap.has(category1) === false) {
            nextNode = { count: 0, subCounters: new Map() };
            counterMap.set(category1, nextNode);
        }
        else {
            nextNode = counterMap.get(category1);
        }
        nextNode.count += count;
        counterMap = nextNode.subCounters;
    }
    /**
     * Increments event counter map and rare event counter map for a specified category and sub category
     * @param category1 Primary category to be updated
     * @param category2 Sub counter category
     * @param count Amount to increment primary and sub counter by. Defaults to 1
     */
    countRareEvent(category1, category2, count = 1) {
        // trigger normal event counter
        this.countEvent(category1, category2, count);
        // start counting rare event
        let counterMap = this.rareEventCounters;
        let nextNode = null;
        if (counterMap.has(category1) === false) {
            nextNode = { count: 0, subCounters: new Map() };
            counterMap.set(category1, nextNode);
        }
        else {
            nextNode = counterMap.get(category1);
        }
        nextNode.count += count;
        counterMap = nextNode.subCounters;
        //unrolled loop to avoid memory alloc
        category1 = category2;
        if (counterMap.has(category1) === false) {
            nextNode = { count: 0, subCounters: new Map() };
            counterMap.set(category1, nextNode);
        }
        else {
            nextNode = counterMap.get(category1);
        }
        nextNode.count += count;
        counterMap = nextNode.subCounters;
    }
    /**
     * Recursively convert the counterMap to an array and sort by the count property
     * @param counterMap
     * @returns sorted array of counts
     */
    arrayitizeAndSort(counterMap) {
        const array = [];
        for (const key of counterMap.keys()) {
            const valueObj = counterMap.get(key);
            const newValueObj = { key, count: valueObj.count, subArray: null };
            array.push(newValueObj);
            let subArray = [];
            if (valueObj.subCounters != null) {
                subArray = this.arrayitizeAndSort(valueObj.subCounters);
            }
            newValueObj.subArray = subArray;
        }
        array.sort((a, b) => b.count - a.count);
        return array;
    }
    /**
     * Generates a formatted response and recursively prints it to the response stream
     * @param arrayReport
     * @param stream
     * @param indent
     */
    printArrayReport(arrayReport, stream, indent = 0) {
        const indentText = '___'.repeat(indent);
        for (const item of arrayReport) {
            const { key, count, subArray } = item;
            const countStr = `${count}`;
            stream.write(`${countStr.padStart(10)} ${indentText} ${key}\n`);
            if (subArray != null && subArray.length > 0) {
                this.printArrayReport(subArray, stream, indent + 1);
            }
        }
    }
    resetCounters() {
        this.eventCounters = new Map();
    }
    resetRareCounters() {
        this.rareEventCounters = new Map();
    }
}
exports.nestedCountersInstance = new NestedCounters();
exports.default = NestedCounters;
//# sourceMappingURL=nestedCounters.js.map