/* global VNC_frame_data, VNC_frame_encoding */

import * as WebUtil from '../app/webutil.js';
import RecordingPlayer from './playback.js';
import Base64 from '../core/base64.js';

let frames = null;

function message(str) {
    const cell = document.getElementById('messages');
    cell.textContent += str + "\n";
    cell.scrollTop = cell.scrollHeight;
}

function loadFile() {
    const fname = WebUtil.getQueryVar('data', null);

    if (!fname) {
        return Promise.reject("Must specify data=FOO in query string.");
    }

    message("Loading " + fname + "...");

    return new Promise((resolve, reject) => {
        const script = document.createElement("script");
        script.onload = resolve;
        script.onerror = reject;
        document.body.appendChild(script);
        script.src = "../recordings/" + fname;
    });
}

function enableUI() {
    const iterations = WebUtil.getQueryVar('iterations', 3);
    document.getElementById('iterations').value = iterations;

    const mode = WebUtil.getQueryVar('mode', 3);
    if (mode === 'realtime') {
        document.getElementById('mode2').checked = true;
    } else {
        document.getElementById('mode1').checked = true;
    }

    message("Loaded " + VNC_frame_data.length + " frames");

    const startButton = document.getElementById('startButton');
    startButton.disabled = false;
    startButton.addEventListener('click', start);

    message("Converting...");

    frames = VNC_frame_data;

    let encoding;
    // Only present in older recordings
    if (window.VNC_frame_encoding) {
        encoding = VNC_frame_encoding;
    } else {
        let frame = frames[0];
        let start = frame.indexOf('{', 1) + 1;
        if (frame.slice(start, start+4) === 'UkZC') {
            encoding = 'base64';
        } else {
            encoding = 'binary';
        }
    }

    for (let i = 0;i < frames.length;i++) {
        let frame = frames[i];

        if (frame === "EOF") {
            frames.splice(i);
            break;
        }

        let dataIdx = frame.indexOf('{', 1) + 1;

        let time = parseInt(frame.slice(1, dataIdx - 1));

        let u8;
        if (encoding === 'base64') {
            u8 = Base64.decode(frame.slice(dataIdx));
        } else {
            u8 = new Uint8Array(frame.length - dataIdx);
            for (let j = 0; j < frame.length - dataIdx; j++) {
                u8[j] = frame.charCodeAt(dataIdx + j);
            }
        }

        frames[i] = { fromClient: frame[0] === '}',
                      timestamp: time,
                      data: u8 };
    }

    message("Ready");
}

class IterationPlayer {
    constructor(iterations, frames) {
        this._iterations = iterations;

        this._iteration = undefined;
        this._player = undefined;

        this._start_time = undefined;

        this._frames = frames;

        this._state = 'running';

        this.onfinish = () => {};
        this.oniterationfinish = () => {};
        this.rfbdisconnected = () => {};
    }

    start(realtime) {
        this._iteration = 0;
        this._start_time = (new Date()).getTime();

        this._realtime = realtime;

        this._nextIteration();
    }

    _nextIteration() {
        const player = new RecordingPlayer(this._frames, this._disconnected.bind(this));
        player.onfinish = this._iterationFinish.bind(this);

        if (this._state !== 'running') { return; }

        this._iteration++;
        if (this._iteration > this._iterations) {
            this._finish();
            return;
        }

        player.run(this._realtime, false);
    }

    _finish() {
        const endTime = (new Date()).getTime();
        const totalDuration = endTime - this._start_time;

        const evt = new CustomEvent('finish',
                                    { detail:
                                      { duration: totalDuration,
                                        iterations: this._iterations } } );
        this.onfinish(evt);
    }

    _iterationFinish(duration) {
        const evt = new CustomEvent('iterationfinish',
                                    { detail:
                                      { duration: duration,
                                        number: this._iteration } } );
        this.oniterationfinish(evt);

        this._nextIteration();
    }

    _disconnected(clean, frame) {
        if (!clean) {
            this._state = 'failed';
        }

        const evt = new CustomEvent('rfbdisconnected',
                                    { detail:
                                      { clean: clean,
                                        frame: frame,
                                        iteration: this._iteration } } );
        this.onrfbdisconnected(evt);
    }
}

function start() {
    document.getElementById('startButton').value = "Running";
    document.getElementById('startButton').disabled = true;

    const iterations = document.getElementById('iterations').value;

    let realtime;

    if (document.getElementById('mode1').checked) {
        message(`Starting performance playback (fullspeed) [${iterations} iteration(s)]`);
        realtime = false;
    } else {
        message(`Starting realtime playback [${iterations} iteration(s)]`);
        realtime = true;
    }

    const player = new IterationPlayer(iterations, frames);
    player.oniterationfinish = (evt) => {
        message(`Iteration ${evt.detail.number} took ${evt.detail.duration}ms`);
    };
    player.onrfbdisconnected = (evt) => {
        if (!evt.detail.clean) {
            message(`noVNC sent disconnected during iteration ${evt.detail.iteration} frame ${evt.detail.frame}`);
        }
    };
    player.onfinish = (evt) => {
        const iterTime = parseInt(evt.detail.duration / evt.detail.iterations, 10);
        message(`${evt.detail.iterations} iterations took ${evt.detail.duration}ms (average ${iterTime}ms / iteration)`);

        document.getElementById('startButton').disabled = false;
        document.getElementById('startButton').value = "Start";
    };
    player.start(realtime);
}

loadFile().then(enableUI).catch(e => message("Error loading recording: " + e));
