import Shader 				from "../../framework/WebGpu.js";

import Matrix4 				from "../../framework/Matrix4.js";

import Vector3 				from "../../framework/Vector3.js";

import Camera 				from "../../framework/Camera.js";

import EventManager 		from "../../framework/eventManager.js";

import ShaderInpector 		from "../../framework/ShaderInpector.js";

import WasmModule 			from "./wasm_particles.js";



export class ParticleSimulation {

	canvas;

	device;

	camera;

	eventManager 			= new EventManager();

	frameCount 				= 0;

	wasm;

	framebufferPtr;

	framebufferUint8;

	frameBufferTexture;

	fbSampler;

	width;

	height;

	renderShader;

	time 					= 0;

	lastTime 				= 0;

	lastFrameTime;
	

	setCanvas ( canvas ) {

		this.canvas = canvas;

		this.eventManager.setCanvas( canvas );

	}

	createPlane ( width, height, repeatU, repeatV ) {

		const vertices 		= new Float32Array( new Array(
			-width / 2, -height / 2, 0,
			 width / 2, -height / 2, 0,
			-width / 2,  height / 2, 0,
			 width / 2, -height / 2, 0,
			 width / 2,  height / 2, 0,
			-width / 2,  height / 2, 0
		) );

		const uvs 			= new Float32Array( new Array(
			0,         0,
			repeatU,   0,
			0,         repeatV,
			repeatU,   0,
			repeatU,   repeatV,
			0,         repeatV
		) );

		const result 		= {
			vertices:	vertices,
			normals:	null,
			uvs:		uvs
		};

		return result;

	}

	async setup ( offscreenCanvas, width, height ) {

		this.fpsElement 		= document.getElementById( "fps" );

		this.width 				= width;

		this.height 			= height;

		offscreenCanvas.width 	= width;

		offscreenCanvas.height 	= height;

		this.canvas 			= offscreenCanvas;

		const context 			= offscreenCanvas.getContext( "webgpu" );

		this.camera 			= new Camera( new Array( 0, 0, 1115 ), new Array( 0, -0.3, 0 ), new Array( 0, 1, 0 ) );

		this.eventManager.setup( offscreenCanvas, this.camera );

		const adapter 			= await navigator.gpu.requestAdapter();

		if ( adapter === null ) {

			throw new Error( "Failed to get GPU adapter" );

		}

		this.device 			= await adapter.requestDevice();

		this.wasm 				= await WasmModule();

		this.wasm._init_particles();

		this.particleCount 		= this.wasm._get_particle_count();

		this.particlePtr 		= this.wasm._get_particles_ptr();


		const presentationFormat = navigator.gpu.getPreferredCanvasFormat();

		context.configure({
			device: 		this.device,
			format: 		presentationFormat,
			alphaMode: 		"opaque"
		});

		this.particleSize 			= 8 * 4;

		this.particleBufferSize 	= this.particleCount * this.particleSize;

		this.renderShader 			= new Shader( this.device );

		this.renderShader.setCanvas( this.canvas );

		this.particleFloatArray = new Float32Array(
			this.wasm.HEAPU8.buffer,
			this.wasm._get_particles_ptr(),
			this.particleCount * 8
		);

		await this.renderShader.setup( "../../shaders/simplePoint.wgsl" );

		this.renderShader.setVariable("particles", this.particleFloatArray);

		this.lastFrameTime 		= performance.now();

		this.bindRenderLoop();
	}



	updateTimeDelta () {

		const now 				= performance.now();

		this.deltaTimeValue 	= ( now - this.lastFrameTime ) / 1000;

		this.time 				+= this.deltaTimeValue;

		this.lastFrameTime 		= now;

		this.frameCount++;

		if ( now - this.lastTime >= 1000 ) {

			const fps 		= this.frameCount / ( ( now - this.lastTime ) / 1000 );

			const fpsText 	= "FPS: " + fps.toFixed( 1 );

			this.fpsElement.textContent = fpsText;

			this.frameCount = 0;

			this.lastTime 	= now;

		}

	}

	async render () {

		this.updateTimeDelta();

		this.wasm._update_particles( this.deltaTimeValue );

		let buffer = this.renderShader.buffers.get( "particles" );

		this.device.queue.writeBuffer(
			buffer,
			0,
			this.particleFloatArray.buffer,
			this.particleFloatArray.byteOffset,
			this.particleFloatArray.byteLength
		);

		//this.renderShader.setVariable("particles", this.particleFloatArray);


		const viewMatrix 				= this.camera.getViewMatrix();

		const projectionMatrix 			= Matrix4.createProjectionMatrix( this.camera, this.canvas );

		const viewProjectionMatrix 		= Matrix4.multiply( projectionMatrix, viewMatrix );

		const cameraWorldMatrix 		= Matrix4.invert( viewMatrix );

		const cameraPosition 			= Matrix4.getColumn( cameraWorldMatrix, 3 );

		const cameraRight				= Matrix4.getColumn( cameraWorldMatrix, 0 );

		const cameraUp					= Matrix4.getColumn( cameraWorldMatrix, 1 );


		this.renderShader.setVariable( "cameraRight", cameraRight );

		this.renderShader.setVariable( "cameraUp",    cameraUp );

		this.renderShader.setVariable("viewProjectionMatrix", viewProjectionMatrix);

		this.renderShader.renderToCanvas( 6, this.particleCount, 0 ); // 6 vertices per quad

		//this.fxaaShader.renderToCanvas(6, 1, 0); 

	}

	renderTaa() {

		const prevIndex = this.currentHistoryIndex;
		const nextIndex = 1 - prevIndex;

		// Set framebuffer into history
		this.device.queue.writeTexture(
			{ texture: this.historyTextures[nextIndex] },
			this.framebufferUint8,
			{ bytesPerRow: this.width * 4 },
			{ width: this.width, height: this.height, depthOrArrayLayers: 1 }
		);

		// Apply TAA blend
		this.taaShader.setVariable("currentFrame", this.historyTextures[nextIndex]);
		this.taaShader.setVariable("historyFrame", this.historyTextures[prevIndex]);

		this.taaShader.renderToCanvas(6, 1, 0);

		// Swap history
		this.currentHistoryIndex = nextIndex;

	}


	async setupTaa() {

		this.taaShader = new Shader(this.device);

		this.taaShader.setCanvas(this.canvas);

		const quad = this.createPlane(2, 2, 1, 1);

		this.taaShader.setAttribute("position", quad.vertices);

		this.taaShader.setAttribute("uv", quad.uvs);

		this.taaShader.topology = "triangle-list";

		await this.taaShader.setup("../../shaders/taa.wgsl");

		this.taaShader.setVariable("mySampler", this.fbSampler);

		this.taaShader.setVariable("blendAmount", new Float32Array([0.1])); // you can tweak this

		this.historyTextures = [
			this.frameBufferTexture,
			this.device.createTexture({
				size: [this.width, this.height, 1],
				format: "rgba8unorm",
				usage:
					GPUTextureUsage.COPY_DST |
					GPUTextureUsage.TEXTURE_BINDING |
					GPUTextureUsage.RENDER_ATTACHMENT
			})
		];

		this.currentHistoryIndex = 0;

	}


	bindRenderLoop () {

		requestAnimationFrame( this.loop.bind( this ) );

	}



	async loop () {

		await this.render();

		this.bindRenderLoop();

	}
}
