import BrainfuckLanguageEngine from "./brainfuck/engine";
import ExecutionController from "./execution-controller";
import SampleLanguageEngine from "./sample-lang/engine";
import { StepExecutionResult } from "./types";
import { WorkerRequestData, WorkerResponseData } from "./worker-constants";

let _controller: ExecutionController<any> | null = null;

/** Create a worker response for state update */
const stateMessage = <RS>(
  state: "empty" | "ready"
): WorkerResponseData<RS> => ({
  type: "state",
  data: state,
});

/** Create a worker response for execution result */
const resultMessage = <RS>(
  result: StepExecutionResult<RS>
): WorkerResponseData<RS> => ({
  type: "result",
  data: result,
});

/**
 * Initialize the execution controller.
 */
const initController = () => {
  // const engine = new SampleLanguageEngine();
  const engine = new BrainfuckLanguageEngine();
  _controller = new ExecutionController(engine);
  postMessage(stateMessage("empty"));
};

/**
 * Reset the state of the controller and engine, to
 * prepare for execution of a new program.
 */
const resetController = () => {
  _controller!.resetState();
  postMessage(stateMessage("empty"));
};

/**
 * Load program code into the engine.
 * @param code Code content of the program
 */
const prepare = ({ code, input }: { code: string; input: string }) => {
  _controller!.prepare(code, input);
  postMessage(stateMessage("ready"));
};

/**
 * Execute the entire program loaded on engine,
 * and return result of execution.
 */
const execute = (interval?: number) => {
  console.info(`Executing at interval ${interval}`);
  _controller!.executeAll({
    interval,
    onResult: (res) => postMessage(resultMessage(res)),
  });
};

addEventListener("message", (ev: MessageEvent<WorkerRequestData>) => {
  if (ev.data.type === "Init") return initController();
  if (ev.data.type === "Reset") return resetController();
  if (ev.data.type === "Prepare") return prepare(ev.data.params);
  if (ev.data.type === "Execute") return execute(ev.data.params.interval);
  throw new Error("Invalid worker message type");
});