#include "empire.h"
#include "netlobby.h"

void refresh_maplist();
void clear_gamelist();

string MOTD;
string connectmsg;
vector<listedgame *> gamelist;
vector<string> playerlist;

string connectaddr = "";  //the address of the host to connect to

int net_lobby()
{
	static short since_update;
	static bool first = true, waiting_for_update;
	static _button hostgame, back, up, down;
	static unsigned int gamelist_offset;
	static _listbox playerbox, chatbox;
	static _textline chattext;
	string chatstring;
	DyBytes indata;
	
	static _button *games;
	
	const int playerbox_w = 160;
	const int chatbox_w = global.screen_width - 10 - playerbox_w - 10;
	const int gamecols = (global.screen_width - playerbox_w - 35 - 50) / (listedgame::w + 5);
	
	const int chatbox_h = global.screen_height / 5;
	const int chattext_h = 15;
	const int playerbox_h = global.screen_height - 40 - 25 - chatbox_h - chattext_h - 3;
	const int gamerows = (playerbox_h - 20) / (listedgame::h + 5);
	
	if (first)
	{
		char c[256];
		
		back.init(10, global.screen_height - 25, 100, 20, "Back");
		hostgame.init(10 + chatbox_w + (playerbox_w / 2) - big.Width("Host Game") / 2, global.screen_height - (40 + chatbox_h + chattext_h + 25) / 2, 110, 30, "Host Game", 1);
		
		up.init(27 + gamecols * (listedgame::w + 5), 50, 50, 84, -1);  //just using the button class' mouse functions, don't draw anything
		down.init(27 + gamecols * (listedgame::w + 5), 50 + gamerows * (listedgame::h + 5) - 84, 50, 84, -1);
		
		if (gamerows * gamecols <= 0)
		{
			empdebug("Error: not enough space for even a single netgame button");
			exit(1);
		}
		
		games = new _button[gamerows * gamecols];
		if (games == 0)
		{
			empdebug("Error allocating space for netgame buttons");
			exit(1);
		}
		
		for (int y = 0; y < gamerows; y++)
			for (int x = 0; x < gamecols; x++)
				games[y * gamecols + x].init(20 + (x * (listedgame::w + 5)), 50 + (y * (listedgame::h + 5)), listedgame::w, listedgame::h, -1);
		
		chatbox.init(10, global.screen_height - 30 - chattext_h - chatbox_h, chatbox_w, chatbox_h);
		chattext.init(10, chatbox.get_y() + chatbox_h + 3, chatbox_w, chattext_h, true, 300);
		playerbox.init(global.screen_width - playerbox_w - 5, 40, playerbox_w, playerbox_h);
		
		for (unsigned int i = 0; i < playerlist.size(); i++)
			playerbox.additem(playerlist[i].c_str(), false);
		playerbox.sort();
		
		sprintf(c, "Your version of Trichromic is %s", TRICHROMIC_VERSION);
		chatbox.additem(c, true);
		chatbox.additem("", false);
		
		chatbox.additem(MOTD.c_str(), true);
		
		if (global.orignetname == "Default")
		{
			chatbox.additem("You still have the default multiplayer name.  Might want to change that.", true);
		}
		
		if (connectmsg != "")
		{  //if we have a message from the networking init
			chatbox.additem(connectmsg.c_str(), true);
			connectmsg = "";
		}
		
		chatbox.additem("", false);
		chatbox.scroll_to_end();
		
		gamelist_offset = 0;
		since_update = 0;
		waiting_for_update = false;
		
		first = false;
	}
	
	buffer_textout_ex(big, "Available games", 10, 10, WHITE);
	buffer_textout_right_ex(big, "Players in Lobby", global.screen_width - 5, 10, WHITE);
	buffer_rectrounded(10, 40, 25 + gamecols * (listedgame::w + 5), 55 + gamerows * (listedgame::h + 5), LGREY);
	buffer_rectrounded(15, 45, 20 + gamecols * (listedgame::w + 5), 50 + gamerows * (listedgame::h + 5), BLACK);
	
	back.check();
	if (key[KEY_ESC] || back.clicked())
	{
		if (net.lobbyengine->isConnected(net.lobbythread))
			net.lobbyengine->disconnectHost(net.lobbythread);
		
		net.unload();
		clear_gamelist();
		delete[] games;
		
		first = true;
		return NET_QUIT;
	}
	
	if (!net.lobbyengine->isConnected(net.lobbythread))
	{  //if the lobby connection is lost, show this error message, the "back" button, the mouse, and nothing else
		buffer_textout_centre_ex(big, "Lost connection to server", 320, 200, RED);
		clear_gamelist();
		chatbox.check(false);
		draw_mouse();
		return NET_LOBBY;
	}

	hostgame.check();
	playerbox.check(false);  //don't make lines in the player box selectable
	chatbox.check(false);  //don't make lines in the chatbox selectable either
	chatstring = chattext.check();
	if ((chatstring != "/NOTHING YET/") && (chatstring.size() > 0))
	{
		DyBytes data;
		addID(data, CHAT);
		data.appendString(global.netname);
		data.appendString(": ");
		data.appendString(chatstring);
		net.lobbyengine->sendTCP(net.lobbythread, data);
	}
	
	if (net.lobbyengine->getMessage(net.lobbythread, indata))
	{
		string id = getID(indata);
		if (id == SERVER_CHAT)
		{
			chatbox.additem(indata.getAsString().c_str(), true);
			chatbox.scroll_to_end();
		}
		else if (id == SERVER_LOBBY_DATA)
		{
			if (indata.decompress())
				get_lobby_data(indata);
			
			playerbox.clear();
			for (unsigned int i = 0; i < playerlist.size(); i++)
				playerbox.additem(playerlist[i].c_str(), true);
			playerbox.sort();
			
			waiting_for_update = false;
		}
		else if (id == SERVER_NEWNAME)
		{
			string s = "There is already someone on the server with your name.  You are now "; 
			global.netname = indata.getAsString();
			s += global.netname + ".";
			chatbox.additem(s.c_str(), true);
			chatbox.scroll_to_end();
		}
		else if (id == SERVER_WANTS_NAME)
		{
			DyBytes data;
			addID(data, CLIENT_NAME);
			data.appendString(global.orignetname);
			net.lobbyengine->sendTCP(net.lobbythread, data);
		}
		else if (id == SERVER_WANTS_VERSION)
		{
			DyBytes data;
			addID(data, CLIENT_VERSION);
			data.appendString(TRICHROMIC_VERSION);
			net.lobbyengine->sendTCP(net.lobbythread, data);
		}
	}
	
	for (int y = 0; y < gamerows; y++)
	{
		for (int x = 0; x < gamecols; x++)
		{
			const unsigned int gameindex = y * gamecols + x + (gamelist_offset * gamerows * gamecols);
			const int gamebutton_index = y * gamecols + x;
			_button &game = games[gamebutton_index];
			
			if (gameindex >= gamelist.size())
				continue;
			
			gamelist[gameindex]->draw(20 + (x * (listedgame::w + 5)), 50 + (y * (listedgame::h + 5)));
			
			if (game.mouseover())
				buffer_rect(game.x, game.y, game.x + game.w, game.y + game.h, WHITE);
			
			if (game.clicked())
			{
				DyBytes data;
				addID(data, JOINING_GAME);
				net.lobbyengine->sendTCP(net.lobbythread, data);
				
				net.server = false;
				connectaddr = gamelist[gameindex]->get_address();
				clear_gamelist();
				delete[] games;
				first = true;
				return NET_HOST_CONNECT;
			}
		}
	}
	
	if (gamelist.size() > unsigned(gamerows * gamecols))
	{  //more games than will fit on one screen, draw and check the up/down arrows
		if (gamelist_offset > 0)
		{
			glb.draw(56, up.x, up.y);
			if (up.mouseover())
				buffer_rect(up.x, up.y, up.x + up.w, up.y + up.h, WHITE, 0.8);

			if (up.clicked())
				gamelist_offset--;
		}
		else glb.draw(56, up.x, up.y, 0.5);
		
		if (gamelist.size() > (gamelist_offset * 6) + 6)
		{
			glb.draw(57, down.x, down.y);
			if (down.mouseover())
				buffer_rect(down.x, down.y, down.x + down.w, down.y + down.h, WHITE, 0.8);

			if (down.clicked())
				gamelist_offset++;
		}
		else glb.draw(57, down.x, down.y, 0.5);
	}
	
	if (hostgame.clicked())
	{
		refresh_maplist();
		if (maplist.size() < 1)
		{
			chatbox.additem("Can't host game.  You don't have any maps!", true);
			chatbox.scroll_to_end();
		}
		else if ((!net.engine->enableTCPListen(41136)) || (!net.chatengine->enableTCPListen(41137)))
		{
			chatbox.additem("Can't host game.  Couldn't start listening on ports 41136/41137", true);
			chatbox.scroll_to_end();
			empdebug("Error listening on port 41136/41137.  DaSyne message: " + net.engine->getErrMsg());
		}
		else
		{
			net.server = true;
			clear_gamelist();
			delete[] games;
			first = true;
			return NET_HOST;
		}
	}
	
	since_update++;
	if (((since_update > 100) && !waiting_for_update) || (since_update > 750))
	{  //more than 2 seconds since the last update and we're not waiting for an update we asked for already OR we've been waiting for 15 seconds  
		DyBytes data;
		uint16_t size;
		uint8_t namelen;
		uint32_t crc;
		addID(data, QUERY_LOBBY_DATA);
		
		size = gamelist.size();
		data.appendVariable(size);
		
		for (unsigned int i = 0; i < gamelist.size(); i++)
		{
			DyBytes gamedata;
			
			namelen = gamelist[i]->get_name().length();
			data.appendVariable(namelen);
			data.appendString(gamelist[i]->get_name());
			
			gamelist[i]->append_to(gamedata);
			crc = gamedata.crc32();
			data.appendVariable(crc);
		}
		
		net.lobbyengine->sendTCP(net.lobbythread, data);
		
		since_update = 0;
		waiting_for_update = true;
	}

	draw_mouse();
	
	return NET_LOBBY;
}

void clear_gamelist()
{
	for (unsigned int i = 0; i < gamelist.size(); i++)
	{
		delete gamelist[i];
		gamelist[i] = NULL;
	}
	gamelist.clear();
}

void get_lobby_data(DyBytes &indata)
{
	string s;
	uint16_t num;
	uint8_t len, changed;
	
	vector<listedgame*> newlist;
	
	if (indata.size() < 2)
		return;
	
	indata.extractVariable(0, num);
	indata.removeFront(2);
	for (unsigned int i = 0; i < num; i++)
	{
		if (indata.size() < 1)
			break;
		
		indata.extractVariable(0, changed);
		indata.removeFront(1);
		
		if (changed)
		{
			listedgame *l = new listedgame;
			
			if (l->get_from(indata))
				newlist.push_back(l);
		}
		else
		{  //server said the game was unchanged since last time
			DyBytes subdata;
			string name;
			
			if (indata.size() < 1)
				break;
			
			indata.extractVariable(0, len);
			indata.removeFront(1);
			
			if (indata.size() < len)
				break;
			
			for (unsigned int j = 0; j < len; j++)
				subdata.appendByte(indata.getByte(j));
			indata.removeFront(len);
			
			name = subdata.getAsString();
			
			vector<listedgame*>::iterator it = gamelist.begin();
			while (it != gamelist.end())
			{
				if ((*it)->get_name() == name)
				{  //if this is the listedgame that hasn't changed, move it to newlist
					newlist.push_back(*it);
					it = gamelist.erase(it);  //and remove it from gamelist (without deleting the memory of that listedgame)
					break;
				} else it++;
			}
		}
	}
	
	clear_gamelist();  //delete and free the memory of any games left in gamelist
	gamelist = newlist;
	
	//-------
	
	if (indata.size() < 2) return;
	
	playerlist.clear();
	indata.extractVariable(0, num);
	indata.removeFront(2);
	
	for (unsigned int i = 0; i < num; i++)
	{
		DyBytes subdata;
		
		if (indata.size() < 1)
			break;
		indata.extractVariable(0, len);
		indata.removeFront(1);
		
		if (indata.size() < len)
			break;
		
		for (unsigned int j = 0; j < len; j++)
			subdata.appendByte(indata.getByte(j));
		indata.removeFront(len);
		
		s = subdata.getAsString();
		playerlist.push_back(s);
	}
}

int net_connect_to_host()
{
	static bool first = true, dataerr, chaterr, dataconnected, chatconnected;
	static _button cancel;
	
	if (first)
	{
		dataerr = false;
		chaterr = false;
		dataconnected = false;
		chatconnected = false;
		
		cancel.init(global.screen_width / 2 - 40, global.screen_height / 2, 80, 20, "Cancel");
		
		net.engine->connectToHost(connectaddr, 41136);
		net.chatengine->connectToHost(connectaddr, 41137);
		
		first = false;
	}
	
	glb.draw(54, 0, 0);
	cancel.check();
	
	if (cancel.clicked())
	{
		if (net.thread >= 0)
		{
			if (net.engine->isConnected(net.thread))
				net.engine->disconnectHost(net.thread);
			else if (!dataerr)
				net.engine->cancelConnection();
		}
		
		if (net.chatthread >= 0)
		{
			if (net.chatengine->isConnected(net.chatthread))
				net.chatengine->disconnectHost(net.chatthread);
			else if (!chaterr)
				net.chatengine->cancelConnection();
		}
		
		first = true;
		
		if (net.lobbythread >= 0)
		{
			if (net.lobbyengine->isConnected(net.lobbythread))
			{
				DyBytes data;
				
				addID(data, BACK_IN_LOBBY);
				net.lobbyengine->sendTCP(net.lobbythread, data);
				
				data.clear();
				addID(data, QUERY_LOBBY_DATA);
				net.lobbyengine->sendTCP(net.lobbythread, data);
				
				return NET_LOBBY;
			}
		}
		
		return NET_FIRST;
	}
	
	if (!dataerr && !dataconnected)
	{
		int temp;
		int thread = net.engine->checkConnection(temp);
		
		buffer_textout_centre_ex(big, "Connecting to host...", global.screen_width / 2, global.screen_height / 3, WHITE);
		
		if (thread == DY_NOTOPEN) dataerr = true;
		else if (thread >= 0)
		{
			net.thread = thread;
			dataconnected = true;
		}
	}
	else if (dataerr)
	{
		buffer_rectfill(0, global.screen_height / 3 - 10, global.screen_width, global.screen_height / 3 + 60, RED, 0.7);
		buffer_textout_centre_ex(big, "Error connecting to host", global.screen_width / 2, global.screen_height / 3, WHITE);
		buffer_textout_centre_ex(normal, net.engine->getErrMsg(), global.screen_width / 2, global.screen_height / 3 + 30, WHITE);
	}
	
	if (!chaterr && !chatconnected)
	{
		int temp;
		int thread = net.chatengine->checkConnection(temp);
		
		if (thread == DY_NOTOPEN) chaterr = true;
		else if (thread >= 0)
		{
			net.chatthread = thread;
			chatconnected = true;
		}
	}
	else if (chaterr && !dataerr)  //don't display two error messages, just one will do
	{
		buffer_rectfill(0, global.screen_height / 3 - 10, global.screen_width, global.screen_height / 3 + 60, RED, 0.7);
		buffer_textout_centre_ex(big, "Error connecting to host", global.screen_width / 2, global.screen_height / 3, WHITE);
		buffer_textout_centre_ex(normal, net.chatengine->getErrMsg(), global.screen_width / 2, global.screen_height / 3 + 30, WHITE);
	}
	
	if (dataconnected && chatconnected)
	{
		connectaddr = "";
		first = true;
		return NET_CLIENT;
	}
	
	draw_mouse();
	
	return NET_HOST_CONNECT;
}
