/**
 * Soundeffects
 */
const sounds = {
	strike: new Audio("/static/sounds/strike.mp3"),
	alarm: new Audio("/static/sounds/alarm.mp3"),
};

/**
 * State
 */
const state = {
	// Segment display used for telling the remaining time.
	display: null,

	// Current game state.
	gamestate: null,

	// Estimated timeout date when the timer should be stopped.
	estimatedTimeout: new Date(),

	// Amount of strikes (wrong answers) the player has made.
	strikes: 0,

	// If the alarm has been played already
	alarmPlayed: false,

	// Number of mistakes allowed without exploding the bomb
	maxAllowedStrikes: 0,

	// Time on the timer at the beginning of a game
	gameDuration: 0
};

/**
 * Play a given audio file
 * @param {Audio} sound Audio object containing a sound.
 */
function playSound(sound) {
	sound.load();
	sound.play();
}

/**
 * Update the modules.
 * @param {Array} List of puzzles
 */
function updateModules(puzzles) {
	// Delete the modules when the game is inactive or in info mode.
	if (state.gamestate == "INACTIVE" || state.gamestate == "INFO") {
		document.getElementById("modules").innerHTML = "";
		return;
	}

	const modulesElement = document.getElementById("modules");

	// Update the modules.
	const modules = [];
	for (const puzzle of puzzles) {
		const moduleElement = document.createElement("div");
		moduleElement.textContent = puzzle.address;

		// State: Solved
		if (puzzle.solved === true) {
			moduleElement.className = "solved";
		}

		// State: Unsolved
		if (puzzle.solved === false) {
			moduleElement.className = "unsolved";
		}

		// State: Needy
		if (puzzle.solved === null) {
			moduleElement.className = "needy";
		}

		modules.push(moduleElement);
	}

	modulesElement.replaceChildren(...modules);
}

function updateStrikes() {
	const indicators = [];
	for (let idx = 0; idx < state.maxAllowedStrikes; idx++) {
		let indicator = document.createElement("span");
		indicator.classList.add('strikeIndicator');
		indicator.textContent = '!';
		if (idx < state.strikes) {
			indicator.classList.add('active');
		} else {
			indicator.classList.add('inactive');
		}
		indicators.push(indicator);
	}
	document.getElementById("strikeIndicatorContainer").replaceChildren(...indicators);
}

/**
 * Initialize the game state.
 */
function initializeGameState() {
	updateGameState();
	setInterval(updateGameState, 500);
}

/**
 * Update the game state.
 */
function updateGameState() {
	fetch("/status.json")
		.then((res) => res.json())
		.then((data) => {
			// Update the game state
			state.gamestate = data.gamestate;
			state.gameDuration = data.game_duration;
			state.maxAllowedStrikes = data.max_allowed_strikes;
			document.getElementById("gamestate").innerHTML = state.gamestate;

			// Reset the strike amount if the game is not running anymore.
			if (state.gamestate === "INACTIVE") {
				state.strikes = 0;
				state.alarmPlayed = false;
			}

			if (state.gamestate === "GAME") {
				// Update the estimated timeout date
				state.estimatedTimeout = new Date(
					new Date().getTime() + data.timeleft * 1000
				);

				// Calculate the total amount of strikes for all puzzles.
				const newStrikes = data.puzzles
					.map((p) => p.strikes)
					.reduce((a, b) => a + b, 0);

				// Play a "buzzer" sound when a strike is made and update total amount of strikes.
				if (state.strikes < newStrikes) {
					playSound(sounds.strike);
					state.strikes = newStrikes;
				}

				// Play a "alarm" sound when the time is at 20 seconds.
				if (data.timeleft <= 20 && data.timeleft > 19 && !state.alarmPlayed) {
					playSound(sounds.alarm);
					state.alarmPlayed = true;
				}
			}

			// Update the start/restart button visibility.
			const startButton = document.querySelector("#buttonStart");
			const restartButton = document.querySelector("#buttonRestart");
			startButton.disabled = state.gamestate !== "DISCOVER";
			restartButton.disabled = state.gamestate !== "GAMEOVER";

			// Update the modules
			updateModules(data.puzzles);
			updateStrikes();
		});
}

/**
 * When the start button is clicked.
 */
function onStartButtonClick() {
	fetch("/start");
}

/**
 * When the restart button is clicked.
 */
function onRestartButtonClick() {
	fetch("/restart");
}

/**
 * Initialize the segment display.
 */
function initializeSegmentDisplay() {
	display = new SegmentDisplay("display");
	display.pattern = "##:##.#";
	display.displayAngle = 0;
	display.digitHeight = 100;
	display.digitWidth = display.digitHeight / 2;
	display.digitDistance = display.digitHeight / 10;
	display.segmentWidth = display.digitHeight / 10;
	display.segmentDistance = display.digitHeight / 100;
	display.segmentCount = 7;
	display.cornerType = 1;
	display.colorOn = "#ff0000";
	display.colorOff = "#ff000022";
	display.draw();

	// Update the segment
	setInterval(updateSegmentDisplay, 100);
}

function setTimeleft(timeLeft) {
	// Set the display to 0 when there is no time left.
	if (isNaN(timeLeft) || timeLeft <= 0) {
		display.setValue("00:00.0");
		return;
	}
	const integral = Math.floor(timeLeft);
	const fractional = timeLeft - integral;
	const minutes = Math.floor(integral / 60);
	const seconds = integral % 60;

	const minutesStr = String(minutes).padStart(2, "0");
	const secondsStr = String(seconds).padStart(2, "0");
	const fractionalStr = String(Math.floor(fractional * 10)).padStart(1, "0");

	display.setValue(`${minutesStr}:${secondsStr}.${fractionalStr}`);
}

/**
 * Update the segment display with the latest game data.
 */
function updateSegmentDisplay() {
	console.log(state.gamestate);
	if (state.gamestate === "INACTIVE" || state.gamestate === "INFO" || state.gamestate === "DISCOVER") {
		setTimeleft(state.gameDuration);
	}
	// Do not update the timer when the game is not running.
	else if (state.gamestate != "GAME" || !state.estimatedTimeout) {
		return;
	} else {
		setTimeleft((state.estimatedTimeout - new Date()) / 1000);
	}
}

/**
 * When the window is loaded.
 */
window.addEventListener("load", () => {
	initializeGameState();
	initializeSegmentDisplay();
});
