Source: public/client.js

/**
 * @module Frontend 
 */
// buttons
const submitButton = document.getElementById('submitButton');
const settingsButton = document.getElementById('settingsButton');
const saveSettingsButton = document.getElementById('saveSettingsButton');

// input forms
const numbersForm = document.getElementById('numbers');
const countForm = document.getElementById('count');
const minValueForm = document.getElementById('minValue');
const maxValueForm = document.getElementById('maxValue');

// misc elements
const historyDiv = document.getElementById('historyDiv');
const numbersGroup = document.getElementById('numbersFormGroup');
const resultModal = document.getElementById('resultModal');

//labels
const leaderScore = document.getElementById('leaderScore');
const leaderTotal = document.getElementById('leaderTotal');
const resultText = document.getElementById('resultText');
const attemptsLabel = document.getElementById('attempts');
const leaderScoreNames = document.getElementById('leaderScoreNames');
const leaderTotalNames = document.getElementById('leaderTotalNames');

const usernameRegex = new RegExp("^[a-zA-Z0-9_-]{1,45}$");

// globals
var lock = false;
var currentId = "-1";
var reqLoc = new URL(window.location.href.split('#')[0] + "attempt"); // endpoint to process an attempt
var leaderLoc = new URL(window.location.href.split('#')[0] + "leaderboard");
var maxHistory = 50;

var gameConfig = {
	'count': 4,
	'minValue': 0,
	'maxValue': 7,
};


/**
 * Function to collect the numbers from the input fields.
 *
 * @returns {number[]}
 */

function getCurrentNumbers() {
	var forms = numbersGroup.getElementsByTagName('input');
	var ret = [];
	console.log(forms);
	for (x in forms) {
		if (isNaN(parseInt(forms[x].value)) != true)
			ret.push(parseInt(forms[x].value));
		else if (x < gameConfig['count'])
			return null;
	}
	return ret;
}

/**
 * Function to reload the game interface, based on current config.
 */

function reloadGame() {
	// clear the list
	while (numbersGroup.firstChild)
		numbersGroup.removeChild(numbersGroup.firstChild);
	// populate based on config
	for (var i = 0; i < gameConfig['count'] && i < 50; i++) {
		numbersGroup.appendChild(document.createElement("br"));
		var input = document.createElement("input");
		input.setAttribute("type", "text");
		input.setAttribute("id", "number" + i);
		input.setAttribute("class", "form-control");
		input.setAttribute("placeholder", "0");
		numbersGroup.appendChild(input);
	}
	attemptsLabel.innerHTML = "10 attempts remain.";
}

reloadGame();

/**
 * Function to add text to the history log.
 *
 * @param {string} str - The string to display in the log.
 */

function addToHistory(str) {
	// create history entry
	var li = document.createElement('li');
	var ul = historyDiv.getElementsByTagName('ul')[0];
	li.appendChild(document.createTextNode(str));

	// pop last element if we're over 20
	if (ul.getElementsByTagName("li").length > maxHistory)
		ul.removeChild(ul.lastChild);

	// add to history at top
	if (ul.firstChild) 
		ul.insertBefore(li, ul.firstChild);
	else
		ul.appendChild(li);
}

/**
 * Function to cause the alert modal to appear, with a message.
 *
 * @param {string} message - Message to be displayed.
 */

function alertModal(message) {
	alertText.innerHTML = message;
	document.getElementById('toggleAlert').click();
}

/**
 * Function to validate the user's current game settings.
 *
 * @param {{count: number, minValue: number, maxValue: number}} conf - The config object to be checked.
 * @returns {boolean}
 */

function validateConfig(conf) {
	var ret = true;
	if (conf['minValue'] < -1000000000 || conf['minValue'] > 1000000000 || conf['minValue'] >= conf['maxValue']) {
		minValueForm.className = minValueForm.className + " error";
		ret = false;
	}
	if (conf['maxValue'] < -1000000000 || conf['maxValue'] > 1000000000) {
		maxValueForm.className = maxValueForm.className + " error";
		ret = false;
	}
	if (conf['count'] > 50) {
		console.log(conf['count']);
		countForm.className = countForm.className + " error";
		ret = false;
	}
	if (ret == false) {
		//console.log("hiding settings");
		document.getElementById('showSettings').click();
	}
	
	return ret;
}

/**
 * Function to trigger the game result modal.
 *
 * @param {boolean} result - Game result.
 */


function finishGame(result) {
	resultText.innerHTML = result == true ? "You've won!" : "Game over. Try again!";
	document.getElementById('toggleResults').click();
}

/**
 * Function to submit the chosen numbers to our backend.
 *
 */

async function submitNums() {
	// lock our data while we're submitting
	lock = true;
	var attempt = getCurrentNumbers();

	// we have backend checks for this, but let's not allow them to submit incomplete guesses
	if (attempt == null) {
		alertModal("Invalid input. Please make sure you've filled out all the numbers.");
		lock = false;
		return ;
	}

	// validate their config -- we should do this in the modal before close
	if (validateConfig(gameConfig) == false) {
		alertModal("Your game settings are invalid. Please review them and make sure that their values are appropriate.");
		lock = false;
		return ;
	}

	var params = new URLSearchParams(attempt.map(n=>['input',n]));
	params.append('id', currentId);
	params.append('count', gameConfig.count);
	params.append('minValue', gameConfig.minValue);
	params.append('maxValue', gameConfig.maxValue);
	reqLoc.search = params;
	await fetch(reqLoc).then(async function (response) {
		var data = await response.json();
		console.log(data);

		// if we are on a different game, keep track of that
		console.log(data.id);
		if (currentId != data.id) {
			currentId = data.id;
			addToHistory("NEW GAME! Guess " + gameConfig['count'] + " numbers!");
		}
		var resultString = "";

		switch (data['status']) {
			case 'OK':
				resultString += "INPUT: " + data['attemptedNumber'] + " | RESULT: ";
				switch (data['matches']) {
					case 0:
						resultString += "NONE";
						break ;
					case gameConfig['count']:
						resultString += "SUCCESS";
						finishGame(true);
						break ;
					default:
						resultString += "PARTIAL (" + data['matches'] + ")";
						break ;
				}
				attemptsLabel.innerHTML = "" + data['tries'] + " attempts remain.";
				break ;
			case 'KO':
				resultString += "FAIL";
				attemptsLabel.innerHTML = "10 attempts remain.";
				finishGame(false);
				break ;
			case '?':
				alertModal("Invalid input! Please make sure you've filled out all the numbers.");
				break ;
			default:
				break ;

		}
		if (resultString != "")
			addToHistory(resultString);
		// ok we're done now, carry on
		lock = false;
	});
}

// listener for submissions
submitButton.addEventListener('click', function(e) {
	// prevent reload on button press
	e.preventDefault();

	// don't submit another request if we're still waiting on results
	if (lock == false)
		submitNums();
});

// listener for config changes
saveSettingsButton.addEventListener('click', function(e) {
	// prevent reload on button press
	e.preventDefault();

	newConf = {};

	// if they have valid inputs for these fields, update their config entry
	newConf['maxValue'] = isNaN(parseInt(maxValueForm.value)) != true ? parseInt(maxValueForm.value) : gameConfig['maxValue'];
	newConf['minValue'] = isNaN(parseInt(minValueForm.value)) != true ? parseInt(minValueForm.value) : gameConfig['minValue'];
	newConf['count'] = isNaN(parseInt(countForm.value)) != true ? parseInt(countForm.value) : gameConfig['count'];
	if (validateConfig(newConf) == true) {
		gameConfig = newConf;
		reloadGame();
	} else {
		alertModal("Settings invalid! Please double-check the indicated fields.");
	}
});

document.getElementById('alertClose').addEventListener('click', function(e) {
	if (document.getElementById('alertText').innerHTML == "Settings invalid! Please double-check the indicated fields.") {
		document.getElementById('showSettings').click();
	}
});

//load the leaderboards visually

document.getElementById('leaderboardLink').addEventListener('click', async function(e) {
	console.log("testing");
	while (leaderScore.firstChild) {
		leaderScore.removeChild(leaderScore.firstChild);
		leaderScoreNames.removeChild(leaderScoreNames.firstChild);
	}
	while (leaderTotal.firstChild) {
		leaderTotal.removeChild(leaderTotal.firstChild);
		leaderTotalNames.removeChild(leaderTotalNames.firstChild);
	}
	var response = await fetch(leaderLoc);
	console.log(response);
	console.log("testing2");
	await fetch(leaderLoc).then(async function (response) {
		var data = await response.json();
		console.log(data);
		for (x in data['per_game']) {
			var input = document.createElement("li");
			var score = document.createElement("li");
			input.innerHTML = data['per_game'][x].user;
			score.innerHTML = data['per_game'][x].score;
			leaderScore.appendChild(score);
			leaderScoreNames.appendChild(input);
		}
		for (x in data['total']) {
			var input = document.createElement("li");
			var score = document.createElement("li");
			input.innerHTML = data['total'][x].user;
			score.innerHTML = data['total'][x].score;
			leaderTotalNames.appendChild(input);
			leaderTotal.appendChild(score);
		}
		document.getElementById('toggleLeaderboard').click();
	});
});

document.getElementById('debugClick').addEventListener('click', function(e) {
	console.log(user);
});