/* 
	A Space Game Server version 0.1(ASGSv0_1)

	Main program

*/

#include <winsock2.h>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <iomanip>
#include <fstream>
#include <iostream>
#include <cstring>
#include "..\ASGcommon\MessageHandler.h"

#define SERVER_PORT_NO 10000
#define	ADMIN_PORT_NO 10001
#define MAX_BUF_SIZE 4096

using namespace std;

SOCKET theServer;
SOCKET theAdmin;
SOCKET *theGames;
sockaddr_in  addr;
static int playerCount = 0;
static int gamePoolStartingPort = 12000;

char theGameList[2500];

typedef struct _position{
	struct sockaddr playerAddress;
	char *name;
	int clientID;
	bool inUse;
}Position;

typedef struct _teams{
	Position position[2];
	char *name;
	int maxpositions;
}Teams;

typedef struct _games{
	int gport;
	SOCKET gsocket;
	int id;
	bool inUse;
	int numberofteams;
	Teams theTeam[2];
}Games;

Games the_game[25];

void InitializeAGameServer(Games *g){
	g->gsocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	cout<<g->gport<<endl;
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	addr.sin_port = htons(g->gport);

	//bind the socket to our server address
	bind(g->gsocket, (LPSOCKADDR)&addr, sizeof(struct sockaddr));
	cout<<"Game "<<g->id<<" is now listening on port "<<g->gport<<endl;
	// Make the socket listen
	listen(g->gsocket, 12);
}

char *CreateTeamPositionList(Games *g){
	int k = 1;
	char *theList = new char[MAX_BUF_SIZE];
	char temp[80];
	ZeroMemory(theList, MAX_BUF_SIZE);
	for (int i = 0; i < g->numberofteams; i++){
		for (int j = 0; j < g->theTeam[i].maxpositions; j++){
			if(!g->theTeam[i].position[j].inUse){
				ZeroMemory(temp, 80);
				if (j == 0){
					sprintf(temp, "%d: Team: %s (%d): Position: Pilot (1)\n", 
						k, g->theTeam[i].name, i+1);
				}
				else {
					sprintf(temp, "%d: Team: %s (%d): Position: Gunner (2)\n",
						k, g->theTeam[i].name, i+1);
				}
				strcat(theList, temp);
				k++;
			}
		}
	}
	return theList;
}

bool AllPositionsFilled(Games *g){
	int i, j;
	for (i = 0; i < g->numberofteams; i++){
		for (j = 0; j < g->theTeam[i].maxpositions; j++){
			if (!g->theTeam[i].position[j].inUse) return false;
		}
	}
	return true;
}

DWORD WINAPI GameListenerThread(void* g){ 
	Games *currentGame = (Games*)g;
	InitializeAGameServer(currentGame);
	cout<<"Game "<<currentGame->id<<" has been initialized"<<endl;	
	
	struct sockaddr from;
	int fromlen = sizeof( struct sockaddr_in );
	int type, clientid, team, pos, i, j;
	char buf[MAX_BUF_SIZE];
	char *token;
	char delims[] = " ";
	MessageHandler mh;
	Message m;
	bool startable = false;

	char* list;

	while(true){
		ZeroMemory(buf, MAX_BUF_SIZE);
		recvfrom(currentGame->gsocket, buf, MAX_BUF_SIZE, 0, &from, &fromlen);
		if(mh.parseMessage(buf, m)){
			type = m.getType();
			clientid = m.getClientID();
		}
		switch(type){
		case 4:		//player's initial comm with the game server
			list = CreateTeamPositionList((Games*)g);
			//we need to tell the player what teams and positions are available
			sendto(currentGame->gsocket, list, strlen(list), 0, &from, sizeof (struct sockaddr));
			break;
		case 5:		//team and position information
			token = strtok(m.getTheData(), delims);		//this is the team id
			team = atoi(token);
			token = strtok(NULL, delims);				//this is the position id
			pos = atoi(token);
			currentGame->theTeam[team].position[pos].clientID = clientid;
			currentGame->theTeam[team].position[pos].inUse = true;
			currentGame->theTeam[team].position[pos].playerAddress = from;
			if (AllPositionsFilled((Games*)g)) startable = true;
			break;
		case 6:	//oandp changes send to everyone except who sent it
			for (i = 0; i < currentGame->numberofteams; i++){
				for (j = 0; j < currentGame->theTeam[i].maxpositions; j++){
					//send to everyone but the original sender
					if(currentGame->theTeam[i].position[j].clientID != clientid && currentGame->theTeam[i].position[j].inUse){
						sendto( currentGame->gsocket,buf, strlen(buf), 0, 
								&currentGame->theTeam[i].position[j].playerAddress,
								sizeof (struct sockaddr));
					}
				}
			}
			break;
		case 7:		// state change, weapon fires, etc...	
			for (i = 0; i < currentGame->numberofteams; i++){
				for (j = 0; j < currentGame->theTeam[i].maxpositions; j++){
					//send to everyone but the original sender
					if(currentGame->theTeam[i].position[j].clientID != clientid && currentGame->theTeam[i].position[j].inUse){
						sendto( currentGame->gsocket,buf, strlen(buf), 0, 
								&currentGame->theTeam[i].position[j].playerAddress,
								sizeof (struct sockaddr));
					}
				}
			}
			break;
		case 8:		// end of game message goes to everyone
			for (i = 0; i < currentGame->numberofteams; i++){
				for (j = 0; j < currentGame->theTeam[i].maxpositions; j++){
					sendto( currentGame->gsocket,buf, strlen(buf), 0, 
							&currentGame->theTeam[i].position[j].playerAddress,
							sizeof (struct sockaddr));
					
				}
			}
			break;
		case 9:		//client leaving game
			token = strtok(m.getTheData(), delims);		//this is the position id
			pos = atoi(token);
			currentGame->theTeam[team].position[pos].inUse = false;	
			break;
		default:
			break;
		}
		if (startable){
			cout<<"\tThe positions are all filled!"<<endl;
			//send final info and start message
		}
	}
	return 0;
}
HRESULT InitializeWinsock(){
	WSADATA w;
	int error = WSAStartup(0x0202, &w);
	
	if (error)	{					// there was an error
	  return -1;
	}
	if (w.wVersion != 0x0202){		// wrong WinSock version!
	  WSACleanup ();				// unload ws2_32.dll
	  return -1;
	}
	return S_OK;
}

void InitializeServer(){
	
	theServer = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	addr.sin_port = htons(SERVER_PORT_NO);

	//bind the socket to our server address
	bind(theServer, (LPSOCKADDR)&addr, sizeof(struct sockaddr));
	cout<<"Server is now listening on port "<<SERVER_PORT_NO<<endl;
	// Make the socket listen
	listen(theServer, 12);
}

DWORD WINAPI ServerAdminListenerThread(void* n){ 

	//Initialize the Admin socket
	theAdmin = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	addr.sin_port = htons(ADMIN_PORT_NO);
	//bind the socket to our server address
	bind(theAdmin, (LPSOCKADDR)&addr, sizeof(struct sockaddr));
	//cout<<"Admin is now listening on port "<<ADMIN_PORT_NO<<endl;
	// Make the socket listen
	listen(theAdmin, 12);

	struct sockaddr from;
	int fromlen = sizeof( struct sockaddr_in );
	int type, clientid, packetid;
	char buf[MAX_BUF_SIZE];
	char *msg;
	int gameid;
	MessageHandler mh;
	Message m;
	while(true){
		ZeroMemory(buf, MAX_BUF_SIZE);
		recvfrom(theAdmin, buf, MAX_BUF_SIZE, 0, &from, &fromlen);
		
		if(mh.parseMessage(buf, m)){
			type = m.getType();
			clientid = m.getClientID();
			packetid = m.getPacketID();
			//cout<<type<<" "<<clientid<<" "<<packetid<<endl;
		}
		switch(type){
		case 1:
			/*	
				This code section is incomplete

			/* /
			//this is a create game message, so let's create a game
			// first lets find an open game slot to place it
			int i;
			for (i = 0; i < 25; i++){
				if (!the_game[i].inUse)	break;
			}
			
			//the game needs to be added to the game list
	
			//start the game thread so we can join it later
			CreateThread(NULL, 0, GameListenerThread,  &the_game[i], 0, 0);*/
			break;
		case 2:
			//client requesting the game list, so lets send it to them
			msg = mh.createGameListMessage(theGameList);
			sendto(theAdmin, msg, strlen(msg), 0, &from, sizeof (struct sockaddr));
			break;
		case 3:
			//client wants to join a game
			cout<<"client wants to join a game"<<endl;
			//we need to know what game they want to join			
			ZeroMemory(msg, MAX_BUF_SIZE);
			gameid = atoi(m.getTheData());
			//make sure game is not full or started
			//if game choice is acceptable send them the address
			sprintf(msg, "%d", the_game[gameid].gport);
			sendto(theAdmin, msg, strlen(msg), 0, &from, sizeof (struct sockaddr));
			//otherwise send them the list again...
			break;
		default:
			break;
		}	
	}
	return 0; 
}
bool AuthenticateClient(char *name, char *pwd){

	return true;
}
void SendWelcome(char *name, sockaddr from){

	int fromlen = sizeof( struct sockaddr_in ); 
	char retMessage[80];
	ZeroMemory(retMessage, 80);
	cout<<name<<endl;
	sprintf(retMessage, "%d/%d/0/Login successful! Welcome to the Space Server, %s\nyour id is %d",
			ADMIN_PORT_NO,playerCount, name, playerCount);
	sendto(theServer, retMessage, strlen(retMessage), 0, &from, sizeof (struct sockaddr));
	//we need to add this player/client to a list 
}

void SendError(sockaddr from){

	int fromlen = sizeof( struct sockaddr_in ); 
	char retMessage[80];
	ZeroMemory(retMessage, 80);
	sprintf(retMessage, "6/0/0/Error processing your request!");
	sendto(theServer, retMessage, strlen(retMessage), 0, &from, sizeof (struct sockaddr));
}

void CreateDefaultGame(){
	char name[] = "0. Default Game";
	the_game[0].inUse = true;
	for (int i = 0; i < the_game[0].numberofteams; i++){
		the_game[0].theTeam[i].name = new char[20];
		ZeroMemory(the_game[0].theTeam[i].name, 20);
		if(i == 0) sprintf(the_game[0].theTeam[i].name, "Blue");
		if(i == 1) sprintf(the_game[0].theTeam[i].name, "Red");
		for (int j = 0; j < the_game[0].theTeam[i].maxpositions; j++){
			the_game[0].theTeam[i].position[j].inUse = false;
		}
	}
}

void InitializeGames(){
	
	for (int i = 0; i < 25; i++){
		the_game[i].id = i;
		the_game[i].inUse = false;
		the_game[i].numberofteams = 2;
		the_game[i].gport = gamePoolStartingPort + i;
		the_game[i].theTeam[0].maxpositions = 2;
		the_game[i].theTeam[1].maxpositions = 2;
	}
}
int main(int argc, char **argv) {

	struct sockaddr from;
	int fromlen = sizeof( struct sockaddr_in ); 
	char buf[MAX_BUF_SIZE];
	char name[20];
	char pwd[20];
	bool validMsg = true;
	Message m;
	MessageHandler mh;

	InitializeWinsock();
	InitializeServer();
	InitializeGames();
	//start the admin thread
	CreateThread(NULL, 0, ServerAdminListenerThread, 0, 0, 0);

	////////////////////////////////////////
	// we may only allow one game per 
	// server to speed things up.
	//testing...
	//test initialize the myGame list
	ZeroMemory(theGameList, 2500);
	strcat(theGameList, "0. Default Game");
	//lets create a default myGame for testing 	
	CreateDefaultGame();
	CreateThread(NULL, 0, GameListenerThread,  &the_game[0], 0, 0);
	//end testing
	////////////////////////////////////////

//	cout<<"Waiting for a connection request from a client"<<endl;
	//login in handler
	while(true){
		ZeroMemory(buf, MAX_BUF_SIZE);
		ZeroMemory(name, 20);
		ZeroMemory(pwd, 20);
		recvfrom(theServer, buf, MAX_BUF_SIZE, 0, &from, &fromlen);

		if (mh.parseMessage(buf, m)){
			validMsg = true;
		}
		else {
			cout<<"Error parsing header"<<endl;
			validMsg = false;
		}
		char *dataPtr = m.getTheData();
		strcat(name, mh.parseName(dataPtr));
		strcat(pwd, mh.parsePwd(dataPtr));
		if (validMsg){
			if(AuthenticateClient(name, pwd)){
				playerCount++;
				SendWelcome(name, from);
			}
		}
		else {
			SendError(from);
		}
	}
	closesocket(theServer);
	closesocket(theAdmin);
	WSACleanup ();

	return 0;
}