import * as React from 'react';

import CenterElement from '/components/center-element';
import FormSelectFiles from '/components/form-select-files';
import CanvasGBAEmulator from '/components/canvas-gba-emulator';
import OverlayControls from '/components/overlay-controls';
import OverlayMenu from '/components/overlay-menu';
import OverlaySelectFiles from '/components/overlay-select-files';
import CloseButton from '/components/close-button';

import Endian from '/endian';

import {MIN_WIDTH, MIN_HEIGHT, PACKET_ID_HELLO, PACKET_ID_SEND_FRAME} from '/constants';

type setBooleanCallback = (c: boolean) => boolean;

export interface handleClickStartEmulationButtonObjectArgs {
    e: React.MouseEvent<HTMLInputElement>;
    inputRom: HTMLInputElement | null;
    inputSaveState: HTMLInputElement | null;
    canvas: HTMLCanvasElement | null;
    printingFrame: boolean;
    setPrintingFrame: (c: setBooleanCallback) => void;
    setEmulationStarted: (c: boolean) => void,
    setHiddenMenu: (c: boolean) => void;
    setHiddenFormSelectFiles: (c: boolean) => void;
};

function handleClickStartEmulationButton({e, inputRom, inputSaveState, canvas, printingFrame,
        setPrintingFrame, setEmulationStarted, setHiddenMenu,
        setHiddenFormSelectFiles}: handleClickStartEmulationButtonObjectArgs) {
    e.preventDefault();
    if (canvas == null) {
        alert('Canvas does not exists?');
        return;
    }
    const ctx = canvas.getContext('2d')
    if (ctx == null) {
        alert('Unable to create canvas context, doing nothing');
        return;
    }
    if (inputRom == null || inputSaveState == null || inputRom.files == null || inputSaveState.files == null) {
        alert('Unable to read the files ');
        return;
    }
    if (inputRom.files.length == 0) {
        alert('There is no rom still');
        return;
    }
    if (inputSaveState.files.length == 0) {
        alert('There is no savestate still');
        return;
    }
    const rom_file = inputRom.files[0];
    const savestate_file = inputSaveState.files[0];
    rom_file.arrayBuffer().then((rom_buffer) => {
        savestate_file.arrayBuffer().then((savestate_buffer) => {
            const rom_array = new Uint8Array(rom_buffer);
            const savestate_array = new Uint8Array(savestate_buffer);
            const websocket = new WebSocket(`ws://${window.location.host}/ws`);
            websocket.binaryType = 'arraybuffer';
            websocket.onclose = (message) => {
                setEmulationStarted(false);
                console.log('Closing websocket.');
            }
            websocket.onopen = () => {
                console.log('Opened websocket.');
                setEmulationStarted(true);
                sendHello(websocket, rom_array, savestate_array); 
                setHiddenMenu(true);
                setHiddenFormSelectFiles(true);
            };
            setPrintingFrame(c => false);
            websocket.addEventListener('message', (event) => {
                onWebSocketPacket(event, canvas, ctx, printingFrame, setPrintingFrame)
           });
        });
    });
}

function concatU8Array(array1: Uint8Array, array2: Uint8Array) {
    const final_array = new Uint8Array(array1.length + array2.length);
    final_array.set(array1);
    final_array.set(array2, array1.length);
    return final_array;
}

function sendPacket(websocket: WebSocket, id: bigint, raw_data: Uint8Array) {
    const packet_u8 = concatU8Array( 
        concatU8Array(Endian.u64ToByteArrayBigEndian(id), Endian.u64ToByteArrayBigEndian(BigInt(raw_data.length))),
        raw_data
    );
    const packet_buffer = packet_u8.buffer;
    console.log('Sending packet');
    websocket.send(packet_buffer);
}

 function sendHello(websocket: WebSocket, rom_array: Uint8Array, savestate_array: Uint8Array) {
    console.log('Sending hello.');
    const length_rom = BigInt(rom_array.length);
    const length_savestate = BigInt(savestate_array.length);
    const raw_data = 
    concatU8Array(
        concatU8Array(
            concatU8Array(Endian.u64ToByteArrayBigEndian(length_rom), rom_array),
            Endian.u64ToByteArrayBigEndian(length_savestate)
        ),
        savestate_array
    );
    sendPacket(websocket, PACKET_ID_HELLO, raw_data);
}

function onWebSocketPacket(event: MessageEvent, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, printingFrame: boolean, setPrintingFrame: (c: setBooleanCallback) => void) {
    const buffer = event.data;
    let packet_u8: Uint8Array | null = new Uint8Array(buffer);
    const id = Endian.byteArrayToU64BigEndian(packet_u8.slice(0, 8));
    packet_u8 = packet_u8.slice(8, packet_u8.length);
    const size = Endian.byteArrayToU64BigEndian(packet_u8.slice(0, 8));
    const raw_data = packet_u8.slice(8, packet_u8.length);
    packet_u8 = null;
    switch (id) {
        case PACKET_ID_SEND_FRAME:
            handleSendFrame(raw_data, canvas, ctx, printingFrame, setPrintingFrame);
            break;
        default:
            console.log(`Received unknown packet ${id}`);
    }
}

function handleSendFrame(raw_data: Uint8Array, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, printingFrame: boolean, setPrintingFrame: (c: setBooleanCallback) => void) {
    if (printingFrame) {
        return;
    }
    setPrintingFrame(c => true);
    let data: Uint8Array | null = raw_data;
    const stride = Endian.byteArrayToU32BigEndian(data.slice(0, 4));
    data = data.slice(4, data.length);
    const output_buffer_size = Endian.byteArrayToU64BigEndian(data.slice(0, 8));
    data = data.slice(8, data.length);
    const img_data = ctx.createImageData(MIN_WIDTH, MIN_HEIGHT);
    const img_data_u8 = new Uint8Array(img_data.data.buffer);
    for (let i = 0; i<data.length; i++) {
        if (i % 4 == 3) {
            img_data_u8[i] = 255;
            continue;
        }
        img_data_u8[i] = data[i];
    }
    data = null;
    createImageBitmap(img_data).then((bitmap) => drawBitmap(bitmap, canvas, ctx, printingFrame));
}

function drawBitmap(bitmap: ImageBitmap, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, printingFrame: boolean) {
    ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
    printingFrame = false;
}

export default function Page() {
    const screenDimensions = useScreenDimensions();
    const emulatorDimensions = calculateSizeEmulator(screenDimensions);
    const canvasRef = React.useRef<HTMLCanvasElement>(null);
    function resizeCanvas() {
        const canvas = canvasRef.current;
        if (canvas == null) {
            return;
        }
        if (emulatorDimensions.width === undefined || emulatorDimensions.height === undefined) {
            return;
        }
        canvas.width = emulatorDimensions.width;
        canvas.height = emulatorDimensions.height;
        const ctx = canvas.getContext('2d')
        if (ctx == null) {
            return;
        }
        fillBlack(canvas, ctx);        
    };
    const [hiddenFormSelectFiles, setHiddenFormSelectFiles] = React.useState<boolean>(true);
    const [printingFrame, setPrintingFrame] = React.useState<boolean>(false);
    React.useEffect(resizeCanvas, [emulatorDimensions]);
    const refInputRom = React.useRef<HTMLInputElement | null>(null);
    const refInputSaveState = React.useRef<HTMLInputElement | null>(null);
    const [emulationStarted, setEmulationStarted] = React.useState<boolean>(false);
    const [hiddenMenu, setHiddenMenu] = React.useState<boolean>(true);
    const onStartEmulation = (e: React.MouseEvent<HTMLInputElement>) => {
        handleClickStartEmulationButton({
            e: e,
            setEmulationStarted: setEmulationStarted,
            inputRom: refInputRom.current,
            inputSaveState: refInputSaveState.current,
            canvas: canvasRef.current,
            setPrintingFrame: setPrintingFrame,
            printingFrame: printingFrame,
            setHiddenMenu: setHiddenMenu,
            setHiddenFormSelectFiles: setHiddenFormSelectFiles,
        });
    };
    const firstMenuElement = React.useRef<HTMLAnchorElement>(null);
    const screenRef = React.useRef<HTMLDivElement>(null);
    const [isFullscreen, setIsFullscreen] = React.useState<boolean>(false);
    return (
        <div ref={screenRef}>
            <OverlayControls firstMenuElement={firstMenuElement}
                setHiddenMenu={setHiddenMenu}/>
            <OverlayMenu hiddenMenu={hiddenMenu}
                setHiddenMenu={setHiddenMenu} emulationStarted={emulationStarted}
                setHiddenFormSelectFiles={setHiddenFormSelectFiles} screenRef={screenRef}
                isFullscreen={isFullscreen} setIsFullscreen={setIsFullscreen}
                firstMenuElement={firstMenuElement}/>
            <OverlaySelectFiles hiddenFormSelectFiles={hiddenFormSelectFiles}
                setHiddenFormSelectFiles={setHiddenFormSelectFiles}
                refInputRom={refInputRom} refInputSaveState={refInputSaveState}
                onStartEmulation={onStartEmulation}/>
            <div>
                <CenterElement full={true}>
                        <CanvasGBAEmulator canvasRef={canvasRef}/>
                </CenterElement>
            </div>
        </div>
    );
}

function getScreenDimensions() {
    return {
        width: document.body.clientWidth,
        height: document.body.clientHeight
    };
}

function useScreenDimensions() {
    const [screenDimensions, setScreenDimensions] = React.useState<EmulatorDimensions>(getScreenDimensions());

    React.useEffect(() => {
        function onResize() {
            setScreenDimensions(getScreenDimensions());
        }

        window.addEventListener("resize", onResize);
    
        return () => {
            window.removeEventListener("resize", onResize);
        }
    }, []);
    
    return screenDimensions;
}

function fillBlack(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
    ctx.beginPath();
    ctx.rect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = '#0E0E10';
    ctx.fill();
}

export interface EmulatorDimensions {
    width?: number;
    height?: number;
};
function calculateSizeEmulator(screenDimensions: EmulatorDimensions): EmulatorDimensions {
    if (screenDimensions.width === undefined || screenDimensions.height === undefined) {
        console.error(screenDimensions, 'screenDimensions has undefined fields');
        return {};
    }
    const width = screenDimensions.width;
    const height = screenDimensions.height;
    const emulatorDimensions: EmulatorDimensions = {};
    if (width < MIN_WIDTH || height < MIN_HEIGHT) {
        return {
            width: MIN_WIDTH,
            height: MIN_HEIGHT,
        };
    }
    const ratioWidth = width / MIN_WIDTH;
    const ratioHeight = height / MIN_HEIGHT;
    if (ratioWidth < ratioHeight) {
        emulatorDimensions.width = MIN_WIDTH * ratioWidth;
        emulatorDimensions.height = MIN_HEIGHT * ratioWidth;
    } else {
        emulatorDimensions.height = MIN_HEIGHT * ratioHeight;
        emulatorDimensions.width = MIN_WIDTH * ratioHeight;
    }
    return emulatorDimensions;
}
