
import * as c from "./constants.js"
import { gc, matchOptions } from "./global.js"
import * as ev from "../shared_js/class/Event.js"
import * as en from "../shared_js/enums.js"
import { matchmaking, matchmakingComplete, startGame } from "./pong.js";
import { RacketClient } from "./class/RectangleClient.js";
import { repeatInput } from "./handleInput.js";
import { soundRoblox } from "./audio.js"
import { sleep } from "./utils.js";
import { Vector, VectorInteger } from "../shared_js/class/Vector.js";

class ClientInfo {
	id = "";
	side: en.PlayerSide;
	racket: RacketClient;
	opponent: RacketClient;
	opponentNextPos: VectorInteger;
}

const wsPort = 8042;
const wsUrl = "ws://" + document.location.hostname + ":" + wsPort + "/pong";
export let socket: WebSocket; /* TODO: A way to still use "const" not "let" ? */
export const clientInfo = new ClientInfo();

export function initWebSocket(options: en.MatchOptions)
{
	socket = new WebSocket(wsUrl, "json");
	socket.addEventListener("open", (event) => {
		socket.send(JSON.stringify( new ev.ClientAnnounce(en.ClientRole.player, options, clientInfo.id) ));
	});
	// socket.addEventListener("message", logListener); // for testing purpose
	socket.addEventListener("message", preMatchListener);
}

function logListener(this: WebSocket, event: MessageEvent) {
	console.log("%i: " + event.data, Date.now());
}

function preMatchListener(this: WebSocket, event: MessageEvent)
{
	const data: ev.ServerEvent = JSON.parse(event.data);
	switch (data.type) {
		case en.EventTypes.assignId:
			clientInfo.id = (<ev.EventAssignId>data).id;
			break;
		case en.EventTypes.matchmakingInProgress:
			matchmaking();
			break;
		case en.EventTypes.matchmakingComplete:
			clientInfo.side = (<ev.EventMatchmakingComplete>data).side;
			if (clientInfo.side === en.PlayerSide.left)
			{
				clientInfo.racket = gc.playerLeft;
				clientInfo.opponent = gc.playerRight;
			}
			else if (clientInfo.side === en.PlayerSide.right)
			{
				clientInfo.racket = gc.playerRight;
				clientInfo.opponent = gc.playerLeft;
			}
			clientInfo.opponentNextPos = new VectorInteger(clientInfo.opponent.pos.x, clientInfo.opponent.pos.y);
			clientInfo.racket.color = "darkgreen"; // for testing purpose
			socket.send(JSON.stringify( new ev.ClientEvent(en.EventTypes.clientPlayerReady) )); // TODO: set an interval/timeout to resend until matchStart response (in case of network problem)
			matchmakingComplete();
			break;
		case en.EventTypes.matchStart:
			socket.removeEventListener("message", preMatchListener);
			socket.addEventListener("message", inGameListener);
			startGame();
			break;
	}
}

function inGameListener(event: MessageEvent)
{
	const data: ev.ServerEvent = JSON.parse(event.data);
	switch (data.type) {
		case en.EventTypes.gameUpdate:
			// setTimeout(gameUpdate, 500, data as ev.EventGameUpdate); // artificial latency for testing purpose
			gameUpdate(data as ev.EventGameUpdate);
			break;
		case en.EventTypes.scoreUpdate:
			scoreUpdate(data as ev.EventScoreUpdate);
			break;
		case en.EventTypes.matchEnd:
			matchEnd(data as ev.EventMatchEnd);
			break;
	}
}

function gameUpdate(data: ev.EventGameUpdate)
{
	console.log("gameUpdate");

	if (matchOptions & en.MatchOptions.movingWalls) {
		gc.wallTop.pos.y = data.wallTop.y;
		gc.wallBottom.pos.y = data.wallBottom.y;
	}

	data.ballsArr.forEach((ball, i) => {
		gc.ballsArr[i].pos.assign(ball.x, ball.y);
		gc.ballsArr[i].dir.assign(ball.dirX, ball.dirY);
		gc.ballsArr[i].speed = ball.speed;
	});
	/* // Equivalent to
	gc.ballsArr.forEach((ball, i) => {
		ball.pos.assign(data.ballsArr[i].x, data.ballsArr[i].y);
		ball.dir.assign(data.ballsArr[i].dirX, data.ballsArr[i].dirY);
		ball.speed = data.ballsArr[i].speed;
	}); */

	const predictionPos = new VectorInteger(clientInfo.racket.pos.x, clientInfo.racket.pos.y); // debug

	if (clientInfo.side === en.PlayerSide.left) {
		clientInfo.racket.pos.assign(clientInfo.racket.pos.x, data.playerLeft.y);
	}
	else if (clientInfo.side === en.PlayerSide.right) {
		clientInfo.racket.pos.assign(clientInfo.racket.pos.x, data.playerRight.y);
	}

	// interpolation
	clientInfo.opponent.pos.assign(clientInfo.opponentNextPos.x, clientInfo.opponentNextPos.y);
	if (clientInfo.side === en.PlayerSide.left) {
		clientInfo.opponentNextPos.assign(clientInfo.opponent.pos.x, data.playerRight.y);
	}
	else if (clientInfo.side === en.PlayerSide.right) {
		clientInfo.opponentNextPos.assign(clientInfo.opponent.pos.x, data.playerLeft.y);
	}

	clientInfo.opponent.dir = new Vector(
		clientInfo.opponentNextPos.x - clientInfo.opponent.pos.x,
		clientInfo.opponentNextPos.y - clientInfo.opponent.pos.y
	);

	if (Math.abs(clientInfo.opponent.dir.x) + Math.abs(clientInfo.opponent.dir.y) !== 0) {
		clientInfo.opponent.dir = clientInfo.opponent.dir.normalized();
	}

	// server reconciliation
	repeatInput(data.lastInputId);

	// debug
	if (clientInfo.racket.pos.y > predictionPos.y + 1
	||  clientInfo.racket.pos.y < predictionPos.y - 1)
	{
		console.log(
			`Reconciliation error:
			server y:         ${data.playerLeft.y}
			reconciliation y: ${clientInfo.racket.pos.y}
			prediction y:     ${predictionPos.y}`
			);
	}
}

function scoreUpdate(data: ev.EventScoreUpdate)
{
	// console.log("scoreUpdate");
	if (clientInfo.side === en.PlayerSide.left && data.scoreRight > gc.scoreRight.value) {
		soundRoblox.play();
	}
	else if (clientInfo.side === en.PlayerSide.right && data.scoreLeft > gc.scoreLeft.value) {
		soundRoblox.play();
	}
	gc.scoreLeft.value = data.scoreLeft;
	gc.scoreRight.value = data.scoreRight;
}

function matchEnd(data: ev.EventMatchEnd)
{
	if (data.winner === clientInfo.side) {
		gc.text1.pos.assign(c.w*0.415, c.h_mid);
		gc.text1.text = "WIN";
	}
	else {
		gc.text1.pos.assign(c.w*0.383, c.h_mid);
		gc.text1.text = "LOSE";
	}
	// matchEnded = true;
}

// export let matchEnded = false;
