import { AssetPreloaderService } from '../../../assetPreloaderService';
import { SimMain } from '../sim/SimMain';
import { VisLevel } from './VisLevel';
import { Vector2 } from '../util/Vector2';
import { InputHandler } from './input/InputHandler';
import { Hex } from '../util/Hex';
import { DefaultInputHandler } from './input/DefaultInputHandler';

export class VisMain {
	canvas: HTMLCanvasElement;
	context: CanvasRenderingContext2D;
	wallPattern: CanvasPattern;
	visLevel: VisLevel;
	simMain: SimMain;
	active: boolean = true;
	assets: AssetPreloaderService;
	wrapper: HTMLDivElement;
	inputHandlers: InputHandler[] = [];
	private startTimestamp: number = 0;
	private ready: boolean = false;
	private gap: number = 0;
	private animationFrameId: number | null = null;

	constructor(simMain: SimMain, assets: AssetPreloaderService, wrapper: HTMLDivElement, canvas: HTMLCanvasElement) {
		this.assets = assets;
		this.simMain = simMain;
		this.canvas = canvas;
		this.wrapper = wrapper;
		this.context = this.canvas.getContext('2d')!;
		this.context.globalCompositeOperation = 'source-over';
		this.wallPattern = this.createPattern(assets.getImage('images/wall.png'), 48);
		this.visLevel = new VisLevel(this, this.simMain, this.simMain.gdRoot, assets);
		this.setupInput();
		this.start();
		this.inputHandlers.push(new DefaultInputHandler(this));
	}

	private createPattern(image: HTMLImageElement, size: number): CanvasPattern {
		const tempCanvas = document.createElement('canvas');
		const tempContext = tempCanvas.getContext('2d')!;

		tempCanvas.width = size;
		tempCanvas.height = size;
		tempContext.drawImage(image, 0, 0, image.width, image.height, 0, 0, size, size);

		return this.context.createPattern(tempCanvas, 'repeat')!;
	}

	private step(timestamp: number) {
		if (!this.active) {
			return;
		}

		this.animationFrameId = requestAnimationFrame((timestamp: number) => {
			this.step(timestamp);
		});

		const simLevel = this.simMain.currentLevel;
		if (!simLevel) {
			return;
		}

		let targetStep = Math.floor(((timestamp - this.startTimestamp) * this.simMain.gdRoot.simulation.stepsPerSecond) / 1000 - this.gap);
		if (simLevel.paused) {
			this.gap += targetStep - this.simMain.currentStep;
		}
		this.simMain.executeUntilStep(targetStep);
		this.visLevel.updateEveryFrame(targetStep);
		this.onRender();
	}

	public onResized() {
		// Always get the latest wrapper size
		const width = this.wrapper.clientWidth;
		const height = this.wrapper.clientHeight;
		const ratio = window.devicePixelRatio;

		// Set canvas style to always fill the wrapper
		this.canvas.width = width * ratio;
		this.canvas.height = height * ratio;

		// Reset transform before scaling to avoid compounding
		this.context.setTransform(1, 0, 0, 1, 0, 0);
		this.context.scale(ratio, ratio);

		this.visLevel.updateSize();
	}

	onRender() {
		this.clear();
		const ctx = this.context;
		ctx.font = '12px Tahoma';
		const simLevel = this.simMain.currentLevel;
		if (!!simLevel) {
			if (!this.ready) {
				this.onResized();
				this.ready = true;
			}
			this.visLevel.draw();
		}
	}

	private clear() {
		this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
	}

	stop() {
		this.active = false;
		this.startTimestamp = 0;
		this.visLevel.updateEveryFrame(-1);
		if (this.animationFrameId !== null) {
			cancelAnimationFrame(this.animationFrameId);
			this.animationFrameId = null;
		}
	}

	start() {
		this.active = true;
		this.animationFrameId = requestAnimationFrame((timestamp: number) => {
			this.startTimestamp = timestamp - (this.simMain.currentStep / this.simMain.gdRoot.simulation.stepsPerSecond) * 1000;
			this.step(timestamp);
		});
	}

	private setupInput() {
		this.canvas.addEventListener('mousedown', (e) => this.handleMouseDown(e));
		this.canvas.addEventListener('mousemove', (e) => this.handleMouseMove(e));
		window.addEventListener('keydown', (e) => this.handleKeyDown(e));
		window.addEventListener('resize', (_) => this.onResized());
	}

	private getHexFromMouseCoords(event: MouseEvent): Hex {
		const rect = this.canvas.getBoundingClientRect();
		const ratio = this.canvas.width / rect.width;
		const x = ((event.clientX - rect.left) * ratio) / window.devicePixelRatio;
		const y = ((event.clientY - rect.top) * ratio) / window.devicePixelRatio;
		const coords = new Vector2(x, y);
		const hex = this.visLevel.getHexFromScreenCoords(coords);
		return hex;
	}

	private handleMouseDown(event: MouseEvent) {
		const hex = this.getHexFromMouseCoords(event);
		for (let i = this.inputHandlers.length - 1; i >= 0; i--) {
			const handled = this.inputHandlers[i].onMouseDown?.(event, hex);
			if (handled) {
				event.preventDefault();
				return;
			}
		}
	}

	private handleMouseMove(event: MouseEvent) {
		const hex = this.getHexFromMouseCoords(event);
		for (let i = this.inputHandlers.length - 1; i >= 0; i--) {
			const handled = this.inputHandlers[i].onMouseMove?.(event, hex);
			if (handled) {
				event.preventDefault();
				return;
			}
		}
	}

	handleKeyDown(event: KeyboardEvent) {
		for (let i = this.inputHandlers.length - 1; i >= 0; i--) {
			const handled = this.inputHandlers[i].onKeyDown?.(event);
			if (handled) {
				event.preventDefault();
				return;
			}
		}
	}
}
