import { assertExists } from "@davidsouther/jiffies/lib/esm/assert.js";
import { Memory } from "@nand2tetris/simulator/cpu/memory.js";
import { useCallback, useRef, useState } from "react";
import { useClockFrame, useClockReset } from "../clockface.js";

const WHITE = "white";
const BLACK = "black";
type COLOR = typeof WHITE | typeof BLACK;

export interface ScreenMemory {
  get(idx: number): number;
}

export function reduceScreen(memory: Memory, offset = 0): ScreenMemory {
  return {
    get(idx: number): number {
      return memory.get(offset + idx);
    },
  };
}

function get(mem: ScreenMemory, x: number, y: number): COLOR {
  const byte = mem.get(32 * y + ((x / 16) | 0));
  const bit = byte & (1 << x % 16);
  return bit === 0 ? WHITE : BLACK;
}

function set(data: Uint8ClampedArray, x: number, y: number, value: COLOR) {
  const pixel = (y * 512 + x) * 4;
  const color = value === WHITE ? 255 : 0;
  data[pixel] = color;
  data[pixel + 1] = color;
  data[pixel + 2] = color;
  data[pixel + 3] = 255;
}

function drawImage(ctx: CanvasRenderingContext2D, memory: ScreenMemory) {
  const image = assertExists(
    ctx.getImageData(0, 0, 512, 256),
    "Failed to create Context2d",
  );
  for (let col = 0; col < 512; col++) {
    for (let row = 0; row < 256; row++) {
      const color = get(memory, col, row);
      set(image.data, col, row, color);
    }
  }
  ctx.putImageData(image, 0, 0);
}

export type ScreenScales = 0 | 1 | 2;

export const Screen = ({
  memory,
  showScaleControls = false,
  scale = 1,
  onScale,
}: {
  memory: ScreenMemory;
  showScaleControls?: boolean;
  scale?: ScreenScales;
  onScale?: (scale: ScreenScales) => void;
}) => {
  const canvas = useRef<HTMLCanvasElement>();
  const [screenScale, setScreenScale] = useState<ScreenScales>(scale);

  const onScaleCB = (scale: ScreenScales) => {
    onScale?.(scale);
    setScreenScale(scale);
  };

  const draw = useCallback(() => {
    const ctx =
      canvas.current?.getContext("2d", { willReadFrequently: true }) ??
      undefined;

    if (ctx) {
      drawImage(ctx, memory);
    }
  }, [memory]);

  const ctxRef = useCallback(
    (ref: HTMLCanvasElement | null) => {
      canvas.current = ref ?? undefined;
      draw();
    },
    [canvas, draw],
  );

  useClockFrame(draw);
  useClockReset(() => {
    canvas.current
      ?.getContext("2d")
      ?.clearRect(0, 0, canvas.current.width, canvas.current.height);
  });

  return (
    <article className="panel">
      <header>
        <div>Screen</div>
        {showScaleControls && (
          <fieldset role="group">
            <button
              aria-current={screenScale === 0}
              onClick={() => onScaleCB(0)}
            >
              x0
            </button>
            <button
              aria-current={screenScale === 1}
              onClick={() => onScaleCB(1)}
            >
              x1
            </button>
            <button
              aria-current={screenScale === 2}
              onClick={() => onScaleCB(2)}
            >
              x2
            </button>
          </fieldset>
        )}
      </header>
      {screenScale > 0 && (
        <main style={{ backgroundColor: "var(--code-background-color)" }}>
          <figure
            style={{
              width: `${512 * screenScale}px`,
              height: `${256 * screenScale}px`,
              boxSizing: "content-box",
              marginInline: "auto",
              margin: "auto",
              borderTop: "2px solid gray",
              borderLeft: "2px solid gray",
              borderBottom: "2px solid lightgray",
              borderRight: "2px solid lightgray",
            }}
          >
            <canvas
              ref={ctxRef}
              width={512}
              height={256}
              style={{
                transform: `translate(-50%, -50%) scale(${screenScale}) translate(50%, 50%)`,
                imageRendering: "pixelated",
              }}
            ></canvas>
          </figure>
        </main>
      )}
    </article>
  );
};
