<script setup>
  import { onMounted, watch } from "vue";
  import { globalStore } from "../stores/index.js";
  import {
    create1dState,
    create1dStateOneCell,
    create2dState,
    createBoard,
    conwayRules,
    overpopulationRules,
    lonelinessRules,
    threebornRules,
    highLifeRules,
    servietteRules,
    evolve2d,
  } from "../modules/automata.js";
  import { getRandomInt } from "../modules/common.js";
  import { boardToPic, picToBoard } from "../modules/picture.js";

  const store = globalStore();

  // TODO: Do we really need to declare a work canvas in this scope?
  // Do we really need to declare a canvas here at all?
  let canvas = null;
  let workCanvas = null;
  let workCtx = null;
  let ctx = null;
  const available2dRules = {
    conway: conwayRules,
    overpopulation: overpopulationRules,
    loneliness: lonelinessRules,
    threeborn: threebornRules,
    highlife: highLifeRules,
    serviette: servietteRules,
  };

  // used to determine the dimensions of the board
  const max = () => {
    return Math.max(store.boardWidth, store.boardHeight);
  };

  const selectedRules = () => {
    return available2dRules[store.selected2dRules.id];
  };

  watch(
    () => store.draw1d,
    (value) => {
      if (value == true) draw1d();
    }
  );

  watch(
    () => store.draw2d,
    (value) => {
      if (value == true) draw2dNew();
    }
  );

  watch(
    () => store.draw2dLast,
    async (value) => {
      if (value == true) await draw2dLast();
    }
  );

  watch(
    () => store.draw2dpicture,
    (value) => {
      if (value == true) draw2dPicture();
    }
  );

  watch(
    () => store.reset,
    (value) => {
      if (value == true) reset();
    }
  );

  onMounted(() => {
    canvas = Object.freeze(document.getElementById("board-canvas"));
    workCanvas = Object.freeze(document.getElementById("work-canvas"));
    ctx = canvas.getContext("2d", { willReadFrequently: true });
    workCtx = workCanvas.getContext("2d", {
      willReadFrequently: true,
    });
    store.canvasWidth = canvas.parentElement.clientWidth;
    store.canvasHeight = canvas.parentElement.clientHeight;
    store.setBoardWidth();
    store.setBoardHeight();
  });

  // draws the board on the canvas
  const drawCanvas = async (board, width, height) => {
    const d = store.cellProperties.size;
    // bool to RGBA colors
    const img = await boardToPic(board, width, height, store.cellProperties);
    // rescale and draw
    ctx.save();
    ctx.clearRect(0, 0, store.canvasWidth, store.canvasHeight);
    workCtx.putImageData(img, 0, 0);
    ctx.imageSmoothingEnabled = false;
    ctx.scale(d, d);
    ctx.drawImage(workCanvas, 0, 0, store.canvasWidth, store.canvasHeight);
    ctx.restore();
  };

  // create a first state, either a single living cell
  // at the center or random ones
  const compute1dInitialState = () => {
    if (store.initial1dState === "onecell")
      return create1dStateOneCell(store.boardWidth);
    return create1dState(store.boardWidth, getRandomInt, [0, 2]);
  };

  // initialize board with random cells
  const randomInitialState = () => {
    return create2dState(
      store.boardWidth,
      store.boardHeight,
      getRandomInt,
      [0, 2]
    );
  };

  // draw elementary automaton on the canvas based on selected ruleset
  const draw1d = () => {
    const initialState = compute1dInitialState();
    const board = createBoard(initialState, store.ruleset1d.rules, max());
    store.lastBoard = Object.freeze(board);
    // TODO: the board clearly could be an object
    drawCanvas(store.lastBoard, store.boardWidth, store.boardHeight);
    store.toggleStop();
  };

  // draw 2D automaton on the canvas in a loop
  const draw2d = (board) => {
    const newBoard = evolve2d(board, selectedRules());
    drawCanvas(newBoard, store.boardWidth, store.boardHeight);
    store.lastBoard = Object.freeze(newBoard);
  };

  // draw 2d automaton in a loop, starting from passed state
  const draw2dNext = async (board) => {
    setTimeout(() => {
      requestAnimationFrame(() => {
        if (!store.canDraw) return;
        draw2d(board);
        return draw2dNext(store.lastBoard);
      });
    }, store.refreshRate);
  };

  // draw 2d automaton from a new state
  const draw2dNew = async () => {
    if (!store.canDraw) return;
    const initialState = randomInitialState();
    const board = evolve2d(initialState, selectedRules());
    if (store.loop) return draw2dNext(board);
    else draw2d(board);
    store.toggleStop();
  };

  // draw 2d automaton from the last known generated board
  const draw2dLast = async () => {
    if (!store.canDraw) return;
    if (store.loop) return store.draw2dNext(store.lastBoard);
    else draw2d(store.lastBoard);
    store.toggleStop();
  };

  // draw 2d automaton from an uploaded picture.
  // use the picture representation as an initial state
  const draw2dPicture = () => {
    // draw image on canvas
    ctx.fillStyle = "black";
    ctx.fillRect(0, 0, store.canvasWidth, store.canvasHeight);
    ctx.drawImage(
      store.picture,
      Math.floor((store.canvasWidth - store.picture.width) / 2),
      Math.floor((store.canvasHeight - store.picture.height) / 2),
      store.picture.width,
      store.picture.height
    );

    // draw the image back on the work canvas with the dimensions of the board
    workCtx.drawImage(store.picture, 0, 0, store.boardWidth, store.boardHeight);

    // get the resized image data from work canvas
    const resized = workCtx.getImageData(
      0,
      0,
      store.boardWidth,
      store.boardHeight
    );

    // convert the image into a 2D board of boolean based on pixel value
    store.lastBoard = Object.freeze(
      picToBoard(resized.data, store.boardWidth, store.boardHeight)
    );

    store.toggleStop();
  };

  // stop drawing routines and clear the canvas
  const reset = () => {
    store.toggleStop();
    store.lastBoard = null;
    ctx.clearRect(0, 0, store.canvasWidth, store.canvasHeight);
    store.reset = false;
  };
</script>
<template>
  <canvas
    id="board-canvas"
    ref="board-canvas"
    :width="store.canvasWidth"
    :height="store.canvasHeight"
  />
  <canvas
    id="work-canvas"
    ref="work-canvas"
    :width="store.canvasWidth"
    :height="store.canvasHeight"
  />
</template>
<style>
  #canvas-board {
    flex: 1;
    margin: 0 auto;
  }
</style>
