/* ************************************************************************** */
/*                                                                            */
/*                                                        :::      ::::::::   */
/*   multiOnlineGamePage.js                             :+:      :+:    :+:   */
/*                                                    +:+ +:+         +:+     */
/*   By: edbernar <edbernar@student.42angouleme.    +#+  +:+       +#+        */
/*                                                +#+#+#+#+#+   +#+           */
/*   Created: 2024/08/18 00:53:53 by edbernar          #+#    #+#             */
/*   Updated: 2024/11/19 18:25:40 by edbernar         ###   ########.fr       */
/*                                                                            */
/* ************************************************************************** */

import { availableSkins } from '/static/javascript/lobbyPage/3d.js';
import { VRButton } from "/static/javascript/three/examples/jsm/webxr/VRButton.js"
import { Opponent } from '/static/javascript/multiOnlineGame/Opponent.js'
import * as THREE from '/static/javascript/three/build/three.module.js'
import { Player, isOnPointAnim } from '/static/javascript/multiOnlineGame/Player.js'
import { pageRenderer, isMobile, isOnChrome } from '/static/javascript/main.js'
import { Ball } from '/static/javascript/multiOnlineGame/Ball.js'
import { Map } from '/static/javascript/multiOnlineGame/Map.js'
import { sendRequest } from "/static/javascript/websocket.js";
import { files } from '/static/javascript/filesLoader.js';

let 	scene				= null;
let 	map					= null;
let 	ball				= null;
let		renderer			= null;
let		player				= null;
let		spotLight			= null;
let 	ambiantLight		= null;
let 	opponent			= null;
let		interval			= null;
let		intervalPing		= null;
let 	debug				= false;
let		lastPingTime		= 0;
let		lastFpsTime			= 0;
let 	lastFpsDisplayed	= 0;
let		lastFpsArr			= [60];
let		VrButton			= null;
let		isInVrMode			= false;

const	observer = new MutationObserver((mutationsList) => {
	mutationsList.forEach((mutation) => {
		if (VrButton.innerText == 'VR NOT SUPPORTED')
			document.getElementById('newButtonVr').style.display = 'none';
		if (mutation.attributeName === 'style')
			VrButton.style.display = 'none';
	});
});

class MultiOnlineGamePage
{
	static create(skin)
	{
		if (!skin)
		{
			skin = {player: 4, opponent: 0};
			availableSkins[4].texture = files.skinOneTexture;
			availableSkins[5].texture = files.skinTwoTexture;
			availableSkins[6].texture = files.skinThreeTexture;
			availableSkins[7].texture = files.skinFourTexture;
		}
		const bar1		= createBarPlayer(availableSkins[skin.player]);
		const bar2		= createBarPlayer(availableSkins[skin.opponent]);

		document.body.setAttribute('style', '');
		scene					= new THREE.Scene()
		map						= new Map(scene, 13, false, skin.pfp, skin.pfpOpponent);
		renderer				= new THREE.WebGLRenderer({antialias: true});
		renderer.shadowMap.enabled = true;
		renderer.shadowMap.type = THREE.PCFSoftShadowMap;
		renderer.domElement.style.animation = 'fadeOutStartGames 1s';
		renderer.domElement.style.filter = 'brightness(1)';
		
		vrMode();
		opponent				= new Opponent(bar2, map, Math.floor(Math.random() * 100 % 6));
		player					= new Player(bar1, map, opponent, Math.floor(Math.random() * 100 % 6), skin.opponentGoaldId);
		spotLight				= new THREE.SpotLight(0xffffff, 10000, 0, 0.2);
		spotLight.castShadow	= true;
		ambiantLight			= new THREE.AmbientLight(0xffffff, 0.5);
		ball					= new Ball(scene, map);

		window.addEventListener('resize', windowUpdater);
		scene.add(player.object);
		scene.add(opponent.object);
		scene.add(ambiantLight);
		spotLight.position.set(0, 100, 0);
		scene.add(spotLight);
		scene.background = new THREE.Color(0x1a1a1a);
		renderer.setSize(window.innerWidth, window.innerHeight);
		document.body.appendChild(renderer.domElement);
		renderer.domElement.setAttribute('id', 'canvasMultiGameOnline');
		map.ballObject = ball.object;
		if (isMobile)
			player.mobileMode();
		document.addEventListener('keypress', (e) => {
			if (e.key == 'g')
			{
				player.pointAnimation(map);
				map.animationGoal(ball.object.position.x, ball.object.position.y, ball.object.position.z, player.playerGoalAnimation);
			}
			if (e.key == 'h')
			{
				player.pointOpponentAnimation(map, opponent.object);
				map.animationGoal(ball.object.position.x, ball.object.position.y, ball.object.position.z, opponent.playerGoalAnimation);
			}
			if (e.key == 'c')
				debug = !debug;
		})

		renderer.setAnimationLoop(loop)
		sendRequest('game', {action: 1});
		if (!isMobile && isOnChrome)
			map.putVideoOnCanvas(2, 3);
		let lastPosition = player.object.position.x;
		let lastUp = player.isUp;
		interval = setInterval(() => {
			if (player && player.object.position.x != lastPosition || player.isUp != lastUp)
			{
				lastPosition = player.object.position.x;
				lastUp = player.isUp;
				sendRequest('game', {action: 3, pos: player.object.position.x, up: player.isUp});
			}
		}, 1000 / 20);
		intervalPing = setInterval(() => {
			if (!lastPingTime)
			{
				sendRequest('game', {action: 4});
				lastPingTime = Date.now();
			}
		}, 800);
	}

	static dispose()
	{
		const session = renderer.xr.getSession();

		if (session)
			session.end();
		observer.disconnect();
		map.putVideoOnCanvas(0, null);
		VrButton = null;
		window.removeEventListener('resize', windowUpdater);
		if (interval)
			clearInterval(interval);
		interval = null;
		if (intervalPing)
			clearInterval(intervalPing);
		lastPingTime = 0;
		intervalPing = null;
		if (renderer)
			renderer.dispose();
		renderer = null;
		if (map)
			map.dispose();
		map = null;
		if (ball)
			ball.dispose();
		ball = null;
		if (player)
			player.dispose();
		player = null;
		if (opponent)
			opponent.dispose();
		opponent = null;
		if (scene)
		{
			scene.children.forEach(child => {
				if (child.geometry)
					child.geometry.dispose();
				if (child.material && child.material.dispose)
					child.material.dispose();
				if (child.texture)
					child.texture.dispose();
				scene.remove(child);
			});
		}
		scene = null;
	}

	static opponentDisconnect()
	{
		pageRenderer.changePage('lobbyPage');
	}

	static ping()
	{
		const	text	=	document.getElementById('ping');
		const	ping	=	Date.now() - lastPingTime;

		if (ping < 90)
			text.style.color = 'white';
		else if (ping >= 90 && ping < 150)
			text.style.color = 'orange';
		else
			text.style.color = 'red';
		text.innerText = ping + ' ms';
		lastPingTime = null;
	}

	static endGame(content)
	{
		const	endGameDiv		=	document.getElementById('endGameDiv');
		const	scoreText		=	document.getElementById('endGameText');
		const	endGameScore	=	document.getElementById('endGameScore');
		const	simpleText		=	document.getElementById('endGameSimpleText');
		let		intervalEnd		=	null;
		let		time			=	4;

		if (renderer && renderer.xr && renderer.xr.getSession())
			renderer.xr.getSession().end();
		if (!map)
			return ;
		if (map && map.score)
			endGameScore.innerText = `${map.score.player} - ${map.score.opponent}`;
		if (content.won)
			scoreText.innerText = "You won !"
		endGameDiv.style.display = 'flex';
		intervalEnd = setInterval(() => {
			if (content.opponentLeft)
				simpleText.innerText = `Your opponent has given up...\nYou will be redirected to the ` + (content.tournamentCode ?  "tournament" : "lobby") + ` in ${time} seconds`
			else
				simpleText.innerText = `You will be redirected to the ` + (content.tournamentCode ?  "tournament" : "lobby") + ` in ${time} seconds`
			time--;
			if (time == -1)
			{
				clearInterval(intervalEnd);
				if (content.tournamentCode)
					setTimeout(() => pageRenderer.changePage('tournamentPage', false, content.tournamentCode), 500);
				else
					setTimeout(() => pageRenderer.changePage('lobbyPage'), 500);
			}
		}, 1000);
	}
}

function createBarPlayer(skin)
{
	const	geometry		= new THREE.BoxGeometry(1, 0.1, 0.1);
	let		material		= null;

	if (skin.color)
		material = new THREE.MeshPhysicalMaterial({color: skin.color});
	else
	{
		if (typeof skin.texture !== 'object')
			material = new THREE.MeshPhysicalMaterial({map: new THREE.TextureLoader().load(skin.texture)});
		else
		{
			material = [
				new THREE.MeshPhysicalMaterial({map: new THREE.TextureLoader().load(skin.texture.left)}),
				new THREE.MeshPhysicalMaterial({map: new THREE.TextureLoader().load(skin.texture.right)}),
				new THREE.MeshPhysicalMaterial({map: new THREE.TextureLoader().load(skin.texture.top)}),
				new THREE.MeshPhysicalMaterial({map: new THREE.TextureLoader().load(skin.texture.bottom)}),
				new THREE.MeshPhysicalMaterial({map: new THREE.TextureLoader().load(skin.texture.front)}),
				new THREE.MeshPhysicalMaterial({map: new THREE.TextureLoader().load(skin.texture.back)}),
			]
		}
	}
	const mesh		= new THREE.Mesh(geometry, material);

	mesh.castShadow = true;
	return (mesh);
}

function windowUpdater()
{
	renderer.setSize(window.innerWidth, window.innerHeight);
	player.camera.aspect = window.innerWidth / window.innerHeight;
	player.camera.updateProjectionMatrix();
};

function loop()
{
	showFps();
	player.update();
	opponent.update();
	ball.update();
	map.update(ball);
	renderer.render(scene, player.camera);
}


function showFps()
{
	const	fps		=	document.getElementById('fps')
	const	now		=	Date.now();

	if (now > lastFpsDisplayed + 800)
	{
		fps.innerText = Math.round(lastFpsArr.reduce((a, b) => a + b, 0) / lastFpsArr.length) + ' fps';
		lastFpsDisplayed = now;
		lastFpsArr = [];
	}
	else
		lastFpsArr.push(Math.floor(1000 / (now - lastFpsTime)));
	lastFpsTime = now;
}

function vrMode()
{
	const	supportsXR	=	'xr' in window.navigator;
	const	newButton	=	configButton();

	if (!supportsXR)
		return ;
	renderer.xr.enabled = true;
	document.body.appendChild( VRButton.createButton(renderer) );
	VrButton = document.getElementById('VRButton');
	observer.observe(VrButton, { attributes: true });
	if (VrButton.innerText !== 'VR NOT SUPPORTED')
		document.body.append(newButton);
}

let xrReferenceSpace;

function configButton()
{
	const	newButton = document.createElement('button');
	const	cameraGroup	=	new THREE.Group();
	let		interval	=	null;

	cameraGroup.name = "vrHeadset";
	newButton.innerText = "Vr mode";
	newButton.setAttribute('id', 'newButtonVr');
	newButton.addEventListener('click', () => {
		VrButton.click();
		player.configureVrController();
		let interval2 = setInterval(() => {
			const xrSession = renderer.xr.getSession();
			if (!xrSession)
				return ;
			clearInterval(interval2);
			interval2 = null;
			xrSession.requestReferenceSpace('local').then((refSpace) => {
				xrReferenceSpace = refSpace;
			});
		}, 16);
		if (isOnPointAnim)
		{
			interval = setInterval(() => {
				if (isOnPointAnim)
					return ;
				scene.add(cameraGroup);
				scene.remove(player.camera);
				cameraGroup.add(player.camera);
				cameraGroup.position.set(0, 0.5, 7.5);
				clearInterval(interval);
			}, 10);
		}
		else
		{
			scene.add(cameraGroup);
			scene.remove(player.camera);
			cameraGroup.add(player.camera);
			cameraGroup.position.set(0, 0.5, 7.5);
		}
		isInVrMode = true;
	});
	return (newButton);
}


export { MultiOnlineGamePage, player, opponent, ball, map, scene, renderer, isInVrMode, createBarPlayer };