"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const zwave_js_1 = require("zwave-js");
const EventEmitter_1 = require("./EventEmitter");
const logger_1 = require("./logger");
const SocketEvents_1 = require("./SocketEvents");
const app_1 = require("../config/app");
const utils_1 = require("./utils");
const shared_1 = require("@zwave-js/shared");
const path_1 = require("path");
// eslint-disable-next-line @typescript-eslint/no-var-requires
const loglevels = require('triple-beam').configs.npm.levels;
const logger = (0, logger_1.module)('ZnifferManager');
const ZNIFFER_LOG_FILE = (0, utils_1.joinPath)(app_1.logsDir, 'zniffer_%DATE%.log');
const ZNIFFER_CAPTURE_FILE = (0, utils_1.joinPath)(app_1.storeDir, 'zniffer_capture_%DATE%.zlf');
class ZnifferManager extends EventEmitter_1.TypedEventEmitter {
    zniffer;
    config;
    socket;
    error;
    restartTimeout;
    get started() {
        return !!this.zniffer?.active;
    }
    constructor(config, socket) {
        super();
        this.config = config;
        this.socket = socket;
        if (!config.enabled) {
            logger.info('Zniffer is DISABLED');
            return;
        }
        const znifferOptions = {
            convertRSSI: config.convertRSSI,
            defaultFrequency: config.defaultFrequency,
            logConfig: {
                enabled: config.logEnabled,
                level: config.logLevel ? loglevels[config.logLevel] : 'info',
                logToFile: config.logToFile,
                filename: ZNIFFER_LOG_FILE,
                forceConsole: (0, shared_1.isDocker)() ? !config.logToFile : false,
                maxFiles: config.maxFiles || 7,
                nodeFilter: config.nodeFilter && config.nodeFilter.length > 0
                    ? config.nodeFilter.map((n) => parseInt(n))
                    : undefined,
            },
        };
        (0, utils_1.parseSecurityKeys)(config, znifferOptions);
        this.zniffer = new zwave_js_1.Zniffer(config.port, znifferOptions);
        logger.info('Initing Zniffer...');
        this.init().catch(() => { });
    }
    async init() {
        try {
            await this.zniffer.init();
            this.zniffer.on('frame', (frame, rawData) => {
                const socketFrame = this.parseFrame(frame, rawData);
                this.socket.emit(SocketEvents_1.socketEvents.znifferFrame, socketFrame);
            });
            this.zniffer.on('corrupted frame', (frame, rawData) => {
                const socketFrame = this.parseFrame(frame, rawData);
                this.socket.emit(SocketEvents_1.socketEvents.znifferFrame, socketFrame);
            });
            this.zniffer.on('error', (error) => {
                this.onError(error);
            });
            this.zniffer.on('ready', () => {
                logger.info('Zniffer ready');
            });
        }
        catch (error) {
            this.onError(error);
            logger.info('Retrying in 5s...');
            await this.close();
            this.restartTimeout = setTimeout(() => {
                this.init().catch(() => { });
            }, 5000);
        }
        this.onStateChange();
    }
    parseFrame(frame, rawData, timestamp = Date.now()) {
        const socketFrame = {
            ...frame,
            corrupted: !('protocol' in frame),
            payload: '',
            timestamp,
            raw: (0, utils_1.buffer2hex)(rawData),
        };
        if ('payload' in frame) {
            if (frame.payload instanceof zwave_js_1.CommandClass) {
                socketFrame.parsedPayload = this.ccToLogRecord(frame.payload);
            }
            else {
                socketFrame.payload = (0, utils_1.buffer2hex)(frame.payload);
            }
        }
        return socketFrame;
    }
    onError(error) {
        logger.error('Zniffer error:', error);
        this.error = error.message;
        this.onStateChange();
    }
    onStateChange() {
        this.socket.emit(SocketEvents_1.socketEvents.znifferState, this.status());
    }
    checkReady() {
        if (!this.config.enabled || !this.zniffer) {
            throw new Error('Zniffer is not initialized');
        }
    }
    status() {
        return {
            error: this.error,
            started: this.started,
            frequency: this.zniffer?.currentFrequency,
        };
    }
    getFrames() {
        this.checkReady();
        return this.zniffer.capturedFrames.map((frame) => {
            return this.parseFrame(frame.parsedFrame, frame.frameData, frame.timestamp.getTime());
        });
    }
    async setFrequency(frequency) {
        this.checkReady();
        logger.info(`Setting Zniffer frequency to ${frequency}`);
        await this.zniffer.setFrequency(frequency);
        this.onStateChange();
        logger.info(`Zniffer frequency set to ${frequency}`);
    }
    ccToLogRecord(commandClass) {
        try {
            const parsed = commandClass.toLogEntry(this.zniffer);
            if ((0, zwave_js_1.isEncapsulatingCommandClass)(commandClass)) {
                parsed.encapsulated = [
                    this.ccToLogRecord(commandClass.encapsulated),
                ];
            }
            else if ((0, zwave_js_1.isMultiEncapsulatingCommandClass)(commandClass)) {
                parsed.encapsulated = [
                    commandClass.encapsulated.map((cc) => this.ccToLogRecord(cc)),
                ];
            }
            return parsed;
        }
        catch (error) {
            logger.error('Error parsing command class:', error);
            return {
                error: error.message,
            };
        }
    }
    async close() {
        if (this.restartTimeout)
            clearTimeout(this.restartTimeout);
        if (this.zniffer) {
            this.zniffer.removeAllListeners();
            await this.stop();
            await this.zniffer.destroy();
        }
    }
    async start() {
        this.checkReady();
        if (this.started) {
            logger.info('Zniffer already started');
            return;
        }
        logger.info('Starting...');
        await this.zniffer.start();
        this.onStateChange();
        logger.info('Started');
    }
    async stop() {
        this.checkReady();
        if (!this.started) {
            logger.info('Zniffer is already stopped');
            return;
        }
        logger.info('Stopping...');
        await this.zniffer.stop();
        this.onStateChange();
        logger.info('Stopped');
    }
    clear() {
        this.checkReady();
        logger.info('Clearing...');
        this.zniffer.clearCapturedFrames();
        logger.info('Frames cleared');
    }
    async saveCaptureToFile() {
        this.checkReady();
        const filePath = ZNIFFER_CAPTURE_FILE.replace('%DATE%', new Date().toISOString());
        logger.info(`Saving capture to ${filePath}`);
        await this.zniffer.saveCaptureToFile(filePath);
        logger.info('Capture saved');
        return {
            path: filePath,
            name: (0, path_1.basename)(filePath),
        };
    }
}
exports.default = ZnifferManager;
