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_renderer.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();

		const presentationFormat = navigator.gpu.getPreferredCanvasFormat();

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

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

		this.renderShader.setCanvas( this.canvas );

		this.wasm 				= await WasmModule();

		this.wasm._set_resolution( width, height );

		this.framebufferPtr 	= this.wasm._get_framebuffer_ptr();

		this.framebufferUint8 	= new Uint8Array(
			this.wasm.HEAPU8.buffer,
			this.framebufferPtr,
			width * height * 4
		);

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

		this.fbSampler 			= this.device.createSampler({
			magFilter: 		"nearest",
			minFilter: 		"nearest",
			addressModeU: 	"clamp-to-edge",
			addressModeV: 	"clamp-to-edge"
		});

		const plane 			= this.createPlane( 1800, 1800, 1, 1 );

		this.renderShader.setAttribute( "position", plane.vertices );

		this.renderShader.setAttribute( "uv", plane.uvs );

		this.vertexCount 		= plane.vertices.length / 3;

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

		await this.renderShader.setup( "../../shaders/triangle-list-texture-solid.wgsl" );

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

		this.renderShader.setVariable( "myTexture", this.frameBufferTexture );


		await this.setupFxaa();

		await this.setupTaa();


		this.lastFrameTime 		= performance.now();

		this.bindRenderLoop();


	}

	async setupFxaa() {

		this.fxaaShader = new Shader(this.device);
		
		this.fxaaShader.setCanvas(this.canvas);

		const fullscreenQuad = this.createPlane(2, 2, 1, 1); // covers NDC [-1,1]

		this.fxaaShader.setAttribute("position", fullscreenQuad.vertices);

		this.fxaaShader.setAttribute("uv", fullscreenQuad.uvs);

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

		await this.fxaaShader.setup("../../shaders/fxaa.wgsl");

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

		this.fxaaShader.setVariable("myTexture", this.frameBufferTexture);

		this.fxaaShader.setVariable("resolution", new Float32Array([this.width, this.height]));


	}

	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_framebuffer( this.time );

		this.device.queue.writeTexture(
			{ texture: this.frameBufferTexture },
			this.framebufferUint8,
			{
				bytesPerRow: this.width * 4
			},
			{
				width: this.width,
				height: this.height,
				depthOrArrayLayers: 1
			}
		);
/*
		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 );

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

		this.renderShader.renderToCanvas( this.vertexCount, 1, 0 );
*/


		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();

	}
}
