/*
 * Copyright (C) 2021 Thomas Van Acker
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 *
 * This project is hosted on https://git.bitscuit.be/bitscuit/ReverseQuiz
 *
*/

// Imports
const http = require("http");
const url = require("url");
const fs = require("fs");
const util = require("util");
const fetch = require("node-fetch");

// Vars
const hostname = "192.168.0.128"; // Enter your local IP address here
const port = 3000; // Enter a port number here

const STATE_LOBBY = 0;
const STATE_MANUAL = 10;
const STATE_WRITE = 20;
const STATE_PICK = 30;
const STATE_ANSWER = 40;
const STATE_END = 50;
var state = STATE_LOBBY;

var userSockets = [];
var userNames = [];
var userOK = [];

var questionNr = 0; // Current question
var questionsTotal = 1; // Total nr of questions
var questionData = {};
var questionCorrectAnswerIndex;
var answers;
const QUESTION_CATEGORIES = [9,15,16,17,18,19,20,22,23,27,28,30];

var userWrite = [];
var userPick = [];
var userPoints = [];



// Create HTTP server
const server = http.createServer((req, res) => {
	// Parse url
	const requrl = url.parse(req.url);
	var reqpath = requrl.pathname;
	if(reqpath == "/") reqpath = "/index.html";

	// Respond to requests
	try{
		// Return requested page
		res.statusCode = 200;
		res.write(fs.readFileSync("html"+reqpath));
		res.end();

	}catch(error){
		// 404 if page not found
		res.statusCode = 404;
		res.setHeader("Content-Type", "text/plain");
		res.end("404 - Page Not Found");
	}
});


// Create socket.io server
const io = require("socket.io")(server); // Can only be done when server is created

io.on("connection", (socket) => {
	console.log("New user connected!");
	
	// Init socket listeners
	
	// Disconnect
	socket.on("disconnect", () => {
		console.log("User disconnected!");

		// Remove from user lists
		if(userSockets.indexOf(socket) != -1){
			// When in users list
			name = userNames[userSockets.indexOf(socket)];

			userNames.splice(userSockets.indexOf(socket), 1);
			userSockets.splice(userSockets.indexOf(socket), 1);

			if(state == STATE_LOBBY){
				// Send new lobby
				userSockets.forEach(function(s){s.emit("LOBBY", userNames);});
			}else{
				// Send disconnect message
				userSockets.forEach(function(s){s.emit("DISCONNECT", name);});
			}
		}
	});

	// JOIN
	socket.on("JOIN", (username) => {
		console.log("JOIN request for "+username);

		// Check if can enter in lobby
		if(state == STATE_LOBBY){
			// Add user to lists
			userSockets.push(socket);
			userNames.push(username);
			console.log(userNames);

			// Send lobby data
			userSockets.forEach(function(s){s.emit("LOBBY", userNames);});

		}else{
			// When can't enter
			socket.emit("LOBBY_CLOSED");
		}
	});

	// START
	socket.on("START", () => {
		console.log("START request");

		// Send START to all users
		userSockets.forEach(function(s){s.emit("START");});
		
		// Set state
		state = STATE_MANUAL;
		
		// Reset vars
		questionNr = 0;
		userPoints = [];
		for(i=0; i<userSockets.length; i++){
			userPoints.push(0);
		}

		// Send game data
		var dataUser = {usernames:userNames, questionsTotal:questionsTotal};
		for(i=0; i<userSockets.length; i++){
			userSockets[i].emit("START", dataUser);
		}

		console.log("Game data sent!");

		// Reset OK
		userOK = [];
		for(i=0; i<userSockets.length; i++){
			userOK[i] = false;
		}
	});

	// OK
	socket.on("OK", () => {
		ok(socket);
	});


	// WRITE
	socket.on("WRITE", (data) => {
		// When received write from player
		
		// Check if empty
		if(data.write == ""){
			socket.emit("WRITE_REPLY", {error:"Please enter a false answer!"});
			return;
		}

		// Check if valid
		var valid = true;
		valid = valid && data.write.toUpperCase() != questionData.correct_answer.toUpperCase();
		for(i=0; i<questionData.incorrect_answers.length; i++){
			valid = valid && data.write.toUpperCase() != questionData.incorrect_answers[i].toUpperCase();
		}
		for(i=0; i<userWrite.length; i++){
			valid = valid && data.write.toUpperCase() != userWrite[i].toUpperCase();
		}

		// Send error message if invalid
		if(!valid){
			socket.emit("WRITE_REPLY", {error:"This answer already exists!"});
			return;
		}
		
		// Add to list
		userWrite[userSockets.indexOf(socket)] = data.write;

		// Perform ok
		ok(socket);
	});


	// PICK
	socket.on("PICK", (id) => {
		// When received pick form player
		userPick[userSockets.indexOf(socket)] = id;

		// Perform ok
		ok(socket);
	});



});


// Start HTTP server
server.listen(port, hostname, () => {
	console.log("Server running at http://"+hostname+":"+port);
});



// Game methods
function next(){
	// Go to next step of game
	
	// Send LOADING
	userSockets.forEach(function(s){s.emit("LOADING");});
	
	
	if(state == STATE_MANUAL || state == STATE_ANSWER){
		// Write
		state = STATE_WRITE;

		// Update vars
		questionNr++;
		userWrite = [];
		for(i=0; i<userSockets.length; i++){
			userWrite.push("");
		}
		userPick = [];
		for(i=0; i<userSockets.length; i++){
			userPick.push("");
		}

		// Stop if got all questions
		if(questionNr > questionsTotal){
			// End
			state = STATE_END;

			// Gather data
			var sorted = [];
			for(i=0; i<userNames.length; i++){
				sorted.push({name:userNames[i], score:userPoints[i]});
			}
			sorted.sort((a,b) => b.score-a.score);
			console.log(sorted);

			var ranks = [];
			var r = 1;
			var ls = sorted[0].score;
			for(i=0; i<sorted.length; i++){
				if(sorted[i].score < ls){
					r = i+1;
					ls = sorted[i].score;
				}
				ranks.push({name:sorted[i].name, score:sorted[i].score, rank:r});
			}
			console.log(ranks);

			// Send data
			var data = {ranks:ranks};
			userSockets.forEach(function(s){s.emit("STOP", data);});

			// Reset vars
			state = STATE_LOBBY;
			userSockets = [];
			userNames = [];


		}else{
			// Load new question
			console.log("Loading new question...");
			var category = QUESTION_CATEGORIES[rand(0, QUESTION_CATEGORIES.length)];
			fetch("https://opentdb.com/api.php?amount=1&type=multiple&category="+category)
				.then(res => res.json()) // Convert to JSON
				.then(json => {
					// Question loaded
					console.log("Question loaded!");
					console.log(json);

					// TODO: check response code

					// Save data
					questionData = json.results[0];

					// Send question to players
					userSockets.forEach(function(s){s.emit("WRITE", {questionNr:questionNr, questionsTotal:questionsTotal, question:questionData.question});});
				});
		}

		
	}else if(state == STATE_WRITE){
		// Pick
		state = STATE_PICK;
		console.log("Preparing PICK");

		// Send question and answers to players
		answers = [];
		for(i=0; i<questionData.incorrect_answers.length; i++){
			answers.splice(rand(0, answers.length+1), 0, questionData.incorrect_answers[i]);
		}
		for(i=0; i<userWrite.length; i++){
			answers.splice(rand(0, answers.length+1), 0, userWrite[i]);
		}
		questionCorrectAnswerIndex = rand(0, answers.length+1);
		answers.splice(questionCorrectAnswerIndex, 0, questionData.correct_answer);
		console.log(answers);

		// Send
		var data = {questionNr:questionNr, questionsTotal:questionsTotal, question:questionData.question, answers:answers};
		userSockets.forEach(function(s){s.emit("PICK", data);});
		console.log(data);

		

	}else if(state == STATE_PICK){
		// Answer
		state = STATE_ANSWER;
		console.log("Preparing ANSWER");

		// Update points
		var pointsDiff = [];
		for(i=0; i<userSockets.length; i++){
			pointsDiff.push(0);
		}
		for(i=0; i<userSockets.length; i++){
			// Check if correct
			if(userPick[i] == questionCorrectAnswerIndex){
				userPoints[i] += 3;
				pointsDiff[i] += 3;
			}

			// Check writes
			for(j=0; j<userWrite.length; j++){
				if(answers[userPick[i]].toUpperCase() == userWrite[j].toUpperCase() && i != j){
					userPoints[j] += 1;
					pointsDiff[j] += 1;
				}
			}
		}

		// Send data to players
		var data = {questionNr:questionNr, questionsTotal:questionsTotal, question:questionData.question, answers:answers, correctAnswer:questionCorrectAnswerIndex, userPick:userPick, userWrite:userWrite, userPoints:userPoints, pointsDiff:pointsDiff, usernames:userNames};

		userSockets.forEach(function(s){s.emit("ANSWER", data);});
		console.log(data);

	}
}


function ok(socket){
	// Set OK for this player
	userOK[userSockets.indexOf(socket)] = true;
	
	// Check if everybody OK
	var OK = true;
	for(i=0; i<userOK.length; i++){
		OK = OK && userOK[i];
	}
	
	// Send OK data
	var waitingFor = [];
	for(i=0; i<userOK.length; i++){
		if(!userOK[i]) waitingFor.push(userNames[i]);
	}
	var dataOK = {users:waitingFor};
	userSockets.forEach(function(s){s.emit("OK_REPLY", dataOK)});
	
	// Perform next step if OK
	if(OK){

		// Reset OK
		userOK = [];
		for(i=0; i<userSockets.length; i++){
			userOK[i] = false;
		}

		// Next step
		next();
	}
}


// Helper methods
function rand(min, max){
	return Math.floor(Math.random()*(max-min) + min);
}




























