import { Command } from './command';
import { GameData } from './gameData';
import { GameRules } from './gameRules';
import { GameState } from './gameState';

export class SimulationEngine {
	interval: number = 50; // ms per step
	lastTime = 0;
	private frameRequestId: number | null = null;
	private data: GameData;
	private previousState: GameState | null = null;

	currentStep = 0;
	initialState: GameState;
	state: GameState;
	isRunning = false;
	lastStepTime: number = 0;

	constructor(
		initialState: GameState,
		private rules: typeof GameRules,
		initialData: GameData
	) {
		this.initialState = initialState;
		this.state = this.cloneState(this.initialState);
		this.data = initialData;
	}

	setGameData(newData: GameData) {
		this.data = newData;
	}

	reinitializeWithNewData(data: GameData) {
		this.setGameData(data);
		const rewound = JSON.parse(JSON.stringify(this.initialState));
		rewound.tick = this.state.tick;
		rewound.commandHistory = this.state.commandHistory.filter(
			(c) => c.step <= rewound.tick
		);
		this.state = rewound;
		this.fastForwardTo(this.currentStep);
	}

	fastForwardToEnd() {
		this.fastForwardTo(
			this.state.commandHistory[this.state.commandHistory.length - 1].step
		);
	}

	private fastForwardTo(target: number) {
		this.state.tick = 0;
		while (this.state.tick < target) {
			this.step();
		}
	}

	step() {
		this.previousState = this.cloneState(this.state);
		this.rules.step(this.state, this.data);
		this.currentStep = this.state.tick;
		this.lastStepTime = performance.now();
	}

	start() {
		if (this.isRunning) return;
		this.isRunning = true;
		this.lastTime = performance.now();
		this.loop();
	}

	stop() {
		this.isRunning = false;
		if (this.frameRequestId) {
			cancelAnimationFrame(this.frameRequestId);
			this.frameRequestId = null;
		}
	}

	rewindToZero() {
		this.stop();
		this.state = this.cloneState(this.initialState);
		this.previousState = this.cloneState(this.state);
		this.currentStep = 0;
	}

	issueCommand(command: Command) {
		// Purge future commands
		this.state.commandHistory = this.state.commandHistory.filter(
			(c) => c.step < command.step
		);
		this.state.commandHistory.push(command);
	}

	getRenderStates(): [GameState, GameState] {
		return [this.previousState ?? this.state, this.state];
	}

	cloneState(state: GameState): GameState {
		return {
			tick: state.tick,
			commandHistory: [...state.commandHistory],
			units: state.units.map((u) => ({ ...u })),
		};
	}

	private loop = () => {
		const now = performance.now();
		while (now - this.lastTime >= this.interval) {
			this.step();
			this.lastTime += this.interval;
		}
		if (this.isRunning) {
			this.frameRequestId = requestAnimationFrame(this.loop);
		}
	};
}
