<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>RDP - %s</title>
    <link rel="icon" href="/favicon.ico" type="image/x-icon">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <script src="/rdpwasm/rdp.wasm.js"></script>
    <script src="/rdpwasm/scancodes.js"></script>
    <script src="/rdpwasm/reversed_layouts.js"></script>
    <script src="/rdpwasm/rdp_graphics.js"></script>
    <script src="/rdpwasm/out_stream.js"></script>
    <script src="/rdpwasm/clipboard.js"></script>
    <style>
        .navbar-brand {
            font-weight: bold;
        }
        .rdp-container {
            background-color: #1e1e1e;
            color: #f8f8f2;
            border-radius: 8px;
            height: calc(100vh - 200px);
            min-height: 400px;
            overflow: hidden;
            position: relative;
        }
        .rdp-container.full-size {
            overflow: auto;
            max-height: none;
            height: auto;
        }
        .rdp-container.full-size #canvas {
            min-width: fit-content;
            min-height: fit-content;
        }
        /* GNOME-like window decorations */
        .rdp-window {
            border: 1px solid #ccc;
            border-radius: 8px;
            overflow: hidden;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
        }
        .window-titlebar {
            background: linear-gradient(to bottom, #f0f0f0, #e0e0e0);
            border-bottom: 1px solid #ccc;
            padding: 4px 8px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            height: 32px;
        }
        .window-title {
            font-weight: bold;
            color: #333;
            font-size: 14px;
        }
        .window-controls {
            display: flex;
            align-items: center;
            gap: 4px;
        }
        .window-btn {
            width: 24px;
            height: 24px;
            border: none;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 14px;
            font-weight: bold;
            cursor: pointer;
            transition: all 0.2s;
        }
        .window-btn:hover {
            transform: scale(1.1);
        }
        .close-btn {
            background: #ff605c;
            color: white;
        }
        .close-btn:hover {
            background: #ff403c;
        }
        .maximize-btn {
            background: #28ca42;
            color: white;
        }
        .maximize-btn:hover {
            background: #24b83a;
        }
        .minimize-btn {
            background: #ffbd44;
            color: white;
            display: none; /* Hidden by default, shown in fullscreen */
        }
        .minimize-btn:hover {
            background: #ffad2c;
        }
        .zoom-btn {
            background: #17a2b8;
            color: white;
        }
        .zoom-btn:hover {
            background: #138496;
        }
        .zoom-btn.active {
            background: #007bff;
        }
        .zoom-btn.active:hover {
            background: #0056b3;
        }
        .window-content {
            background: #1e1e1e;
            padding: 8px;
        }
        /* Fullscreen styles */
        .rdp-window.fullscreen {
            position: fixed;
            top: 0;
            left: 0;
            width: 100vw;
            height: 100vh;
            border: none;
            border-radius: 0;
            z-index: 9999;
        }
        .rdp-window.fullscreen .rdp-container {
            height: calc(100vh - 40px) !important;
            min-height: calc(100vh - 40px) !important;
        }
        .rdp-window.fullscreen .minimize-btn {
            display: flex;
        }
        #canvas {
            width: 100%;
            height: 100%;
        }
        #canvasFocus {
            width: 0px;
            height: 0px;
            position: absolute;
            z-index: -10;
            padding: 0;
            margin: 0;
        }
    </style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">
<i class="fas fa-desktop"></i> WSSSHD control panel</a>
<div class="navbar-nav ms-auto">
<span class="navbar-text me-3">RDP Session - %s</span>
<a class="nav-link" href="/logout">Logout</a>
</div></div></nav>
<div id="notification-area" class="position-fixed top-0 end-0 p-3" style="z-index: 1050;"></div>

<!-- RDP Configuration Modal -->
<div class="modal fade" id="rdpConfigModal" tabindex="-1" aria-labelledby="rdpConfigModalLabel" aria-hidden="true">
  <div class="modal-dialog modal-lg">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="rdpConfigModalLabel">RDP Connection Configuration</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        <form id="rdpConfigForm">
          <div class="row">
            <div class="col-md-6">
              <fieldset class="border p-3 mb-3">
                <legend class="w-auto">Authentication</legend>
                <div class="mb-3">
                  <label for="rdpUsername" class="form-label">Username</label>
                  <input type="text" class="form-control" id="rdpUsername" placeholder="Username">
                </div>
                <div class="mb-3">
                  <label for="rdpPassword" class="form-label">Password</label>
                  <input type="password" class="form-control" id="rdpPassword" placeholder="Password">
                </div>
                <div class="mb-3">
                  <label for="rdpDomain" class="form-label">Domain</label>
                  <input type="text" class="form-control" id="rdpDomain" placeholder="Domain (optional)">
                </div>
              </fieldset>
            </div>
            <div class="col-md-6">
              <fieldset class="border p-3 mb-3">
                <legend class="w-auto">Display</legend>
                <div class="mb-3">
                  <label for="rdpWidth" class="form-label">Width</label>
                  <input type="number" class="form-control" id="rdpWidth" value="1024" min="640" max="4096">
                </div>
                <div class="mb-3">
                  <label for="rdpHeight" class="form-label">Height</label>
                  <input type="number" class="form-control" id="rdpHeight" value="768" min="480" max="2160">
                </div>
                <div class="mb-3">
                  <label for="rdpBpp" class="form-label">Color Depth (BPP)</label>
                  <select class="form-select" id="rdpBpp">
                    <option value="8">8-bit</option>
                    <option value="15">15-bit</option>
                    <option value="16" selected>16-bit</option>
                    <option value="24">24-bit</option>
                    <option value="32">32-bit</option>
                  </select>
                </div>
                <div class="mb-3">
                  <label for="rdpGraphicsImpl" class="form-label">Graphics Implementation</label>
                  <select class="form-select" id="rdpGraphicsImpl">
                    <option value="2d" selected>Canvas 2D</option>
                    <option value="webgl">WebGL</option>
                    <option value="webgl2">WebGL2</option>
                  </select>
                </div>
              </fieldset>
            </div>
          </div>
          <div class="row">
            <div class="col-md-6">
              <fieldset class="border p-3 mb-3">
                <legend class="w-auto">Keyboard</legend>
                <div class="mb-3">
                  <label for="rdpKeyboardMethod" class="form-label">Keyboard Method</label>
                  <select class="form-select" id="rdpKeyboardMethod">
                    <option value="codeToScancode" selected>KeyboardEvent.code → Scancode</option>
                    <option value="emulateScancode">KeyboardEvent.key → Scancode or Unicode</option>
                    <option value="unicode">KeyboardEvent.key → Unicode</option>
                  </select>
                </div>
                <div class="mb-3">
                  <div class="form-check">
                    <input class="form-check-input" type="checkbox" id="rdpAltGrCtrlAlt" checked>
                    <label class="form-check-label" for="rdpAltGrCtrlAlt">
                      Ctrl + Alt = AltGr
                    </label>
                  </div>
                </div>
              </fieldset>
            </div>
            <div class="col-md-6">
              <fieldset class="border p-3 mb-3">
                <legend class="w-auto">Advanced</legend>
                <div class="mb-3">
                  <label for="rdpVerbosity" class="form-label">Verbosity Level</label>
                  <input type="number" class="form-control" id="rdpVerbosity" value="0" min="0">
                </div>
                <div class="mb-3">
                  <label for="rdpJsonConfig" class="form-label">JSON Configuration (Advanced)</label>
                  <textarea class="form-control" id="rdpJsonConfig" rows="3" placeholder='{"key": "value"}'></textarea>
                </div>
              </fieldset>
            </div>
          </div>
        </form>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
        <button type="button" class="btn btn-primary" id="startRdpBtn">Start RDP Session</button>
      </div>
    </div>
  </div>
</div>

<div class="container mt-4">
<div class="row">
    <div class="col-12">
        <div class="rdp-window">
            <div class="window-titlebar">
                <div class="window-title">
                    <i class="fas fa-desktop"></i> RDP Session - %s
                </div>
                <div class="window-controls">
                    <button id="connectBtn" class="btn btn-success btn-sm me-1" style="height: 24px; font-size: 12px; padding: 0 8px;">
                        <i class="fas fa-play"></i> Connect
                    </button>
                    <button id="disconnectBtn" class="btn btn-danger btn-sm me-1" disabled style="height: 24px; font-size: 12px; padding: 0 8px;">
                        <i class="fas fa-stop"></i> Disconnect
                    </button>
                    <button class="window-btn zoom-btn" title="Toggle Zoom (Scale/Fit)" id="zoomBtn">
                        <i class="fas fa-search-plus"></i>
                    </button>
                    <button class="window-btn minimize-btn" title="Exit Fullscreen">_</button>
                    <button class="window-btn maximize-btn" title="Fullscreen"><span class="maximize-icon">□</span></button>
                    <button class="window-btn close-btn" title="Disconnect">×</button>
                </div>
            </div>
            <div class="window-content">
                <div id="rdp" class="rdp-container w-100">
                    <canvas id="canvas" width="1024" height="768"></canvas><input id="canvasFocus" type="text"/>
                    <div id="rdp_status" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; color: #f8f8f2;">Click Connect to start RDP session</div>
                    <div id="rdp_loading" style="display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; color: #f8f8f2;">
                        <div class="spinner-border text-primary mb-2" role="status">
                            <span class="visually-hidden">Loading...</span>
                        </div>
                        <div>Connecting to RDP server...</div>
                        <button id="cancelConnectBtn" class="btn btn-outline-danger btn-sm mt-2" style="display: none;">
                            <i class="fas fa-times"></i> Cancel
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
</div>
<script>
// init kbdlayout_input
let defaultLayoutId;
try {
    if (typeof layouts === 'undefined') {
        throw new Error('layouts is not defined - reversed_layouts.js may not have loaded');
    }
    for (let i = 0; i < layouts.length; ++i) {
        const layout = layouts[i];
        const displayName = layout.displayName;
        const opt = document.createElement('option');
        opt.textContent = layout.localeName + ' 0x' + layout.klid.toString(16).padStart(5, '0') + ' (' + displayName + ')';
        opt.value = i;
        if (displayName === 'United States - English') {
            opt.selected = true;
            defaultLayoutId = i;
        }
        // Since we don't have kbdlayout_input in this simplified version, we'll use default
    }
} catch (error) {
    console.error('Error initializing keyboard layouts:', error);
    defaultLayoutId = 0; // fallback
}

let kbdInputLayoutEvent = () => {};

let _Module = null;

if (typeof WallixModule === 'undefined') {
    console.error('WallixModule is not defined - rdp.wasm.js may not have loaded');
} else {
    WallixModule({
    // INITIAL_MEMORY: 16777216, // 16**6
    // INITIAL_MEMORY: 268435456, // 16**7
}).then((Module) => {
    _Module = Module;

    // optional
    const LogLevel = Module.LogLevel;
    Module.log = function(priority, msg) {
        const logger = (priority === LogLevel.Info) ? console.log
                  : (priority === LogLevel.Warning) ? console.warn
                  : (priority === LogLevel.Error) ? console.error
                  : (priority === LogLevel.Debug) ? (s) => {
                    console.debug("%c%s", 'color:yellow', s)
                  }
                  : console.info;
        logger(msg);
    };

    // Set constants now that module is loaded
    RdpClient = _Module.RdpClient;
    ClipboardChannel = _Module.ClipboardChannel;
    MouseFlags = _Module.MouseFlags;
    InputFlags = _Module.InputFlags;


}).catch((e) => {
    console.error('Failed to load WebAssembly module:', e);
    showNotification('Failed to load RDP WebAssembly module', 'danger');
});
console.log('After WallixModule setup');

// Constants will be set after module loads
let RdpClient, ClipboardChannel, MouseFlags, InputFlags;

// Define constants that will be available after module loads
const KeyAcquire = 0;
const KeyRelease = 0x80;
const SyncFlags = {
    ShiftLeft: 0x01,
    ShiftRight: 0x02,
    ControlLeft: 0x04,
    ControlRight: 0x08,
    AltLeft: 0x10,
    AltRight: 0x20,
    OSLeft: 0x40,
    OSRight: 0x80,
    NumLock: 0x100,
    CapsLock: 0x200,
    KanaLock: 0x400,
    ScrollLock: 0x800
};
console.log('After constants');

function MultiSelectToInt(e, constants)
{
    let flags = 0;
    for (let i = 0; i < e.length; i++) {
        if (e.options[i].selected) {
            flags |= constants[e.options[i].value] || 0;
        }
    }
    return flags;
}

const gdImplTable = {
    '2d':newRdpGraphics2D,
    webgl:newRdpGraphicsGL,
    webgl2:newRdpGraphicsGL2,
};

class Keyboard
{
    // driver = class {
    //  sendScancodes(scancodes:Array[Number], evt:KeyEvent);
    //  syncKbdLocks(syncFlags:Number)
    // }
    constructor(keymap, driver) {
        this._driver = driver;
        this._keymap = keymap;
        this._syncState = 0;
        // on window, when left + right shift are pressed, onkeyup is only triggered for the last one
        this._shiftDown = 0;
    }

    copyInternalStateFrom(other) {
        this._syncState = other._syncState;
        this._shiftDown = other._shiftDown;
        this._keymap = other._keymap;
    }

    activeModsSync() {
        this._syncState = 1;
    }

    keyup(evt) {
        if (!this._syncState && evt.key === 'Shift') {
            this._shiftDown &= ~this._codeToShiftMod(evt.code);

            // shift is released but one of the sides is still considered pressed
            if (this._shiftDown && !evt.getModifierState('Shift')) {
                const scancodes = [];
                if (this._shiftDown & SyncFlags.ShiftRight) {
                    scancodes.push(ScancodeByMod.ShiftRight | KeyRelease);
                }
                if (this._shiftDown & SyncFlags.ShiftLeft) {
                    scancodes.push(ScancodeByMod.ShiftLeft | KeyRelease);
                }
                this._shiftDown = 0;
                this._driver.sendScancodes(scancodes, evt);
                return;
            }
        }
        this._keyEvent(evt, KeyRelease);
    }

    keydown(evt) {
        if (!this._syncState && evt.key === 'Shift') {
            this._shiftDown |= this._codeToShiftMod(evt.code);
        }
        this._keyEvent(evt, KeyAcquire);
    }

    _keyEvent(evt, flag) {
        if (!this._preprocessKeyEvent(evt, flag)) return;

        // numpad
        if (evt.location === KeyboardEvent.DOM_KEY_LOCATION_NUMPAD) {
            const scancode = numpadCodeToScancode(evt.code);
            this._driver.sendScancodes([scancode | flag], evt);
            return;
        }

        const scancodes = codeToScancodes(evt.key, flag) || codeToScancodes(evt.code, flag);
        if (scancodes) {
            this._driver.sendScancodes(scancodes, evt);
        }
    }

    /// \return false when key processing should stop
    _preprocessKeyEvent(evt, flag) {
        if (!this._syncState) return true;

        let syncFlags = 0;

        // locks
        if (evt.getModifierState("NumLock")) syncFlags |= SyncFlags.NumLock;
        if (evt.getModifierState("CapsLock")) syncFlags |= SyncFlags.CapsLock;
        if (evt.getModifierState("KanaLock")) syncFlags |= SyncFlags.KanaLock;
        if (evt.getModifierState("ScrollLock")) syncFlags |= SyncFlags.ScrollLock;

        // locks are always properly synchronized and only need to be sent once
        if (this._syncState === 1) {
            this._driver.syncKbdLocks(syncFlags);
            this._syncState = 2;
        }

        if (evt.altKey) syncFlags |= SyncFlags.AltLeft;
        if (evt.getModifierState("AltGraph")) syncFlags |= SyncFlags.AltRight;

        // ambiguously mods
        let leftOrRight = 0;
        if (evt.metaKey) leftOrRight |= (evt.code === 'OSRight' && flag === KeyAcquire) ? SyncFlags.OSRight : SyncFlags.OSLeft;
        if (evt.ctrlKey) leftOrRight |= (evt.code === 'ControlRight' && flag === KeyAcquire) ? SyncFlags.ControlRight : SyncFlags.ControlLeft;
        if (evt.shiftKey) leftOrRight |= (evt.code === 'ShiftRight' && flag === KeyAcquire) ? SyncFlags.ShiftRight : SyncFlags.ShiftLeft;

        syncFlags |= leftOrRight;
        this._driver.sendScancodes(scancodesForSynchronizedMods(syncFlags), evt);

        this._keymap.sync(syncFlags);
        this._shiftDown = leftOrRight & (SyncFlags.ShiftRight | SyncFlags.ShiftLeft);

        // mods are unambiguously synchronized
        if (!leftOrRight) {
            this._syncState = 0;
        }

        switch (evt.code) {
            // mods are already processed, no need to continue
            case "OSLeft":
            case "OSRight":
            case "ShiftLeft":
            case "ShiftRight":
            case "ControlLeft":
            case "ControlRight":
                return false;
        }

        return true;
    }

    _codeToShiftMod(code) {
        if (code === 'ShiftRight') return SyncFlags.ShiftRight;
        if (code === 'ShiftLeft') return SyncFlags.ShiftLeft;
        return 0;
    }
}

class EmulatedKeyboard extends Keyboard
{
    // driver = class {
    // sendUnicode(unicode:String, flag:Number, evt:KeyEvent)
    //  sendScancodes(scancodes:Array[Number], evt:KeyEvent);
    //  syncKbdLocks(syncFlags:Number)
    // }
    constructor(keymap, driver) {
        super(keymap, driver)
        this._hasUnicodeSupport = false;
    }

    setUnicodeSupport(enable) {
        this._hasUnicodeSupport = enable;
    }

    compositeKeyEvent(evt, text) {
        if (!this._preprocessKeyEvent(evt, KeyAcquire)) return;

        const scancodes = this._keymap.toScancodesAndFlags(text, text, KeyAcquire);
        if (scancodes) {
            this._driver.sendScancodes(this._unstatedScancodes(scancodes), evt);
        }
        else if (this._hasUnicodeSupport) {
            this._driver.sendUnicode(text, KeyAcquire, evt);
            this._driver.sendUnicode(text, KeyRelease, evt);
        }
    }

    // release pressed keys and press released keys
    _unstatedScancodes(scancodes) {
        const set = new Set();
        for (const scancode of scancodes) {
            if (scancode & KeyRelease)
                set.delete(scancode & ~KeyRelease);
            else
                set.add(scancode);
        }

        const len = scancodes.length;
        for (let i = 0; i < len; ++i) {
            const scancode = scancodes[i];
            if (!(scancode & KeyRelease) && set.has(scancode)) {
                scancodes.push(scancode & KeyRelease);
            }
        }

        return scancodes;
    }

    _keyEvent(evt, flag) {
        if (!this._preprocessKeyEvent(evt, flag)) return;

        if (isComposing(evt)) {
            return;
        }

        // numpad
        if (evt.location === KeyboardEvent.DOM_KEY_LOCATION_NUMPAD) {
            const scancode = numpadCodeToScancode(evt.code);
            this._driver.sendScancodes([scancode | flag], evt);
            return;
        }

        const scancodes = this._keymap.toScancodesAndFlags(evt.key, evt.code, flag);
        if (scancodes) {
            this._driver.sendScancodes(scancodes, evt);
        }
        else if (this._hasUnicodeSupport) {
            this._driver.sendUnicode(evt.key, flag, evt);
        }
    }
}

class UnicodeKeyboard extends Keyboard
{
    // driver = class {
    // sendUnicode(unicode:String, flag:Number, evt:KeyEvent)
    //  sendScancodes(scancodes:Array[Number], evt:KeyEvent);
    //  syncKbdLocks(syncFlags:Number)
    // }
    constructor(keymap, driver) {
        super(keymap, driver)
    }

    _keyEvent(evt, flag) {
        if (!this._preprocessKeyEvent(evt, flag)) return;

        if (isComposing(evt)) {
            return;
        }

        if (evt.key.length === 1 || evt.key.charCodeAt(0) > 127) {
            this._driver.sendUnicode(evt.key, flag, evt);
        }
        else {
            const scancodes = codeToScancodes(evt.key, flag) || codeToScancodes(evt.code, flag);
            if (scancodes) {
                this._driver.sendScancodes(scancodes, evt);
            }
        }
    }
}
console.log('After classes');

let rdpclient = null;
let socket = null;
let connected = false;

let hWheelSupport = false;
let MouseXSupport = false;

const sendData = function() {
    if (socket && socket.readyState === WebSocket.OPEN && rdpclient) {
        const out = rdpclient.getOutputData();
        if (out.length) {
            socket.send(out);
            rdpclient.resetOutputData();
        }
    }
};

const sendScancodes = function(scancodes, evt) {
    if (scancodes) {
        evt.preventDefault();
        evt.stopImmediatePropagation();
        for (let i = 0; i < scancodes.length; ++i) {
            const scancode = scancodes[i];
            console.log('Scancode: send', scancode, '0x'+scancode.toString(16));
            rdpclient.writeScancodeEvent(scancode);
        }
        sendData();
    }
    else {
        console.warn('Scancode: unknown keycode', evt.key, evt.code)
    }
}

const sendUnicode = function(unicode, flag, evt) {
    const scancode = keycodeToSingleScancode(unicode); // actionLayout[unicode];
    if (scancode) {
        sendScancodes([scancode | flag], evt)
    }
    else {
        evt.preventDefault();
        evt.stopImmediatePropagation();
        for (let i = 0; i < unicode.length; ++i) {
            const code = unicode.charCodeAt(i);
            console.log('Unicode: send', code, `(0x${code.toString(16)})`, 'flag:', flag, `(0x${flag.toString(16)})`);
            rdpclient.writeUnicodeEvent(code, flag);
        }
        sendData();
    }
}

const isComposing = function(evt) {
    switch (evt.key) {
        case 'Control':
        case 'Shift':
        case 'Alt':
        case 'AltGraph':
        case 'NumLock':
        case 'ScrollLock':
        case 'CapsLock':
        case 'OS':
            return false;
        case 'Dead':
        case 'Compose':
        case 'Process':
        // case 'Unidentified':
            return true;
    }
    return evt.isComposing;
};

const currentLayout = function() {
    const kbdLayoutId = defaultLayoutId; // since we don't have input, use default
    const layout = layouts[kbdLayoutId];
    return layout;
}

const keyboardDriver = {
    sendUnicode,
    sendScancodes,
    syncKbdLocks: function(syncFlags) {
        rdpclient.syncKbdLocks(syncFlags);
    },
};

const keymap = new ReversedKeymap(currentLayout());

const scancodeKeyboard = new Keyboard(keymap, keyboardDriver);
const emulatedKeyboard = new EmulatedKeyboard(keymap, keyboardDriver);
const unicodeKeyboard = new UnicodeKeyboard(keymap, keyboardDriver);
let keyboardForEvent = scancodeKeyboard;

const onKeyUp = (evt) => { keyboardForEvent.keyup(evt); };
const onKeyDown = (evt) => { keyboardForEvent.keydown(evt); };

function sendMouseEvent(evt, flags)
{
    evt.preventDefault();
    evt.stopImmediatePropagation();
    rdpclient.writeMouseEvent(evt.offsetX, evt.offsetY, flags);
    sendData();
}

function sendMouseButton(evt, flag)
{
    switch (evt.button) {
    case 0: return sendMouseEvent(evt, flag | MouseFlags.LeftButton);
    case 1: return sendMouseEvent(evt, flag | MouseFlags.MiddleButton);
    case 2: return sendMouseEvent(evt, flag | MouseFlags.RightButton);
    case 3: if (MouseXSupport) return sendMouseEvent(evt, flag | MouseFlags.Button4); break;
    case 4: if (MouseXSupport) return sendMouseEvent(evt, flag | MouseFlags.Button5); break;
    }
}

const onMouseMove = (evt) => sendMouseEvent(evt, MouseFlags.Move);
const onMouseDown = (evt) => sendMouseButton(evt, MouseFlags.Down);
const onMouseUp = (evt) => sendMouseButton(evt, MouseFlags.Up);
const onMouseWheel = (evt) => {
    if (evt.deltaY) {
        const delta = (evt.deltaY < 0)
            ? MouseFlags.WheelRotationMask
            : MouseFlags.WheelNegative;
        sendMouseEvent(evt, MouseFlags.VerticalWheel | delta);
    }
    else if (evt.deltaX && hWheelSupport) {
        const delta = (evt.deltaX < 0)
            ? MouseFlags.WheelRotationMask
            : MouseFlags.WheelNegative;
        sendMouseEvent(evt, MouseFlags.HorizontalWheel | delta);
    }
};

console.log('Attaching event listeners...');

// Check if elements exist
const connectBtn = document.getElementById('connectBtn');
const disconnectBtn = document.getElementById('disconnectBtn');
const cancelConnectBtn = document.getElementById('cancelConnectBtn');
const startRdpBtn = document.getElementById('startRdpBtn');
const closeBtn = document.querySelector('.close-btn');
const maximizeBtn = document.querySelector('.maximize-btn');
const minimizeBtn = document.querySelector('.minimize-btn');
const zoomBtn = document.getElementById('zoomBtn');

if (connectBtn) {
    connectBtn.addEventListener('click', function(e) {
        showRdpConfig();
    });
}

if (disconnectBtn) {
    disconnectBtn.addEventListener('click', function(e) {
        disconnect();
    });
}

if (cancelConnectBtn) {
    cancelConnectBtn.addEventListener('click', function(e) {
        cancelConnect();
    });
}


// Window control buttons
if (closeBtn) {
    closeBtn.addEventListener('click', function(e) {
        console.log('Close button clicked');
        disconnect();
    });
    console.log('Close button listener attached');
}

if (maximizeBtn) {
    maximizeBtn.addEventListener('click', function(e) {
        console.log('Maximize button clicked');
        toggleFullscreen();
    });
    console.log('Maximize button listener attached');
}

if (minimizeBtn) {
    minimizeBtn.addEventListener('click', function(e) {
        console.log('Minimize button clicked');
        toggleFullscreen();
    });
    console.log('Minimize button listener attached');
}

if (zoomBtn) {
    zoomBtn.addEventListener('click', function(e) {
        console.log('Zoom button clicked');
        toggleZoom();
    });
    console.log('Zoom button listener attached');
}

// Input event handlers
let ecanvas = document.getElementById('canvas');
let ecanvasFocus = document.getElementById('canvasFocus');

function showNotification(message, type = 'info') {
    const notificationArea = document.getElementById('notification-area');
    const notification = document.createElement('div');
    notification.className = `alert alert-${type} alert-dismissible fade show`;
    notification.innerHTML = `${message}<button type="button" class="btn-close" data-bs-dismiss="alert"></button>`;
    notificationArea.appendChild(notification);
    setTimeout(() => {
        if (notification.parentNode) {
            notification.remove();
        }
    }, 5000);
}

function toggleFullscreen() {
    const rdpWindow = document.querySelector('.rdp-window');
    const maximizeBtn = document.querySelector('.maximize-btn');
    const maximizeIcon = maximizeBtn.querySelector('.maximize-icon');

    if (rdpWindow.classList.contains('fullscreen')) {
        // Exit fullscreen
        rdpWindow.classList.remove('fullscreen');
        maximizeIcon.textContent = '□';
        maximizeBtn.title = 'Fullscreen';
    } else {
        // Enter fullscreen
        rdpWindow.classList.add('fullscreen');
        maximizeIcon.textContent = '▭'; // Restore icon
        maximizeBtn.title = 'Exit Fullscreen';
    }
}

function toggleZoom() {
    if (!rdpclient || !connected) return;

    const zoomBtn = document.getElementById('zoomBtn');
    const zoomIcon = zoomBtn.querySelector('i');
    const rdpContainer = document.getElementById('rdp');

    if (zoomBtn.classList.contains('active')) {
        // Switch to scaled view (fit to window)
        zoomBtn.classList.remove('active');
        zoomBtn.title = 'Toggle Zoom (Scale/Fit)';
        zoomIcon.className = 'fas fa-search-plus';
        rdpContainer.classList.remove('full-size');

        // Enable scaling, disable clipping
        if (rdpclient.scaleViewport !== undefined) {
            rdpclient.scaleViewport = true;
        }

        showNotification('Switched to scaled view (fit to window)', 'info');
    } else {
        // Switch to full-size view with scrollbars
        zoomBtn.classList.add('active');
        zoomBtn.title = 'Toggle Zoom (Actual Size)';
        zoomIcon.className = 'fas fa-search-minus';
        rdpContainer.classList.add('full-size');

        // Disable scaling, enable clipping
        if (rdpclient.scaleViewport !== undefined) {
            rdpclient.scaleViewport = false;
        }

        showNotification('Switched to actual size view with scrollbars', 'info');
    }
}

function showRdpConfig() {
    if (connected) return;

    // Show the RDP configuration modal
    const modal = new bootstrap.Modal(document.getElementById('rdpConfigModal'));
    modal.show();
}

function startRdpConnection() {
    // Hide the modal
    const modal = bootstrap.Modal.getInstance(document.getElementById('rdpConfigModal'));
    modal.hide();

    // Start the actual connection
    connect();
}

function connect() {
    console.log('connect called, connected:', connected);
    if (connected) return;

    // Check if WebAssembly module is loaded
    if (!_Module) {
        console.log('WebAssembly module not loaded yet');
        showNotification('WebAssembly module not loaded yet. Please wait.', 'warning');
        return;
    }

    // Hide the status text and show loading message
    document.getElementById('rdp_status').style.display = 'none';
    document.getElementById('rdp_loading').style.display = 'block';
    document.getElementById('cancelConnectBtn').style.display = 'inline-block';

    const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    const wsUrl = wsProtocol + '//' + window.location.host + '/rdp/%s/ws';

    try {
        socket = new WebSocket(wsUrl);
        socket.binaryType = 'arraybuffer';

        socket.onopen = () => {
            console.info('Graphics implementation: ' + document.getElementById('rdpGraphicsImpl').value);
            const gd = gdImplTable[document.getElementById('rdpGraphicsImpl').value](ecanvas, _Module);
            gd.drawRect(0, 0, gd.width, gd.height, 0);

            // Get form values
            const config = {
                width: parseInt(document.getElementById('rdpWidth').value) || 1024,
                height: parseInt(document.getElementById('rdpHeight').value) || 768,
                username: document.getElementById('rdpUsername').value || '',
                password: document.getElementById('rdpPassword').value || '',
                domain: document.getElementById('rdpDomain').value || '',
                bpp: parseInt(document.getElementById('rdpBpp').value) || 32,
                verbosity: parseInt(document.getElementById('rdpVerbosity').value) || 0,
                keylayout: currentLayout().klid,
            };

            // Add JSON config if provided
            const jsonConfig = document.getElementById('rdpJsonConfig').value.trim();
            if (jsonConfig) {
                try {
                    const extraConfig = JSON.parse(jsonConfig);
                    Object.assign(config, extraConfig);
                } catch (e) {
                    console.warn('Invalid JSON config, ignoring:', e);
                }
            }

            console.table(config);

            rdpclient = new RdpClient(gd, config);

            window.rdpclient = rdpclient;
            window.sendData = sendData;

            socket.onmessage = function(event) {
                rdpclient.processInputData(event.data);
                sendData();
            };

            rdpclient.writeFirstPacket();
            sendData();

            connected = true;
            document.getElementById('connectBtn').disabled = true;
            document.getElementById('disconnectBtn').disabled = false;
            // Hide loading message
            document.getElementById('rdp_loading').style.display = 'none';
            showNotification('RDP session connected', 'success');

            // Set up input handling
            updateInput();
        };

        socket.onclose = () => {
            connected = false;
            document.getElementById('connectBtn').disabled = false;
            document.getElementById('disconnectBtn').disabled = true;
            // Reset UI to initial state
            document.getElementById('rdp_status').style.display = 'block';
            document.getElementById('rdp_loading').style.display = 'none';
            document.getElementById('cancelConnectBtn').style.display = 'none';
            showNotification('RDP session disconnected', 'info');
            canvasStopEvents();
            rdpclient = null;
            socket = null;
        };

        socket.onerror = (e) => {
            console.error('RDP WebSocket error:', e);
            showNotification('RDP connection error', 'danger');
        };

    } catch (e) {
        console.error('Failed to create RDP client:', e);
        showNotification('Failed to connect RDP: ' + e.message, 'danger');
    }
}

function updateInput()
{
    if (!rdpclient) return;

    const inputFlags = rdpclient.getInputFlags();
    const keyboardLayout = rdpclient.getKeyboardLayout();
    console.log(`keyboardLayout: 0x${keyboardLayout.toString(16)}`);
    console.group(`inputFlags: 0x${inputFlags.toString(16)}`);
    for (const k in InputFlags) {
        console.log(`${k}: ${(inputFlags & InputFlags[k]) ? 1 : 0} (0x${InputFlags[k].toString(16)})`);
    }
    console.groupEnd();
    hWheelSupport = Boolean(inputFlags & InputFlags.HorizontalWheel);
    MouseXSupport = Boolean(inputFlags & InputFlags.MouseX);

    updateKbdInput(inputFlags);
    kbdInputLayoutEvent = () => {
        if (!rdpclient) return;
        updateKbdInput(inputFlags);
    }

    canvasStopEvents();
    canvasStartEvents();
}

function updateKbdInput(inputFlags)
{
    const kbdMethodId = document.getElementById('rdpKeyboardMethod').selectedIndex;
    const altGrIsCtrlAndAlt = document.getElementById('rdpAltGrCtrlAlt').checked;
    const kbdMethod = document.getElementById('rdpKeyboardMethod')[kbdMethodId].value;
    const hasUnicode = !!(inputFlags & InputFlags.Unicode);
    const layout = currentLayout();
    console.group('keyboard');
    console.log(`unicode support: ${hasUnicode}`);
    console.log(`method: (${kbdMethodId}) ${kbdMethod}`);
    console.log(`layout: (0x${layout.klid.toString(16)}) ${layout.displayName}`);
    console.log(`AltGr = Ctrl + Alt: ${altGrIsCtrlAndAlt}`);
    console.groupEnd();

    keymap.altGrIsCtrlAndAlt = altGrIsCtrlAndAlt;
    keymap.layout = layout;

    emulatedKeyboard.setUnicodeSupport(hasUnicode);

    const newKeyboardForEvent = (kbdMethod === 'emulateScancode') ? emulatedKeyboard
                              : (kbdMethod === 'unicode') ? unicodeKeyboard
                              : scancodeKeyboard;
    newKeyboardForEvent.copyInternalStateFrom(keyboardForEvent);
    keyboardForEvent = newKeyboardForEvent;
    keyboardForEvent.activeModsSync();
}

function canvasStartEvents()
{
    ecanvas.addEventListener('mousemove', onMouseMove);
    ecanvas.addEventListener('mousedown', onMouseDown);
    ecanvas.addEventListener('mouseup', onMouseUp);
    ecanvas.addEventListener('wheel', onMouseWheel);
    ecanvasFocus.addEventListener('keydown', onKeyDown);
    ecanvasFocus.addEventListener('keyup', onKeyUp);
    canvasEnableFocus();
}

function canvasStopEvents()
{
    ecanvas.removeEventListener('mousemove', onMouseMove);
    ecanvas.removeEventListener('mousedown', onMouseDown);
    ecanvas.removeEventListener('mouseup', onMouseUp);
    ecanvas.removeEventListener('wheel', onMouseWheel);
    ecanvasFocus.removeEventListener('keydown', onKeyDown);
    ecanvasFocus.removeEventListener('keyup', onKeyUp);
}

function canvasFocus(evt)
{
    console.log('focus');
    ecanvasFocus.addEventListener('keydown', onKeyDown);
    ecanvasFocus.addEventListener('keyup', onKeyUp);
    ecanvas.onclick = (e) => {
        e.preventDefault();
        e.stopImmediatePropagation();
    };
    keyboardForEvent.activeModsSync();
}

function canvasEnableFocus()
{
    // preventScroll don't work with firefox
    ecanvasFocus.focus({preventScroll: true});
}

function canvasBlur()
{
    console.log('blur');
    ecanvasFocus.removeEventListener('keydown', onKeyDown);
    ecanvasFocus.removeEventListener('keyup', onKeyUp);
    ecanvas.onclick = canvasEnableFocus;
}

ecanvasFocus.onblur = canvasBlur;
ecanvasFocus.onfocus = canvasFocus;

function disconnect() {
    if (socket) {
        socket.close();
        socket = null;
    }
    if (rdpclient) {
        rdpclient = null;
    }
    connected = false;
    document.getElementById('connectBtn').disabled = false;
    document.getElementById('disconnectBtn').disabled = true;
    // Reset UI to initial state
    document.getElementById('rdp_status').style.display = 'block';
    document.getElementById('rdp_loading').style.display = 'none';
    document.getElementById('cancelConnectBtn').style.display = 'none';
    canvasStopEvents();
    location.reload();
}

function cancelConnect() {
    if (socket) {
        socket.close();
        socket = null;
    }
    if (rdpclient) {
        rdpclient = null;
    }
    connected = false;
    document.getElementById('connectBtn').disabled = false;
    document.getElementById('disconnectBtn').disabled = true;
    // Reset UI to initial state
    document.getElementById('rdp_status').style.display = 'block';
    document.getElementById('rdp_loading').style.display = 'none';
    document.getElementById('cancelConnectBtn').style.display = 'none';
    showNotification('RDP connection cancelled', 'warning');
}
</script>
</body>
</html>