import { 
	Group, PlaneGeometry, CylinderGeometry, SphereGeometry, RingGeometry,
	Mesh, MeshStandardMaterial, MeshBasicMaterial, DoubleSide, 
	AdditiveBlending
} from "three";

import { Text } from "troika-three-text";
import { EventManager } from "./EventManager";
import Utils from "#/app/Utils";
import { TextObject } from "@/components/InteractiveObjects/TextObject/TextObject";
class DashBoard extends EventManager {
	#points = 0;
	#attached = null;
	#textTimeout = { onKill: 'reject' };
	constructor(engine) {
        super();
		let levelProgress;
		let occupied = false;

		const dash = new Group(), hud = new Group(), hudTarget = new Group();
		this.object = dash;
		hud.visible = false;
		let hudAnimation, hudPlane, textPlane, sceneHeader;
		dash.add(hud);
		hud.add(hudTarget)
		hudTarget.position.set(0,0,0.52);
		dash.visible = false;
		dash.renderOrder = 1000;
		const k = 3.11;
		const dashWidth = engine.aspect * k, dashHeight = k;
		const dashDistance = -0.2;

		this.width = dashWidth;
		this.height = dashHeight;

		const dashGeometry = new PlaneGeometry(dashWidth, dashHeight);
		const dashMesh = new Mesh(dashGeometry, new MeshBasicMaterial({
			transparent: true,
			opacity: 0
		}))

		dash.add(dashMesh);
		engine.cameraRig.add(dash);

		const loadingPlane = new Mesh(
			new PlaneGeometry(dashWidth, dashHeight), 
			new MeshStandardMaterial({
				color:0xffffff,
				opacity:0, transparent:true,
				roughness:0, metalness:0.1
			})
		);
		const loadingProgress = new ProgressBar(engine);
		loadingProgress.object.scale.set(dashWidth*0.8, 0.05*dashHeight, 0.05*dashHeight)
		loadingProgress.object.position.set(-dashWidth/2 + dashWidth*0.1, 0, 0)
		loadingPlane.add(loadingProgress.object);
		dash.add(loadingPlane);

		this.ready = new Promise(async(resolve, reject)=>{
			let map = await engine.loadTexture('/static/textures/hud.png', '');
			hudPlane = new Mesh(
				new PlaneGeometry(dashWidth * 0.96, dashHeight * 0.9), 
				new MeshBasicMaterial({
					map,
					opacity: 0.37,
					transparent:true
				})
			);
			hudPlane.position.z = -0.07 * dashHeight;
			hudPlane.position.y = -0.011 * dashHeight;
			hud.add(hudPlane)
			engine.clickable.add(hudPlane, ()=>{
				engine.clickable.trigger(this.#attached.object)
			})

			textPlane = new Mesh(
				new PlaneGeometry(dashWidth * 0.86, 0.15 * dashHeight), 
				new MeshBasicMaterial({
					map,
					opacity: 0.52,
					transparent:true,
					depthTest: false,
				})
			);
			textPlane.position.z = -0.002;
			textPlane.position.y = -0.33 * dashHeight
			textPlane.visible = false;
			dash.add(textPlane)
			// fix #44
			//textPlane.material.depthTest = false;
			//hudPlane.material.depthTest = false;
			this.sceneHeader = sceneHeader = await new SceneHeader(engine, {dashWidth, dashHeight});
			dash.add(sceneHeader.object);
			resolve();
		});

		const text = new Text()
		Object.assign(text, {
			text:``, 
			fontSize: 0.033 * dashHeight, lineHeight: 1.33, maxWidth: dashWidth * 0.8,
			textAlign: 'center', font: '/static/fonts/Montserrat-Regular.ttf',
			anchorX: 'center', anchorY: 0.03 * dashHeight, depthOffset: 0.1, color: 0x000000,
			clipRect: [-dashWidth * 0.4, -0.12 * dashHeight, dashWidth * 0.4, 0]
		})
		text.sync();
		text.position.set(0, -0.27 * dashHeight, 0.003);
		text.renderOrder = 1001;
		text.material.depthTest = false;
		dash.add(text);

		const pointsText = new Text()
		Object.assign(pointsText, {
			text:``, fontSize: 0.044 * dashHeight, font: '/static/fonts/Montserrat-Bold.ttf',
			outlineColor: 0x113377, outlineWidth: '3%', anchorX: 'center',
		})
		pointsText.position.set(0.86 * dashWidth/2, 0.47 * dashHeight, -0.001);
		dash.add(pointsText);

		engine.renderer.xr.addEventListener('sessionstart', this.orient)

		this.orient = function(){
			let object = engine.camera, dd = dashDistance;
			if (engine.renderer.xr.isPresenting){
				dd = 1.55;
			}
			// dash.quaternion.copy(object.quaternion)
			dash.rotation.y = object.rotation.y;
			dash.position.copy(object.position)
			//dash.position.set(0,0,0);
			dash.translateZ(-dd -(dashHeight/2)/Math.tan(engine.camera.fov/2 * Math.PI/180) * engine.camera.zoom);
		}

		this.orient();

		this.pointer = new Mesh(
			new RingGeometry(dashHeight * 0.002, dashHeight * 0.005), 
			new MeshBasicMaterial( { color: 0x3377ff, depthTest: false, blending: AdditiveBlending } )
		);
		this.pointer.visible = false;
		this.pointer.renderOrder = 1010;
		const pointerWrapper = new Group();
		pointerWrapper.add(this.pointer);
		dash.add(pointerWrapper);

		this.initScene = function(scene, startBtnCallback){
			this.loading(0,0);
			sceneHeader.init(scene, startBtnCallback);
		}

		this.updateText = async function(t, params = {}){
			if (!textPlane) return;
			await Utils.killWait(this.#textTimeout);
			if (!!t) this.orient();
			textPlane.visible = !!t;
			engine.motionQueue.clear(text);
			text.text = t;
			text.anchorY = 0.03 * dashHeight;
			return new Promise((resolve, reject)=>{
				text.sync(async()=>{
					//console.log(text.clipRect);
					let dMax = text.textRenderInfo.blockBounds[3] - text.textRenderInfo.blockBounds[1];
					let dh = dMax + text.clipRect[1];
					if (params.startFrom){
						text.anchorY = text.anchorY + (-dh -text.anchorY) * params.startFrom;
					}
					if (dh > 0){
						engine.motionQueue.add({
							o: text,
							a: { anchorY: -dh },
							t: params.duration ? (params.duration * (1-params.startFrom||0)) : (dMax*77 / dashHeight),
							f: async ()=>{
								params.textScrolledCallback?.(true);
								params.textScrolledCallback = null;
								if (params.hideOnFinish){
									Utils.wait(5000, this.#textTimeout).then(_=>{
										this.updateText('')
									}).catch(_=>{})
								}else{
									engine.motionQueue.add({
										o: text,
										a: { anchorY: -dMax },
										t: 4,
										f: params.textFinishCallback
									})
								}
							}
						})
					}else{
						if (params.hideOnFinish){
							Utils.wait(10000, this.#textTimeout).then(_=>{
								this.updateText('')
								params.textScrolledCallback?.(false)
							}).catch(_=>{});
						}else{
							params.textScrolledCallback?.(false)
						}
					}
					resolve();
				})
			})
		}

		levelProgress = new ProgressBar(engine)
		dash.add(levelProgress.object);
		levelProgress.object.position.set(-dashWidth/2 + dashWidth/30, 0.45 * dashHeight, -0.001)
		levelProgress.object.scale.set(dashWidth/3, 0.02 * dashHeight, 0.02 * dashHeight)
		this.levelProgress = levelProgress;

		this.addPoints = function(p){
			this.#points += p;
			engine.motionQueue.add({
				o: pointsText,
				a: {rotation:{y: Math.PI}, scale:{x:1.5, y:1.5, z:1.5}},
				t: 0.25,
				f:()=>{
					pointsText.text = this.#points;
					pointsText.sync();
					engine.motionQueue.add({
						o: pointsText,
						a: {rotation:{y: Math.PI*2}, scale:{x:1, y:1, z:1}},
						t: 0.5,
						f: ()=>{pointsText.rotation.y = 0;}
					})
				}
			})
		}

		this.enable = ()=>{
			dash.visible = true;
		}

		this.disable = ()=>{
			dash.visible = false;
		}

		this.reset = ()=>{
			this.levelProgress.update(0);
			this.updateText('');
		}

		//dashPlacement, rotate = false, plane = false
		this.attach = (object, opts = {})=>{
			this.orient();
			this.cameraFix = engine.hero.cameraZ;
			engine.hero.cameraZ = 6;
			hud.visible = true;
			pointerWrapper.visible = false;
			hudPlane.visible = !!opts.plane;
			if (opts.plane){
				hudPlane.scale.set(0, .1, 1);
				hudPlane.material.opacity = 0.5;
				engine.motionQueue.add([{
					o:hudPlane, a:{material:{opacity:0.73}}, t:.4, d:.8
				},{
					o:hudPlane, a:{scale:{x:1}}, t:.4
				},{
					o:hudPlane, a:{scale:{y:1}}, t:.4, d:.4,
				}])
			}
			if (occupied) {
				//console.log('Sorry, occupied!')
				return false;
			}
			object._hud = {
				parent: object.parent,
				placement: {
					position: object.position.clone(),
					quaternion: object.quaternion.clone(),
					scale: object.scale.clone()
				}
			}
			hudTarget.attach(object);
			occupied = true;
			this.#attached = {object, opts};

			let result = new Promise((resolve, reject)=>{
				engine.motionQueue.add({
					o: object,
					a: opts.placement || { 
						quaternion: { x:0, y:0, z:0, w:0 },
						position: { x:0, y:0, z:0 },
						scale: { x: dashHeight * 0.8, y: dashHeight * 0.8, z: dashHeight * 0.8 }
					},
					t: opts.skipTransition ? 0 : 1,
					f: resolve
				})
			})

			if (opts.rotate){
				engine.motionQueue.add(hudAnimation = {
					o: hudTarget,
					a: { 
						rotation: { y: 2*Math.PI }
					},
					t: 8,
					r: true,
					d: 1
				})
			}
			return result;
		}

		this.detach = (object, opts = {})=>{
			engine.hero.cameraZ = this.cameraFix;
			pointerWrapper.visible = true;
			//console.log('detaching', object)
			engine.motionQueue.remove(hudAnimation);
			object._hud.parent?.attach(object);
			hud.rotation.y = 0;
			hud.visible = false;
			engine.motionQueue.add({
				o: object,
				a: object._hud.placement,
				t: opts.skipTransition ? 0 : 1
			});
			delete object._hud;
			occupied = false;
			hudAnimation = null;
		}

		this.loading = function(progress, tt){
			loadingPlane.visible = progress > 0 && progress < 1;
			loadingProgress.update(progress, tt)
			if (progress == 1){
				sceneHeader.show();
			}
		}
		this.loading(0,0);
	}

	get active(){
		return this.object.visible;
	}

	set active(v){
		this.object.visible = v;
	}

	get points(){
		return this.#points;
	}
}

class ProgressBar{
	constructor(engine, params = {}){
		this.object = new Group();
		const geometry = new CylinderGeometry( 0.5, 0.5, 1, 3, 1, false, 0, Math.PI ); 
		const staticCylinder = new Mesh( geometry, new MeshStandardMaterial({
			roughness: 0, metalness:0.1, transparent: true, opacity:0.52, color: 0x55ff00, side: DoubleSide
		}) ); 
		staticCylinder.rotation.set(-Math.PI/2, 0, Math.PI/2,)
		staticCylinder.position.x = 0.5;
		this.object.add( staticCylinder );

		const progressCylinder = new Mesh( geometry, new MeshStandardMaterial({
			roughness: 0, metalness:0.1, transparent: true, opacity:0.77, color: 0x11ff00
		}) ); 
		progressCylinder.rotation.set(Math.PI/2, 0, Math.PI/2,)
		this.object.add( progressCylinder );

		this.update = function(value, transitionTime = 0.5){
			staticCylinder.visible = progressCylinder.visible = value > 0;
			engine.motionQueue.clear(progressCylinder);
			engine.motionQueue.add({
				o: progressCylinder,
				a: {
					scale: {y: value},
					position: {x: value / 2}
				},
				t: transitionTime
			})
		}
		
		this.update(0)
	}
}

class SceneHeader{
	constructor(engine, params){
		return new Promise(async(resolve, reject)=>{
			this.object = new Group();
			let start = await engine.loadTexture('/static/textures/play.webp', '');
			let startBtn = new Mesh(
				new SphereGeometry(params.dashWidth * 0.11), 
				new MeshStandardMaterial({
					color: 0xffffff,
					roughnessMap: start,
					metalness: 1
				})
			)
			
			startBtn.position.y = 0.1 * params.dashHeight;
			startBtn.rotation.y = Math.PI/6;

			let title = await new TextObject(engine, {
				position: [0, -0.22 * params.dashHeight, -0.1], 
				fontSize: 0.055 * params.dashHeight, color: 0xffffff, outlineColor: 0x373737, outlineWidth: '3%'
			});
			let description = await new TextObject(engine, {
				position: [0, -0.25 * params.dashHeight, -0.1], anchorY: 'top', lineHeight: 1.33, color: 0x0,
				fontSize: 0.025 * params.dashHeight, outlineWidth: '11%', maxWidth: params.dashWidth * 0.8
			});
			
			[startBtn, title.object, description.object].forEach(o=>o.visible = false);
			
			this.object.add(startBtn);
			this.object.add(title.object);
			this.object.add(description.object);

			resolve(this);

			this.init = function(scene, startBtnCallback){
				[startBtn, title.object, description.object].forEach(o=>o.visible = false);
				title.object.text = scene.data.title || '';
				description.object.text = scene.data.description || '';
				engine.clickable.add(startBtn, ()=>{
					startBtnCallback();
					[startBtn, title.object, description.object].forEach(o=>o.visible = false);
					engine.motionQueue.clear(startBtn)
				});
			}
			this.show = function(){
				[startBtn, title.object, description.object].forEach(o=>o.visible = true);
				engine.motionQueue.add(
					{ o: startBtn, a:{
						scale: (k,s)=>s.setScalar(1+0.05*Math.sin(4*k*Math.PI))
					}, r: true, t: 3 },
				)
			}
		})
	}
}

export { DashBoard };