import * as i0 from '@angular/core';
import { ViewEncapsulation, Component } from '@angular/core';
import { TitleComponent, ActionBarItemComponent, IconDirective, C8yTranslatePipe } from '@c8y/ngx-components';
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import * as i1 from '@c8y/ngx-components/remote-access/data';
import * as i2 from '@angular/router';
import '@xterm/xterm/css/xterm.css';
import { NgIf } from '@angular/common';

class TelnetNegotiator {
    constructor(terminal, termType) {
        this.terminal = terminal;
        this.termType = termType;
    }
    setTermType(type) {
        this.termType = type;
    }
    pushStr(str, arr) {
        for (let i = 0; i < str.length; i++) {
            arr.push(str.charCodeAt(i));
        }
    }
    negotiate(data) {
        const arrUint8 = new Uint8Array(data);
        const arr = [...arrUint8.values()];
        const sendQueue = [];
        let stringToDisplay = '';
        let chr;
        let code;
        let value;
        while (arr.length > 0) {
            chr = arr.shift();
            switch (chr) {
                case 255: // IAC
                    code = arr.shift();
                    value = arr.shift();
                    switch (code) {
                        case 253: // DO
                            if (value === 24) {
                                // Terminal type
                                sendQueue.push(255, 251, value);
                            }
                            else if (value === 31) {
                                // NAWS (Negotiate About Window Size)
                                // client answers WILL NAWS
                                sendQueue.push(255, 251, 31);
                                // client sends IAC SB NAWS 0 terminalSize.cols 0 terminalSize.rows IAC SE
                                sendQueue.push(255, 250, 31, 0, this.terminal.cols, 0, this.terminal.rows, 255, 240);
                            }
                            else {
                                // Refuse other DO requests with a WONT
                                sendQueue.push(255, 252, value);
                            }
                            break;
                        case 251: // WILL
                            if (value === 1) {
                                // Affirm echo with DO
                                sendQueue.push(255, 253, value);
                            }
                            else {
                                // Reject other WILL offers with a DONT
                                sendQueue.push(255, 254, value);
                            }
                            break;
                        case 250: // SB (subnegotiation)
                            if (value === 24) {
                                // TERM-TYPE subnegotiation
                                if (arr[0] === 1 && arr[1] === 255 && arr[2] === 240) {
                                    arr.shift();
                                    arr.shift();
                                    arr.shift();
                                    sendQueue.push(255, 250, 24, 0);
                                    this.pushStr(this.termType, sendQueue);
                                    sendQueue.push(255, 240);
                                }
                                else {
                                    console.warn('Invalid subnegotiation received' + arr);
                                }
                            }
                            else {
                                console.warn('Ignoring SB ' + value);
                            }
                            break;
                        case 254: // DONT
                        case 252: // WONT
                        default:
                            break;
                    }
                    break;
                case 242: // Data Mark (Synch)
                    code = arr.shift();
                    value = arr.shift();
                    break;
                default: // everything else
                    stringToDisplay += String.fromCharCode(chr);
                    break;
            }
        }
        return {
            dataToSend: sendQueue.length ? new Uint8Array(sendQueue) : '',
            isOutput: !!stringToDisplay.length
        };
    }
}

class ShellAdapter {
    constructor(terminal, termType = 'xterm-256color') {
        this.terminal = terminal;
        this.termType = termType;
    }
    filterNonPrintable(str) {
        // get rid of �
        const helpArr = str.split('�');
        str = helpArr.join('');
        return str;
    }
    setTermType(type) {
        this.termType = type;
    }
    filterReceiveData(data) {
        // negotiate telnet signals, if any
        const telnet = new TelnetNegotiator(this.terminal, this.termType);
        const telnetData = telnet.negotiate(data);
        // decode text for display
        const textDecoder = new TextDecoder();
        const decodedToDisplay = textDecoder.decode(data);
        return {
            dataToSend: telnetData.dataToSend,
            dataToDisplay: telnetData.isOutput ? this.filterNonPrintable(decodedToDisplay) : ''
        };
    }
    filterSendData(data) {
        const sendQueue = [];
        for (let i = 0; i < data.length; i++) {
            sendQueue.push(data.charCodeAt(i));
        }
        return sendQueue.length ? new Uint8Array(sendQueue) : '';
    }
}

class TerminalViewerComponent {
    constructor(remoteAccess, activatedRoute) {
        this.remoteAccess = remoteAccess;
        this.activatedRoute = activatedRoute;
        this.title = '';
        this.container = null;
        this.terminal = null;
        this.socket = null;
        this.firstDeviceMessageReceived = false;
        this.configurationId = this.activatedRoute.snapshot.params.configurationId;
        this.deviceId = this.activatedRoute.parent.snapshot.params.id;
    }
    ngOnDestroy() {
        this.observer?.disconnect();
        const stringToSend = 'exit\n';
        const sendQueue = [];
        for (let i = 0; i < stringToSend.length; i++) {
            sendQueue.push(stringToSend.charCodeAt(i));
        }
        this.socket?.send(new Uint8Array(sendQueue));
        this.socket?.close();
    }
    ngAfterViewInit() {
        this.container = document.getElementById('terminal-screen');
        this.terminal = new Terminal({
            fontSize: 18,
            fontFamily: 'consolas, monospace',
            cursorBlink: true
        });
        const fitAddon = new FitAddon();
        this.socket = new WebSocket(this.remoteAccess.getWebSocketUri(this.deviceId, this.configurationId), 'binary');
        this.socket.binaryType = 'arraybuffer';
        this.terminal.loadAddon(fitAddon);
        this.terminal.open(this.container);
        fitAddon.fit();
        const shellAdapter = new ShellAdapter(this.terminal);
        this.observer = new ResizeObserver(() => {
            fitAddon.fit();
        });
        this.observer.observe(this.container);
        this.terminal.writeln('\u001b[92mEstablishing Websocket connection... \u001b[39m \r\n');
        this.terminal.focus();
        this.socket.onopen = () => {
            this.terminal.writeln('\u001b[92mWebsocket connection was established successfully, waiting for device... \u001b[39m \r\n');
        };
        this.socket.onclose = () => {
            this.terminal.writeln('');
            this.terminal.writeln('\r\n\u001b[91mDevice disconnected. \u001b[39m \r\n');
            this.terminal.writeln('\r\n\u001b[91mWebsocket connection was closed. \u001b[39m \r\n');
        };
        this.socket.onmessage = message => {
            if (!this.firstDeviceMessageReceived) {
                this.firstDeviceMessageReceived = true;
                this.terminal.writeln('\u001b[92mDevice connection was established successfully. \u001b[39m \r\n');
            }
            const filteredData = shellAdapter.filterReceiveData(message.data);
            if (filteredData.dataToSend.length) {
                this.socket.send(filteredData.dataToSend);
            }
            if (filteredData.dataToDisplay.length) {
                this.terminal.write(filteredData.dataToDisplay);
            }
        };
        this.terminal.onData(data => {
            if (this.firstDeviceMessageReceived) {
                const encodedToSend = shellAdapter.filterSendData(data);
                if (encodedToSend.length) {
                    this.socket.send(encodedToSend);
                }
            }
        });
    }
    toggleFullscreen() {
        if (document.fullscreenElement) {
            document.exitFullscreen();
        }
        else {
            this.container.requestFullscreen();
        }
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: TerminalViewerComponent, deps: [{ token: i1.RemoteAccessService }, { token: i2.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.25", type: TerminalViewerComponent, isStandalone: true, selector: "c8y-terminal-viewer", ngImport: i0, template: "<c8y-title>Terminal Viewer: {{ title | translate }}</c8y-title>\n\n<c8y-action-bar-item [placement]=\"'right'\" *ngIf=\"firstDeviceMessageReceived\">\n    <button\n      class=\"btn btn-link\"\n      (click)=\"toggleFullscreen()\"\n    >\n      <i [c8yIcon]=\"'expand'\"></i>\n      <span translate>Fullscreen</span>\n    </button>\n  </c8y-action-bar-item>\n\n<div class=\"content-fullpage\" >\n    <div id=\"terminal-screen\" class=\"inner-scroll\"></div>\n</div>", dependencies: [{ kind: "component", type: TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "component", type: ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: TerminalViewerComponent, decorators: [{
            type: Component,
            args: [{ selector: 'c8y-terminal-viewer', encapsulation: ViewEncapsulation.None, standalone: true, imports: [TitleComponent, C8yTranslatePipe, ActionBarItemComponent, IconDirective, NgIf], template: "<c8y-title>Terminal Viewer: {{ title | translate }}</c8y-title>\n\n<c8y-action-bar-item [placement]=\"'right'\" *ngIf=\"firstDeviceMessageReceived\">\n    <button\n      class=\"btn btn-link\"\n      (click)=\"toggleFullscreen()\"\n    >\n      <i [c8yIcon]=\"'expand'\"></i>\n      <span translate>Fullscreen</span>\n    </button>\n  </c8y-action-bar-item>\n\n<div class=\"content-fullpage\" >\n    <div id=\"terminal-screen\" class=\"inner-scroll\"></div>\n</div>" }]
        }], ctorParameters: () => [{ type: i1.RemoteAccessService }, { type: i2.ActivatedRoute }] });

/**
 * Generated bundle index. Do not edit.
 */

export { TerminalViewerComponent };
//# sourceMappingURL=c8y-ngx-components-remote-access-terminal-viewer.mjs.map
