import * as React from 'react';

import CenterElement from '@msgba/components/center-element'
import CanvasGBAEmulator from '@msgba/components/canvas-gba-emulator'
import OverlayControls from '@msgba/components/overlay-controls'
import OverlayMenu from '@msgba/components/overlay-menu'
import OverlaySelectFiles from '@msgba/components/overlay-select-files'
import { sendHello, handleSendFrame } from '@msgba/packet'

import Endian from '@msgba/endian'

import { MIN_WIDTH_GBA, MIN_HEIGHT_GBA, MIN_WIDTH_GBC, MIN_HEIGHT_GBC, PACKET_ID_SEND_FRAME } from '@msgba/constants'

export interface handleClickStartEmulationButtonObjectArgs {
    e: React.MouseEvent<HTMLInputElement>
    inputRom: HTMLInputElement | null
    inputSaveState: HTMLInputElement | null
    canvas: React.RefObject<HTMLCanvasElement | null>
    setEmulationStarted: (c: boolean) => void
    setHiddenMenu: (c: boolean) => void
    setHiddenFormSelectFiles: (c: boolean) => void
    setWebSocket: (c: WebSocket | null) => void
    isGBC: boolean
    setIsGBC: (c: boolean) => void
    controlsRef: React.RefObject<HTMLDivElement>
}

function handleClickStartEmulationButton ({
    e, inputRom, inputSaveState, canvas,
    setEmulationStarted, setHiddenMenu,
    setHiddenFormSelectFiles, setWebSocket,
    isGBC, setIsGBC, controlsRef
}: handleClickStartEmulationButtonObjectArgs): void {
    e.preventDefault()
    if (canvas.current == null) {
        alert('Canvas does not exists?')
        return
    }
    const ctx = canvas.current.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 romFile = inputRom.files[0]
    const savestateFile = inputSaveState.files[0]
    romFile.arrayBuffer().then((romBuffer) => {
        savestateFile.arrayBuffer().then((savestateBuffer) => {
            const romArray = new Uint8Array(romBuffer)
            const savestateArray = new Uint8Array(savestateBuffer)
            const webSocket = new WebSocket(`ws://${window.location.host}/ws`)
            setWebSocket(webSocket)
            webSocket.binaryType = 'arraybuffer'
            webSocket.onclose = (message) => {
                setEmulationStarted(false)
                console.log('Closing websocket.')
                setWebSocket(null)
            }
            webSocket.onopen = () => {
                console.log('Opened websocket.')
                setEmulationStarted(true)
                sendHello(webSocket, romArray, savestateArray)
                setHiddenMenu(true)
                setHiddenFormSelectFiles(true)
            }
            webSocket.addEventListener('message', (event) => {
                onWebSocketPacket(event, canvas.current, ctx, isGBC, setIsGBC)
            })
        }).catch((c: string) => {
            console.log('Unable to convert file to array_buffer')
        })
    }).catch((c: string) => {
        console.log('Unable to convert file to array_buffer')
    })
}

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

export default function Page (): JSX.Element {
    const [isGBC, setIsGBC] = React.useState<boolean>(false)
    const screenDimensions = useScreenDimensions(isGBC)
    const emulatorDimensions = calculateSizeEmulator(screenDimensions, isGBC)
    const canvasRef = React.useRef<HTMLCanvasElement>(null)
    function resizeCanvas (): void {
        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)
    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 [webSocket, setWebSocket] = React.useState<WebSocket | null>(null)

    const controlsRef = React.useRef<HTMLDivElement>(null)
    React.useEffect(() => {
        console.log('Focusing the main screen')
        setTimeout(() => {
            if (!hiddenFormSelectFiles) {
                if (refInputRom.current != null) {
                    refInputRom.current.focus()
                }
                return
            }
            if (!hiddenMenu) {
                if (firstMenuElement.current != null) {
                    firstMenuElement.current.focus()
                }
                return
            }

            if (controlsRef.current != null && hiddenMenu) {
                controlsRef.current.focus()
            }
        }, 100)
    }, [hiddenMenu, hiddenFormSelectFiles])
    const onStartEmulation = (e: React.MouseEvent<HTMLInputElement>): void => {
        handleClickStartEmulationButton({
            e,
            setEmulationStarted,
            inputRom: refInputRom.current,
            inputSaveState: refInputSaveState.current,
            canvas: canvasRef,
            setHiddenMenu,
            setHiddenFormSelectFiles,
            setWebSocket,
            isGBC,
            setIsGBC,
            controlsRef
        })
    }
    const firstMenuElement = React.useRef<HTMLAnchorElement>(null)
    const screenRef = React.useRef<HTMLDivElement>(null)
    const [isFullscreen, setIsFullscreen] = React.useState<boolean>(false)
    return (
        <div ref={screenRef}>
            <OverlayControls controlsRef={controlsRef} firstMenuElement={firstMenuElement}
                setHiddenMenu={setHiddenMenu} webSocket={webSocket}/>
            <OverlayMenu hiddenMenu={hiddenMenu}
                setHiddenMenu={setHiddenMenu} emulationStarted={emulationStarted}
                setHiddenFormSelectFiles={setHiddenFormSelectFiles} screenRef={screenRef}
                isFullscreen={isFullscreen} setIsFullscreen={setIsFullscreen}
                firstMenuElement={firstMenuElement} websocket={webSocket}/>
            <OverlaySelectFiles hiddenFormSelectFiles={hiddenFormSelectFiles}
                setHiddenFormSelectFiles={setHiddenFormSelectFiles}
                refInputRom={refInputRom} refInputSaveState={refInputSaveState}
                onStartEmulation={onStartEmulation}/>
            <div>
                <CenterElement full={true}>
                    <CanvasGBAEmulator canvasRef={canvasRef}/>
                </CenterElement>
            </div>
        </div>
    )
}

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

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

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

        window.addEventListener('resize', onResize)

        return () => {
            window.removeEventListener('resize', onResize)
        }
    }, [isGBC])
    return screenDimensions
}

function fillBlack (canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D): void {
    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, isGBC: boolean): 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 = {}
    const minWidth = !isGBC ? MIN_WIDTH_GBA : MIN_WIDTH_GBC
    const minHeight = !isGBC ? MIN_HEIGHT_GBA : MIN_HEIGHT_GBC
    if (width < minWidth || height < minHeight) {
        return {
            width: minWidth,
            height: minHeight
        }
    }
    const ratioWidth = width / minWidth
    const ratioHeight = height / minHeight
    if (ratioWidth < ratioHeight) {
        emulatorDimensions.width = minWidth * ratioWidth
        emulatorDimensions.height = minHeight * ratioWidth
    } else {
        emulatorDimensions.height = minHeight * ratioHeight
        emulatorDimensions.width = minWidth * ratioHeight
    }
    return emulatorDimensions
}
