/*
 * 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/BoxEscape
 *
*/

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

// Vars
const hostname = "192.168.0.128";
const port = 3000;

const STATE_LOBBY = 0;
const STATE_GAME = 1;
var state = STATE_LOBBY;

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

const BOX_TYPE_TIP = 0;
const BOX_TYPE_KEY = 1;
const BOX_TYPE_BOMB = 2;
var boxes = []; // 2D array with box objects
var currentUserID = -1;
var guardID = 0;

// 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 LAODING to all users
		userSockets.forEach(function(s){s.emit("LOADING");});
		
		// Set state
		state = STATE_GAME;
		
		// Create game
		boxes = [];
		var usedCodes = [];
		for(y=0; y<6; y++){
			boxes[y] = [];
			for(x=0; x<6; x++){
				var code;
				do{
					code = rand(10, 100);
				}while(usedCodes.indexOf(code) != -1);
				usedCodes.push(code);

				boxes[y][x] = {type:BOX_TYPE_TIP, code:code, tip:""};
			}
		}

		// Pick random boxes for key
		var keyX = rand(0, 6);
		var keyY = rand(0, 6);
		var keyCode = boxes[keyY][keyX].code;
		boxes[keyY][keyX].type = BOX_TYPE_KEY;
		boxes[keyY][keyX].tip = "This is the key";

		// Pick random boxes for bombs
		var nrBombs = 4;
		var bombXs = [];
		var bombYs = [];
		for(i=0; i<nrBombs; i++){
			var bombX;
			var bombY;
			do{
				bombX = rand(0, 6);
				bombY = rand(0, 6);
			}while(boxes[bombY][bombX].type != BOX_TYPE_TIP);

			// Set to bomb
			boxes[bombY][bombX].type = BOX_TYPE_BOMB;
			boxes[bombY][bombX].tip = "This is a bomb";

			// Add to list
			bombXs.push(bombX);
			bombYs.push(bombY);
		}

		// Pick guard
		guardID = rand(0, userSockets.length);
		console.log("GuardID = "+guardID+" ("+userNames[guardID]+")");

		// Generate all possible tips
		var tips = [];
		
		//	Bombs located at
		for(i=0; i<nrBombs; i++){
			tips.push(boxes[bombYs[i]][bombXs[i]].code+" is a bomb");
		}

		//	x bombs in column
		for(x=0; x<6; x++){
			var n = 0;
			for(y=0; y<6; y++){
				if(boxes[y][x].type == BOX_TYPE_BOMB) n++;
			}
			var r = rand(0, 6);

			tips.push("There "+(n==1?"is":"are")+" "+n+" bomb"+(n==1?"":"s")+" in the same column as "+boxes[r][x].code);
		}

		//	x bombs in row
		for(y=0; y<6; y++){
			var n = 0;
			for(x=0; x<6; x++){
				if(boxes[y][x].type == BOX_TYPE_BOMB) n++;
			}
			var c = rand(0, 6);

			tips.push("There "+(n==1?"is":"are")+" "+n+" bomb"+(n==1?"":"s")+" in the same row as "+boxes[y][c].code);
		}

		//	Key code bigger or less than
		for(i=0; i<6; i++){
			var a = rand(33, 67);
			var t = "The box with the key is ";
			if(keyCode > a) t += "bigger than";
			else if(keyCode < a) t += "less than";
			else t += "equal to";
			t += " "+a;
			tips.push(t);
		}

		//	Key code odd or even
		tips.push("The key in the box with an "+(keyCode%2 == 0 ? "even" : "odd")+" number");

		//	Key not in same column as
		for(i=0; i<6; i++){
			var c;
			do{
				c = rand(0, 6);
			}while(c == keyX);
			var r = rand(0, 6);

			tips.push("The key is not in the same column as "+boxes[r][c].code);
		}

		//	Key not in same row as
		for(i=0; i<6; i++){
			var r;
			do{
				r = rand(0, 6);
			}while(r == keyY);
			var c = rand(0, 6);

			tips.push("The key is not in the same row as "+boxes[r][c].code);
		}

		//	Key not neighbour of
		for(i=0; i<6; i++){
			var x;
			var y;
			do{
				x = rand(0, 6);
				y = rand(0, 6);
			}while(Math.abs(keyX - x) <= 1 && Math.abs(keyY - y) <= 1);

			tips.push("The box with the key is not next to "+boxes[y][x].code);
		}


		//	Player is not guard
		for(i=0; i<userSockets.length; i++){
			if(i == guardID) continue;

			tips.push(userNames[i]+" is not the guard");
		}


		// Pick random tips for each tip box
		for(y=0; y<6; y++){
			for(x=0; x<6; x++){
				if(boxes[y][x].type == BOX_TYPE_TIP){
					var tipIndex = rand(0, tips.length);
					boxes[y][x].tip = tips[tipIndex];
					tips.splice(tipIndex, 1);
				}
			}
		}

		console.log("Boxes created:");
		console.log(boxes);

		
		// Reset vars
		currentUserID = -1; // Will increment on nextPlayer()


		// Send game data
		var dataUser = [];
		for(i=0; i<userSockets.length; i++){
			dataUser[i] = {guard:(i == guardID)};
		}

		for(i=0; i<userSockets.length; i++){
			userSockets[i].emit("START", dataUser[i]);
		}

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

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

	// OK
	socket.on("OK", () => {
		// 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){
			// Send LOADING
			userSockets.forEach(function(s){s.emit("LOADING");});

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

			// Next step
			nextPlayer();
		}
	});


	// PICK_REPLY
	socket.on("PICK_REPLY", (code) => {
		// When player picked box
		if(currentUserID != userSockets.indexOf(socket)) return;

		// Get box type
		var type = null;
		var x;
		var y;
		for(i=0; i<6; i++){
			for(j=0; j<6; j++){
				if(boxes[i][j].code == code){
					type = boxes[i][j].type;
					x = j;
					y = i;
					break;
				}
			}
			if(type != null) break;
		}

		// Check type
		if(type == BOX_TYPE_TIP || guardID == currentUserID){
			// Return tip to user
			var tip = boxes[y][x].tip;

			var data = {myTurn:false, currentUser:userNames[currentUserID], tip:""};
			for(i=0; i<userSockets.length; i++){
				data.myTurn = (i == currentUserID);
				data.tip = (i == currentUserID ? tip : "");

				userSockets[i].emit("TIP", data);
			}
			console.log("Sent tip: "+tip);
			
		}else if(type == BOX_TYPE_KEY){
			// Prisoners win
			var data = {win:true, board:boxes, reason:"The prisoners found the key to escape.", guard:userNames[guardID]};

			// Send data
			for(i=0; i<userSockets.length; i++){
				data.win = (i != guardID);

				userSockets[i].emit("STOP", data);
			}
			
			// Reset vars
			state = STATE_LOBBY;
			userSockets = [];
			userNames = [];
			
		}else if(type == BOX_TYPE_BOMB){
			// Guard wins
			var data = {win:false, board:boxes, reason:"The prisoners opened a box with a bomb.", guard:userNames[guardID]};

			// Send data
			for(i=0; i<userSockets.length; i++){
				data.win = (i == guardID);

				userSockets[i].emit("STOP", data);
			}
			
			// Reset vars
			state = STATE_LOBBY;
			userSockets = [];
			userNames = [];
		}
	});


	// TIP_REPLY
	socket.on("TIP_REPLY", () => {
		// When player pressed OK after tip
		if(currentUserID != userSockets.indexOf(socket)) return;

		// Next player
		nextPlayer();
	});

});


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



// Game methods
function nextPlayer(){
	// Increment currentPlayerID
	currentUserID = (currentUserID+1)%userSockets.length;
	console.log("Next player = "+currentUserID+" ("+userNames[currentUserID]+")");

	// Show board to every player and listen for one to pick a box
	var board = [];
	for(y=0; y<6; y++){
		board[y] = [];
		for(x=0; x<6; x++){
			board[y][x] = {type:boxes[y][x].type, code:boxes[y][x].code};
		}
	}
	var userData = {myTurn:false, board:board, currentUser:userNames[currentUserID]};

	// Send userData
	for(i=0; i<userSockets.length; i++){
		userData.myTurn = (i == currentUserID);
		userSockets[i].emit("PICK", userData);
	}
	console.log("PICK data sent");
}



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




























