"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BackpressureManager = void 0;
exports.parseBackpressureThresholds = parseBackpressureThresholds;
const LatestValuesAccumulator_1 = require("./LatestValuesAccumulator");
const debug_1 = require("./debug");
const debug = (0, debug_1.createDebug)('signalk-server:backpressure');
const DEFAULT_ENTER_THRESHOLD = 512 * 1024;
const DEFAULT_EXIT_THRESHOLD = 1024;
const DEFAULT_MAX_BUFFER_SIZE = 4 * 512 * 1024;
const DEFAULT_MAX_BUFFER_CHECK_TIME = 30 * 1000;
function parseBackpressureThresholds(configFallbacks) {
    return {
        enterThreshold: process.env.BACKPRESSURE_ENTER
            ? parseInt(process.env.BACKPRESSURE_ENTER, 10)
            : DEFAULT_ENTER_THRESHOLD,
        exitThreshold: process.env.BACKPRESSURE_EXIT
            ? parseInt(process.env.BACKPRESSURE_EXIT, 10)
            : DEFAULT_EXIT_THRESHOLD,
        maxBufferSize: process.env.MAXSENDBUFFERSIZE
            ? parseInt(process.env.MAXSENDBUFFERSIZE, 10)
            : (configFallbacks?.maxSendBufferSize ?? DEFAULT_MAX_BUFFER_SIZE),
        maxBufferCheckTime: process.env.MAXSENDBUFFERCHECKTIME
            ? parseInt(process.env.MAXSENDBUFFERCHECKTIME, 10)
            : (configFallbacks?.maxSendBufferCheckTime ??
                DEFAULT_MAX_BUFFER_CHECK_TIME)
    };
}
class BackpressureManager {
    active = false;
    accumulator = new Map();
    since = null;
    bufferSizeExceeded = undefined;
    transport;
    options;
    constructor(transport, options) {
        this.transport = transport;
        this.options = options;
    }
    onDrain() {
        if (this.active && this.accumulator.size > 0) {
            if (this.transport.getBufferLength() <= this.options.exitThreshold) {
                this.flush();
            }
        }
    }
    send(delta) {
        const bufferLength = this.transport.getBufferLength();
        if (bufferLength > this.options.enterThreshold) {
            if (!this.active) {
                this.active = true;
                this.since = Date.now();
                debug('Entering backpressure for %s (buffer: %d)', this.transport.id, bufferLength);
            }
            (0, LatestValuesAccumulator_1.accumulateLatestValue)(this.accumulator, delta);
        }
        else {
            this.options.beforeWrite?.(delta);
            this.transport.write(delta);
        }
        this.assertBufferSize(bufferLength);
    }
    flush() {
        if (this.accumulator.size === 0)
            return;
        const countBefore = this.accumulator.size;
        const duration = this.since ? Date.now() - this.since : 0;
        const deltas = (0, LatestValuesAccumulator_1.buildFlushDeltas)(this.accumulator, duration);
        for (const delta of deltas) {
            this.options.beforeWrite?.(delta);
            this.transport.write(delta);
        }
        this.accumulator.clear();
        this.active = false;
        this.since = null;
        debug('Flushed %d accumulated values for %s', countBefore, this.transport.id);
    }
    assertBufferSize(knownBufferLength) {
        if (this.options.maxBufferSize === 0)
            return;
        const bufferLength = knownBufferLength ?? this.transport.getBufferLength();
        if (bufferLength > this.options.maxBufferSize) {
            if (!this.bufferSizeExceeded) {
                console.warn(`${this.transport.id} outgoing buffer > max:${bufferLength}`);
                this.bufferSizeExceeded = Date.now();
            }
            if (Date.now() - this.bufferSizeExceeded >
                this.options.maxBufferCheckTime) {
                console.error('Send buffer overflow, terminating connection ' + this.transport.id);
                this.transport.destroy();
            }
        }
        else {
            this.bufferSizeExceeded = undefined;
        }
    }
    clear() {
        this.accumulator.clear();
        this.active = false;
        this.since = null;
        this.bufferSizeExceeded = undefined;
    }
    get isActive() {
        return this.active;
    }
    get accumulatorSize() {
        return this.accumulator.size;
    }
}
exports.BackpressureManager = BackpressureManager;
//# sourceMappingURL=BackpressureManager.js.map