import { Engine }	from "./framework/Engine.js";

import { Scene }	from "./framework/Scene.js";

import { Mesh }		from "./framework/Mesh.js";

import { OceanPipeline }	from "./pipelines/OceanPipeline.js";

import { OceanTests }	from "./tests/OceanTests.js";

import { events }	from "./events.js";


class OceanApp {

	static bitReverse( x, bits ) {

		let n = x;
		let r = 0;

		for ( let i = 0; i < bits; i++ ) {

			r = ( r << 1 ) | ( n & 1 );
			n = n >> 1;

		}

		return r;

	}


	async run( ) {

	const eventsHandler		= new events( );

	const domElements		= eventsHandler.getDomElements( );

	const statusElement		= domElements.statusElement;

	const canvas			= domElements.canvas;

	const waveHeightSlider	= domElements.waveHeightSlider;

	const waveHeightValue	= domElements.waveHeightValue;

	const wavelengthSlider	= domElements.wavelengthSlider;

	const wavelengthValue	= domElements.wavelengthValue;

	const resolutionSlider	= domElements.resolutionSlider;

	const resolutionValue	= domElements.resolutionValue;

	const tilingSlider		= domElements.tilingSlider;

	const tilingValue		= domElements.tilingValue;

	const wireframeToggle	= domElements.wireframeToggle;

	const pauseToggle		= domElements.pauseToggle;

	const shadingModeSelect	= domElements.shadingModeSelect;

	const stepButton		= domElements.stepButton;

	const dumpHeightButton	= domElements.dumpHeightButton;


	function resizeCanvasToDisplay( ) {

		const width		= window.innerWidth;
		const height	= window.innerHeight;

		if ( canvas.width !== width || canvas.height !== height ) {

			canvas.width	= width;
			canvas.height	= height;

		}

	}

	resizeCanvasToDisplay( );

	if ( !navigator.gpu ) {
		statusElement.textContent = "WebGPU not supported in this browser.";
		return;
	}

	statusElement.textContent = "Requesting WebGPU device…";

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

	const device			= await adapter.requestDevice( );

	const engine			= new Engine( device );

	const renderSystem		= engine.createRenderSystem( canvas );

	let scene				= new Scene( );

	let pipelines				= [];

	let currentMeshResolution	= 1024;

	let currentWavelengthScale	= 1.0;

	let currentTiling			= 1;


	function getPrimaryPipeline( ) {

		return pipelines.length > 0 ? pipelines[ 0 ] : null;

	}


	async function rebuildScene( ) {

		const previousPrimary		= getPrimaryPipeline( );

		statusElement.textContent	= "Rebuilding pipelines…";

		const newScene				= new Scene( );

		const meshRes				= currentMeshResolution;

		const tiling				= currentTiling;

		const pipeline				= new OceanPipeline( engine, canvas );

		pipeline.gridSize			= 64;
		pipeline.meshResolution		= meshRes;

		pipeline.memory.set( "canvasRef", canvas );

		await pipeline.create( );

		// restore camera from previous primary pipeline if available
		if ( previousPrimary && previousPrimary.camera && pipeline.camera ) {

			const prevCam	= previousPrimary.camera;
			const newCam	= pipeline.camera;

			newCam.yaw			= prevCam.yaw;
			newCam.pitch		= prevCam.pitch;
			newCam.distance		= prevCam.distance;
			newCam.fovRadians	= prevCam.fovRadians;
			newCam.near			= prevCam.near;
			newCam.far			= prevCam.far;

			if ( prevCam.target && newCam.target ) {

				newCam.target.x	= prevCam.target.x;
				newCam.target.y	= prevCam.target.y;
				newCam.target.z	= prevCam.target.z;

			}

			if ( prevCam.up && newCam.up ) {

				newCam.up.x		= prevCam.up.x;
				newCam.up.y		= prevCam.up.y;
				newCam.up.z		= prevCam.up.z;

			}

			if ( typeof newCam.update === "function" ) {

				newCam.update( );

			}

		}

		// global settings
		if ( waveHeightSlider && waveHeightValue ) {

			const hValue	= parseFloat( waveHeightSlider.value );

			if ( !isNaN( hValue ) ) {

				pipeline.setHeightScale( hValue );

				waveHeightValue.textContent	= hValue.toFixed( 0 );

			}

		}

		if ( wavelengthSlider && wavelengthValue && typeof pipeline.setWavelengthScale === "function" ) {

			const wValue	= parseFloat( wavelengthSlider.value );

			if ( !isNaN( wValue ) && wValue > 0 ) {

				pipeline.setWavelengthScale( wValue );

				wavelengthValue.textContent	= wValue.toFixed( 2 );

			}

		}

		if ( wireframeToggle ) {

			if ( wireframeToggle.checked ) {

				pipeline.setRenderMode( "wireframe" );

			} else {

				pipeline.setRenderMode( "solid" );

			}

		}

		if ( shadingModeSelect ) {

			pipeline.setShadingMode( shadingModeSelect.value || "lighting" );

		}

		if ( pauseToggle ) {

			pipeline.setPaused( pauseToggle.checked );

		}

		// tiling (number of tiles around center)
		pipeline.setTiling( tiling );

		const tileRange			= Math.max( 0, tiling - 1 );
		const tilesPerRow		= tileRange * 2 + 1;
		const tileCount			= tilesPerRow * tilesPerRow;

		const block				= pipeline.getBlockByName( "ocean" );

		if ( block ) {

			const skyPass		= block.getPass( "SkySphere" );

			if ( skyPass && skyPass.shader ) {

				const skyMesh	= new Mesh( );

				skyMesh.addShader( skyPass.shader );

				newScene.addMesh( skyMesh );

			}


			const renderPassName	= wireframeToggle && wireframeToggle.checked ? "RenderWire" : "RenderSolid";

			const renderPass		= block.getPass( renderPassName );

			if ( renderPass && renderPass.shader ) {

				const mesh	= new Mesh( );

				mesh.addShader( renderPass.shader );

				mesh.setInstanceCount( tileCount );

				newScene.addMesh( mesh );

			}

		}

		pipelines					= [ pipeline ];
		scene						= newScene;

		statusElement.textContent	= "Simulating ocean (drag to orbit, scroll to zoom)";

	}


	await rebuildScene( );

	eventsHandler.resizeCanvasToDisplay		= resizeCanvasToDisplay;
	eventsHandler.getPrimaryPipeline		= getPrimaryPipeline;
	eventsHandler.getPipelines				= function( ) { return pipelines; };
	eventsHandler.rebuildScene				= rebuildScene;
	eventsHandler.getScene					= function( ) { return scene; };
	eventsHandler.getRenderSystem			= function( ) { return renderSystem; };
	eventsHandler.setCurrentMeshResolution	= function( value ) { currentMeshResolution = value; };
	eventsHandler.setCurrentWavelengthScale	= function( value ) { currentWavelengthScale = value; };
	eventsHandler.setCurrentTiling			= function( value ) { currentTiling = value; };
	eventsHandler.setAutoRotate				= function( value ) { autoRotate = value; };

	eventsHandler.setup( );


	// expose FFT test helper
	const oceanTests	= new OceanTests( getPrimaryPipeline );

	window.testFft = async function( ) {

		return oceanTests.testFft( );

	};


	window.testSpectrum = async function( ) {

		return oceanTests.testSpectrum( );

	};

	let autoRotate = true;

	let lastAutoRotateTime = performance.now();


	async function frame( ) {

		const now			= performance.now();

		if ( autoRotate ) {

			const dt				= ( now - lastAutoRotateTime ) / 1000;

			const primary			= getPrimaryPipeline();

			if ( primary && primary.camera ) {

				const rotationSpeed	= 0.15;

				primary.camera.yaw	+= rotationSpeed * dt;

				primary.camera.update();

			}

		}

		lastAutoRotateTime	= now;


		const list = pipelines.slice( );

		const run = async function( ) {

			for ( const p of list ) {

				await p.bindBuffers( );
				await p.execute( );

			}

			if ( scene && renderSystem ) {

				renderSystem.render( scene );

			}

		};

		try {

			await run( );

			requestAnimationFrame( frame );

		} catch ( error ) {

			console.error( error );
			statusElement.textContent = "Error while drawing ocean.";

		}

	}

	requestAnimationFrame( frame );

}

}


const app	= new OceanApp( );

app.run( );
