
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";


export class ParticleSimulation {

	canvas;

	device;

	camera;

	useLocalSort		= true;

	eventManager		= new EventManager();

	frameCount 			= 0;

	setCanvas( canvas ) {

		this.canvas = canvas;

		this.eventManager.setCanvas( canvas );

	}



	createTextureFromImageBitmap( device, imageBitmap ) {

		const texture = device.createTexture( {

			size: [ imageBitmap.width, imageBitmap.height, 1 ],

			format: 'rgba8unorm',

			usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT

		} );

		device.queue.copyExternalImageToTexture(

			{ source: imageBitmap },

			{ texture: texture },

			[ imageBitmap.width, imageBitmap.height, 1 ]

		);

		return texture;

	}

	async loadImageBitmap(url) {

		const response = await fetch( url );

		const blob = await response.blob();

		const imageBitmap = await createImageBitmap( blob );

		return imageBitmap;
	}


	async loadTexture( url ) {

		const imageBitmap 	= await this.loadImageBitmap( url );

		const texture 		= this.createTextureFromImageBitmap( this.device, imageBitmap );

		return texture;

	}

 createPlane(width, height, repeatU, repeatV) {

	const vertices   = new Float32Array( 18 );  // 6 vertices (2 triangles) * 3 coords
	const normals    = new Float32Array( 18 );  // same count as vertices
	const uvs        = new Float32Array( 12 );  // 6 vertices * 2 coords

	// Positions (two triangles forming a plane on XY plane at z=0)
	// Large plane from (-width/2, -height/2) to (width/2, height/2)
	vertices.set([
		-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
	]);

	// Normals all pointing +Z
	for (let i = 0; i < 6; i++) {
		normals[i * 3 + 0] = 0;
		normals[i * 3 + 1] = 0;
		normals[i * 3 + 2] = 1;
	}

	// UVs scaled by repeatU, repeatV to repeat texture over the plane
	uvs.set([
		0,         0,
		repeatU,   0,
		0,         repeatV,

		0,         repeatV,
		repeatU,   0,
		repeatU,   repeatV
	]);

	return { vertices, normals, uvs };

}

async  getParticlePositionsAndIndicesFromDiv(div, canvasSize = 256) {

const rect = div.getBoundingClientRect();
console.log(rect.width, rect.height);


		const divWidth = rect.width;
		const divHeight = rect.height;


   const canvas = await html2canvas(div, {
    backgroundColor: null,
    width: divWidth,
    height: divHeight,
    scale: .6,
    useCORS: true
  });

  const ctx = canvas.getContext("2d");
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height );
  const data = imageData.data;
console.log(imageData);
  const positions = [];

const samplesPerPixel = 8;
const colors = [];

for (let y = 0; y < canvas.height; y++) {
	for (let x = 0; x < canvas.width; x++) {

		const i = (y * canvas.width + x) * 4;

			const r = data[i + 0];
			const g = data[i + 1];
			const b = data[i + 2];
			const a = data[i + 3];


		if (a > 10) {
			for (let s = 0; s < samplesPerPixel; s++) {

				const u = (x + Math.random()) / (canvas.width - 1);
				const v = (y + Math.random()) / (canvas.height - 1);

				const clipX = u * 2 - 1;
				const clipY = 1 - v * 2;

				positions.push(clipX, clipY, 0, 1);
				colors.push(r / 255, g / 255, b / 255, a / 255);
			}
		}
	}
}


console.log("positions", positions.length);

  const positionsArray = new Float32Array(positions);

  const indexCount = positionsArray.length / 4;
  const indices = new Uint32Array(indexCount);

  for (let i = 0; i < indexCount; i++) {
    indices[i] = i;
  }

  return {
    positions: positionsArray,
    indices: indices,
	colors: new Float32Array(colors)
  };
}

	async setup( offscreenCanvas, width, height ) {

		offscreenCanvas.width	= width;

		offscreenCanvas.height	= height;

		this.canvas				= offscreenCanvas;

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

		this.camera 			= new Camera( [0, 0, 1115], [0, -.3, 0], [0, 1, 0] );

		this.camera.distance			= 2

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


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

		if ( !adapter ) {

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

		}

		const presentationFormat = navigator.gpu.getPreferredCanvasFormat();

		this.device 			= await adapter.requestDevice();

		

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


		//var model				= await this.loadJSON("demo.json");

		//var mesh				= model.meshes[0];
	var div = document.querySelector("#particleSource");

		
		const { positions, indices, colors } =  await this.getParticlePositionsAndIndicesFromDiv( div );


		const rect = div.getBoundingClientRect();

		const divWidth = rect.width;
		const divHeight = rect.height;

		const aspectRatio = divWidth / divHeight;



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




		await this.initiateParticlesShader.setup( "../../shaders/initiateParticles.wgsl");

		//this.initiateParticlesShader.setVariable( "aspectRatio", aspectRatio );

		this.initiateParticlesShader.setVariable( "initiationPositions", positions );

		this.initiateParticlesShader.setVariable( "positions", positions );



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

		await this.simpleGravityShader.setup( "../../shaders/simpleGravity.wgsl");

		this.simpleGravityShader.setBuffer( "velocities", this.initiateParticlesShader.getBuffer("velocities") );

		this.simpleGravityShader.setBuffer( "positions", this.initiateParticlesShader.getBuffer("positions") );


		this.simpleGravityShader.setVariable( "aspectRatio", aspectRatio );

		//this.simpleGravityShader.setVariable( "gravity", [0, -7, 0] );
		this.simpleGravityShader.setVariable( "hoverRadius", .2 );




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

		this.renderShader.setCanvas( this.canvas );

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

	

		console.log("vertices", positions);


		this.vertexCount = positions.length / 4;


		//this.renderShader.setAttribute( "normal", mesh.normals );

		this.renderShader.setAttribute( "indices",indices );

		this.renderShader.setCanvas( this.canvas );

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

		await this.renderShader.setup( "../../shaders/particle-header.wgsl");

		this.renderShader.setVariable( "hoverRadius", .2 );

		this.renderShader.setBuffer( "positions", this.simpleGravityShader.getBuffer("positions") );

		//this.renderShader.setVariable( "positions", positions );

		this.renderShader.setVariable( "colors", colors );
		
		this.renderShader.setVariable( "aspectRatio", aspectRatio );

		
		div.addEventListener("mousemove", function(event) {
		  const rect = div.getBoundingClientRect();

		  const mouseX = event.clientX - rect.left;
		  const mouseY = event.clientY - rect.top;

		  const aspectRatio = rect.width / rect.height;

		  // Normalize to 0..1
		  const normX = mouseX / rect.width;
		  const normY = mouseY / rect.height;

		  // Map to clip space coordinates
		  const clipX = normX * 2 * aspectRatio - aspectRatio;  // -aspectRatio to +aspectRatio
		  const clipY = 1 - normY * 2;                          // +1 to -1 (top to bottom)

		  // Update uniform buffer for mousePos
		  this.renderShader.setVariable("mousePos", [clipX, clipY]);
		   this.simpleGravityShader.setVariable("mousePos", [clipX, clipY]);

		}.bind(this));


		var texture		= await this.loadTexture("./textures/defaultnouvs.png");

		const sampler 	= this.device.createSampler({
			minFilter: 'linear',
			magFilter: 'linear',
			mipmapFilter: 'linear',
			addressModeU: 'repeat',
			addressModeV: 'repeat',
		});

		//this.renderShader.setVariable( "mySampler", sampler );

		//this.renderShader.setVariable( "myTexture", texture );

		const workgroupSize = 64;

		const particleCount = positions.length / 4;
		this.dispatchCount = Math.ceil(particleCount / workgroupSize);

		if( this.dispatchCount * workgroupSize <= particleCount ) {

			console.error( "this.dispatchCount is to small", this.dispatchCount);

		}
		this.initiateParticlesShader.execute( this.dispatchCount  );

		this.render();

	}

	updateTimeDelta() {

		const now				= performance.now();

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

		this.lastFrameTime		= now;

	}

	async render() {

		this.updateTimeDelta();

		const viewMatrixData 			= this.camera.getViewMatrix();

		const projectionMatrixData 		= Matrix4.createProjectionMatrix( this.camera, this.canvas )

		const viewProjectionMatrix 		= Matrix4.multiply( projectionMatrixData, viewMatrixData );

		const cameraWorldMatrix 		= Matrix4.invert( viewMatrixData );  

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

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

		//this.renderShader.setVariable( "time", this.lastFrameTime*.001 );

		
		this.simpleGravityShader.setVariable("deltaTime", this.deltaTimeValue);

		this.simpleGravityShader.execute( this.dispatchCount );

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

		this.frameCount++;

		requestAnimationFrame( this.render.bind( this ) );

	}

	async loadJSON( pathName ) {

		const response = await fetch( pathName );

		if ( !response.ok ){

			throw new Error( `Failed to load shader: ${ pathName }` );

		}

		return await response.json();

	}

}
