import { createSignal, onCleanup } from "solid-js";
import { Duration } from "./helpers";

export type SimConfig = {
  step(dt: Duration): void,   // The physics step
  render(): void,             // The pure re-rendering
  sync(): void, // The UI update (throttled)
  isPlaying: boolean,         // Should the simulation start immediately, or should it be paused?
  syncEveryFrames?: number,   // Default 10
  panicCap?: number,          // Default 300
}

const syncEveryFramesDefault: number = 10;
const panicCapDefault: number = 300;

// isPlaying == True:
// - the control of the rendering/stepping/syncing is in the hands of the simulation
// - client of the simulation has no control
// isPlaying == False:
// - the control of the rendering/stepping/syncing is in the hands of the client
//
// TODO: Perhaps you should create like two types for such objects, and think of them linearly...
// - one can become the other... but both can't really exist at once... Where are Linear Types when I need them?
export function useSimulation(config: SimConfig) {
  const [fps, setFps] = createSignal(0);
  const [isPlaying, setIsPlaying] = createSignal(config.isPlaying);

  const { step, render, sync } = config;
  const syncEveryFrames = config.syncEveryFrames ?? syncEveryFramesDefault;
  const panicCap = config.panicCap ?? panicCapDefault;

  let frameId: number = 0;
  let lastTime = performance.now();
  let frameCounter = 0;

  function loop(now: number) {
    frameCounter += 1;

    let dt = now - lastTime;
    lastTime = now;
    if (dt > panicCap) dt = panicCap;

    step(dt);
    render();

    if (frameCounter % syncEveryFrames === 0) {
      setFps(Math.round(1000 / dt));
      sync();
    }

    frameId = requestAnimationFrame(loop);
  }

  function togglePlay() {
    if (isPlaying()) {
      cancelAnimationFrame(frameId);
      setIsPlaying(false);
    } else {
      // Reset lastTime so we don't calculate a massive time jump from while it was paused
      lastTime = performance.now();
      frameId = requestAnimationFrame(loop);
      setIsPlaying(true);
    }
  }

  function requestSyncedRender() {
    if (!isPlaying()) {
      render();
      sync();
    }
  }

  function requestSimpleRender() {
    if (!isPlaying()) {
      render();
    }
  }

  if (isPlaying()) {
    frameId = requestAnimationFrame(loop);
  }

  onCleanup(() => {
    cancelAnimationFrame(frameId)
  });

  return { fps, isPlaying, togglePlay, requestSyncedRender, requestSimpleRender };
}
