import {useEffect, useRef} from 'react';
import './PixelGridCanvas.css';
import {useRgbaThemeContext} from '../theme.tsx';

const pixelsPerRow = 352;
const pixelsPerCol = 160;
const observerMessageSize = pixelsPerCol * pixelsPerRow / 8;

enum GamePixelEntityType {
    Wall = 0x0,
    Tank = 0x1,
    Bullet = 0x2,
    PowerUp = 0x3
}

function getPixelDataIndexes(bitIndex: number) {
    return {
        byteIndex: Math.floor(bitIndex / 8),
        bitInByteIndex: 7 - bitIndex % 8
    };
}

function parseAdditionalDataNibble(nibble: number) {
    const isPlayerMask = 1;
    const entityTypeMask = 12;

    return {
        isCurrentPlayer: (nibble & isPlayerMask) != 0,
        entityType: ((nibble & entityTypeMask) >> 2) as GamePixelEntityType,
    };
}

function drawPixelsToCanvas(
    {
        context, width, height, pixels, additional, colors
    }: {
        context: CanvasRenderingContext2D,
        width: number,
        height: number,
        pixels: Uint8ClampedArray,
        additional: Uint8ClampedArray | null,
        colors: {
            background: Uint8ClampedArray,
            foreground: Uint8ClampedArray,
            player: Uint8ClampedArray,
            tanks: Uint8ClampedArray,
            powerUps: Uint8ClampedArray,
            bullets: Uint8ClampedArray
        }
    }
) {
    let additionalDataIndex = 0;
    let additionalDataByte: number | null = null;
    const nextPixelColor = (isOn: boolean) => {
        if (!isOn)
            return colors.background;
        if (!additional)
            return colors.foreground;

        let info;
        if (additionalDataByte === null) {
            additionalDataByte = additional[additionalDataIndex];
            additionalDataIndex++;
            info = parseAdditionalDataNibble(additionalDataByte);
        } else {
            info = parseAdditionalDataNibble(additionalDataByte >> 4);
            additionalDataByte = null;
        }

        if (info.isCurrentPlayer)
            return colors.player;

        if (info.entityType === GamePixelEntityType.Tank)
            return colors.tanks;
        if (info.entityType === GamePixelEntityType.PowerUp)
            return colors.powerUps;
        if (info.entityType === GamePixelEntityType.Bullet)
            return colors.bullets;

        return colors.foreground;
    };

    const imageData = context.getImageData(0, 0, width, height, {colorSpace: 'srgb'});
    const data = imageData.data;

    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const pixelIndex = y * pixelsPerRow + x;
            const {byteIndex, bitInByteIndex} = getPixelDataIndexes(pixelIndex);
            const isOn = (pixels[byteIndex] & (1 << bitInByteIndex)) !== 0;
            const color = nextPixelColor(isOn);

            for (let colorChannel of [0, 1, 2, 3])
                data[pixelIndex * 4 + colorChannel] = color[colorChannel];
        }
    }

    context.putImageData(imageData, 0, 0);
}

export default function PixelGridCanvas({pixels}: {
    readonly pixels: Uint8ClampedArray;
}) {
    const theme = useRgbaThemeContext();
    const canvasRef = useRef<HTMLCanvasElement>(null);

    useEffect(() => {
        let ignore = false;
        const start = async () => {
            const canvas = canvasRef.current;
            if (canvas === null)
                throw new Error('canvas null');

            const drawContext = canvas.getContext('2d');
            if (!drawContext)
                throw new Error('could not get draw context');

            let additionalData: Uint8ClampedArray | null = null;
            if (pixels.length > observerMessageSize) {
                additionalData = pixels.slice(observerMessageSize);
                pixels = pixels.slice(0, observerMessageSize);
            }

            if (ignore)
                return;

            drawPixelsToCanvas({
                context: drawContext,
                width: canvas.width,
                height: canvas.height,
                pixels,
                additional: additionalData,
                colors: {
                    background: theme.background,
                    foreground: theme.primary,
                    player: theme.secondary,
                    tanks: theme.tertiary,
                    powerUps: theme.tertiary,
                    bullets: theme.tertiary
                }
            });
        };

        start();
        return () => {
            ignore = true;
        };
    }, [pixels, canvasRef.current, theme]);

    return <canvas
        ref={canvasRef}
        className="PixelGridCanvas"
        width={pixelsPerRow}
        height={pixelsPerCol}
    />;
}
