"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.startEvents = startEvents;
exports.startServerEvents = startServerEvents;
exports.wrapEmitter = wrapEmitter;
const debug_1 = require("./debug");
function startEvents(app, spark, onEvent, eventsFromQuery = '') {
    if (!app.securityStrategy.isDummy() &&
        !app.securityStrategy.hasAdminAccess?.(spark.request)) {
        return;
    }
    const events = eventsFromQuery.split(',');
    events.forEach((event) => {
        const listener = (data) => onEvent({ event, data });
        app.on(event, listener);
        spark.onDisconnects.push(() => app.removeListener(event, listener));
    });
}
function startServerEvents(app, spark, onServerEvent) {
    app.on('serverevent', onServerEvent);
    spark.onDisconnects.push(() => {
        app.removeListener('serverevent', onServerEvent);
    });
    if (app.securityStrategy.hasAdminAccess?.(spark.request)) {
        app.on('serverAdminEvent', onServerEvent);
        spark.onDisconnects.push(() => {
            app.removeListener('serverAdminEvent', onServerEvent);
        });
    }
    try {
        spark.write({
            type: 'VESSEL_INFO',
            data: {
                name: app.config.vesselName,
                mmsi: app.config.vesselMMSI,
                uuid: app.config.vesselUUID
            }
        });
    }
    catch (e) {
        if (e.code !== 'ENOENT') {
            console.error(e);
        }
    }
    Object.keys(app.lastServerEvents).forEach((propName) => {
        spark.write(app.lastServerEvents[propName]);
    });
    spark.write({
        type: 'DEBUG_SETTINGS',
        data: app.logging.getDebugSettings()
    });
    if (app.securityStrategy.canAuthorizeWS()) {
        spark.write({
            type: 'RECEIVE_LOGIN_STATUS',
            data: app.securityStrategy.getLoginStatus(spark.request)
        });
    }
    spark.write({
        type: 'SOURCEPRIORITIES',
        data: app.config.settings.sourcePriorities || {}
    });
}
function safeApply(handler, context, args) {
    try {
        Reflect.apply(handler, context, args);
    }
    catch (err) {
        // Throw error after timeout so as not to interrupt the stack
        setTimeout(() => {
            throw err;
        });
    }
}
function arrayClone(arr) {
    const n = arr.length;
    const copy = new Array(n);
    for (let i = 0; i < n; i += 1) {
        copy[i] = arr[i];
    }
    return copy;
}
function wrapEmitter(targetEmitter) {
    const targetAddListener = targetEmitter.addListener.bind(targetEmitter);
    const targetRemoveListener = targetEmitter.removeListener.bind(targetEmitter);
    const listenersByActorId = {};
    const eventDebugs = {};
    const eventsData = {};
    let emittedCount = 0;
    function safeEmit(eventName, ...args) {
        if (eventName !== 'serverlog') {
            let eventDebug = eventDebugs[eventName];
            if (!eventDebug) {
                eventDebugs[eventName] = eventDebug = (0, debug_1.createDebug)(`signalk-server:events:${eventName}`);
            }
            if (eventDebug.enabled) {
                //there is ever only one rest argument, outputting args results in a 1 element array
                eventDebug(args[0]);
            }
        }
        // from https://github.com/MetaMask/safe-event-emitter/blob/main/index.t
        let doError = eventName === 'error';
        const events = targetEmitter._events;
        if (events !== undefined) {
            doError = doError && events.error === undefined;
        }
        else if (!doError) {
            return false;
        }
        // If there is no 'error' event listener then throw.
        if (doError) {
            let er;
            if (args.length > 0) {
                ;
                [er] = args;
            }
            if (er instanceof Error) {
                // Note: The comments on the `throw` lines are intentional, they show
                // up in Node's output if this results in an unhandled exception.
                throw er; // Unhandled 'error' event
            }
            // At least give some kind of context to the user
            const err = new Error(`Unhandled error.${er ? ` (${er.message})` : ''}`);
            err.context = er;
            throw err; // Unhandled 'error' event
        }
        const handler = events[eventName];
        if (handler === undefined) {
            return false;
        }
        emittedCount++;
        if (typeof handler === 'function') {
            safeApply(handler, this, args);
        }
        else {
            const len = handler.length;
            const listeners = arrayClone(handler);
            for (let i = 0; i < len; i += 1) {
                safeApply(listeners[i], this, args);
            }
        }
        return true;
    }
    function emitWithEmitterId(emitterId, eventName, ...args) {
        const emittersForEvent = (eventsData[eventName] ??
            (eventsData[eventName] = { emitters: {}, listeners: {} })).emitters;
        if (!emittersForEvent[emitterId]) {
            emittersForEvent[emitterId] = 0;
        }
        emittersForEvent[emitterId]++;
        safeEmit(`${emitterId}:${eventName}`, ...args);
        return safeEmit(eventName, ...args);
    }
    const addListenerWithId = function (listenerId, eventName, listener) {
        const listenersForEvent = (eventsData[eventName] ??
            (eventsData[eventName] = { emitters: {}, listeners: {} })).listeners;
        if (!listenersForEvent[listenerId]) {
            listenersForEvent[listenerId] = true;
        }
        if (!listenersByActorId[listenerId]) {
            listenersByActorId[listenerId] = [];
        }
        listenersByActorId[listenerId].push({ eventName, listener });
        return targetAddListener(eventName, listener);
    };
    function removeAllListenersById(actorId) {
        const tracked = listenersByActorId[actorId];
        if (tracked) {
            tracked.forEach(({ eventName, listener }) => {
                targetRemoveListener(eventName, listener);
            });
            delete listenersByActorId[actorId];
        }
    }
    return {
        getEmittedCount: () => emittedCount,
        getEventRoutingData: () => ({
            events: Object.entries(eventsData).map(([event, data]) => ({
                event,
                ...data
            }))
        }),
        emit: function (eventName, ...args) {
            return emitWithEmitterId('NO_EMITTER_ID', eventName, ...args);
        },
        addListener: (eventName, listener) => addListenerWithId('NO_LISTENER_ID', eventName, listener),
        bindMethodsById: (actorId) => {
            const addListener = (eventName, listener) => addListenerWithId(actorId, eventName, listener);
            return {
                emit: function (eventName, ...args) {
                    return emitWithEmitterId(actorId, eventName, ...args);
                },
                addListener,
                on: addListener
            };
        },
        removeAllListenersById
    };
}
//# sourceMappingURL=events.js.map