import { Terminal, IEvent, IDisposable } from "xterm";

import base64 from './base64';

interface IRectSize {
    width: number;
    height: number;
}

class TTYReceiver {
    private xterminal: Terminal;
    private containerElement: HTMLElement;

    constructor(wsAddress: string, container: HTMLDivElement) {
        const connection = new WebSocket(wsAddress);

        this.xterminal = new Terminal({
            cursorBlink: true,
            macOptionIsMeta: true,
            scrollback: 0,
            fontSize: 12,
            letterSpacing: 0,
            fontFamily: 'SauceCodePro, courier-new, monospace'
        });

        this.containerElement = container;
        this.xterminal.open(container);

        connection.onclose =  (evt: CloseEvent) => {

           this.xterminal.blur();
           this.xterminal.setOption('cursorBlink', false);
           this.xterminal.clear();
           this.xterminal.write('Session closed');
        }

        this.xterminal.focus();

        const containerPixSize = this.getElementPixelsSize(container);
        const newFontSize = this.guessNewFontSize(this.xterminal.cols, this.xterminal.rows, containerPixSize.width, containerPixSize.height);
        this.xterminal.setOption('fontSize', newFontSize);

        connection.onmessage = (ev: MessageEvent) => {
            let message = JSON.parse(ev.data)
            let msgData = base64.decode(message.Data)

            if (message.Type === "Write") {
                let writeMsg = JSON.parse(msgData)
                this.xterminal.writeUtf8(base64.base64ToArrayBuffer(writeMsg.Data));
            }

            if (message.Type == "WinSize") {
                let winSizeMsg = JSON.parse(msgData)

                const containerPixSize = this.getElementPixelsSize(container);
                const newFontSize = this.guessNewFontSize(winSizeMsg.Cols, winSizeMsg.Rows, containerPixSize.width, containerPixSize.height);
                this.xterminal.setOption('fontSize', newFontSize);

                // Now set the new size.
                this.xterminal.resize(winSizeMsg.Cols, winSizeMsg.Rows)
            }
        }

        this.xterminal.onData(function (data:string) {
            let writeMessage = {
                Type: "Write",
                Data: base64.encode(JSON.stringify({ Size: data.length, Data: base64.encode(data)})),
            }
            let dataToSend = JSON.stringify(writeMessage)
            connection.send(dataToSend);
        });

    }

    // Get the pixels size of the element, after all CSS was applied. This will be used in an ugly
    // hack to guess what fontSize to set on the xterm object. Horrible hack, but I feel less bad
    // about it seeing that VSV does it too:
    // https://github.com/microsoft/vscode/blob/d14ee7613fcead91c5c3c2bddbf288c0462be876/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts#L363
    private getElementPixelsSize(element: HTMLElement): IRectSize {
        const defView = this.containerElement.ownerDocument.defaultView;
        let width = parseInt(defView.getComputedStyle(element).getPropertyValue('width').replace('px', ''), 10);
        let height = parseInt(defView.getComputedStyle(element).getPropertyValue('height').replace('px', ''), 10);

        return {
            width,
            height,
        }
    }

    // Tries to guess the new font size, for the new terminal size, so that the rendered terminal
    // will have the newWidth and newHeight dimensions
    private guessNewFontSize(newCols: number, newRows: number, targetWidth: number, targetHeight: number): number {
        const cols = this.xterminal.cols;
        const rows = this.xterminal.rows;
        const fontSize = this.xterminal.getOption('fontSize');
        const xtermPixelsSize = this.getElementPixelsSize(this.containerElement.querySelector(".xterm-screen"));

        const newHFontSizeMultiplier =  (cols / newCols) * (targetWidth / xtermPixelsSize.width);
        const newVFontSizeMultiplier = (rows / newRows) * (targetHeight / xtermPixelsSize.height);

        let newFontSize;

        if (newHFontSizeMultiplier > newVFontSizeMultiplier) {
            newFontSize = Math.floor(fontSize * newVFontSizeMultiplier);
        } else {
            newFontSize = Math.floor(fontSize * newHFontSizeMultiplier);
        }
        return newFontSize;
    }
}

export {
    TTYReceiver
}
