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

_netstuff net;
_character *netcharacter[4];
ol::Bitmap *netbmp[4];

void refresh_maplist();
void get_teleports();
void get_connected_beaches();

_netstuff::_netstuff()
{
	engine = NULL;
	chatengine = NULL;
	lobbyengine = NULL;
}

_netstuff::~_netstuff()
{
	unload();
}

bool _netstuff::init(bool is_server)
{
	unload();
	engine = new netengine();
	chatengine = new netengine();
	lobbyengine = new netengine();
	
	if (!engine || !chatengine || !lobbyengine) return false;
	
	engine->setBufferSize(2000);  // we ocasionally send maps over this, so the buffer needs to be fairly large
	engine->setMaxConnections(MAX_CONNECTIONS);
	chatengine->setBufferSize(250);
	chatengine->setMaxConnections(MAX_CONNECTIONS);
	lobbyengine->setBufferSize(5000);

	server = is_server;

	if ((!engine->initialize()) || (!chatengine->initialize()) || (!lobbyengine->initialize()))
		return false;

	return true;
}

void _netstuff::unload()
{
	if (engine)
	{
		delete engine;
		engine = NULL;
	}
	if (chatengine)
	{
		delete chatengine;
		chatengine = NULL;
	}
	if (lobbyengine)
	{
		delete lobbyengine;
		lobbyengine = NULL;
	}
}

bool init_networking()
{
	if (!DyConnect::startup())
	{
		return false;
	}
	return true;
}

void stop_networking()
{
	net.unload();
	DyConnect::shutdown();
}

void addID(DyBytes &data, const char id[5])
{
	for (unsigned int i = 0; i < 4; i++)
	{
		data.appendByte(id[i]);
	}
}

void prependID(DyBytes &data, const char id[5])
{
	data.insertByte(id[3]);
	data.insertByte(id[2]);
	data.insertByte(id[1]);
	data.insertByte(id[0]);
}

string getID(DyBytes &data)
{
	char s[5];
	
	if (data.size() < 4)
	{
		empdebug("Couldn't get message ID: data size is < 4\n");
		return "AAAA";
	}
	s[0] = data.getByte(0);
	s[1] = data.getByte(1);
	s[2] = data.getByte(2);
	s[3] = data.getByte(3);
	s[4] = '\0';
	data.removeFront(4);
	return s;
}


//==============================================================================
//==============================================================================

int network()
{
	static int netstatus = NET_FIRST;
	
	switch (netstatus)
	{
		case NET_FIRST:
			netstatus = net_serverconnect();
			break;
		case NET_LOBBY:
			netstatus = net_lobby();
			break;
		case NET_HOST_CONNECT:
			netstatus = net_connect_to_host();
			break;
		case NET_HOST:
			netstatus = net_hostgame();
			break;
		case NET_CLIENT:
			netstatus = net_joingame();
			break;
		case NET_PLAY:
			//first = true;
			game->game_just_started = true;
			game->loaded_from_file = false;
			
			global.netgame = true;
			get_connected_beaches();
			get_teleports();
			clear_particles();
			
			netstatus = NET_FIRST;
			
			return LOGIC;
			break;
		case NET_QUIT:
			netstatus = NET_FIRST;
			play_song(title_music);
			return MENU;
			break;
	}
	
	return NETWORK;
}

extern string connectmsg;  //a message to be added to the chatbox when netlobby starts
int net_serverconnect()
{
	static bool first = true, err, initialized, startedconnect, connected, got_list, got_motd;
	static _button cancel;

	glb.draw(54, 0, 0);
	
	if (first)
	{
		stop_music();
		cancel.init(360, 300, 80, 20, "Cancel");
		
		global.netname = global.orignetname;
		
		initialized = false;
		connected = false;
		startedconnect = false;
		got_list = false;
		got_motd = false;
		err = false;
		first = false;
		
		connectmsg = "";
		
		buffer_textout_centre_ex(big, "Starting up networking...", 400, 200, WHITE);
		return NET_FIRST;
	}
	
	if (err)
	{
		buffer_rectrounded(200, 190, 600, 260, RED, 0.7, 5);
		buffer_textout_centre_ex(big, "Error connecting to server", 400, 200, WHITE);
		buffer_textout_centre_ex(normal, net.lobbyengine->getErrMsg(), 400, 230, WHITE);
	}
	else if (!initialized)
	{
		err = !net.init(false);
		initialized = !err;
		
		if (initialized)
			buffer_textout_centre_ex(big, "Connecting to server...", 400, 200, WHITE);
	}
	else if (!connected)
	{  //no errors, and not connected
		int connection, temp;
		
		if (!startedconnect)
		{
			err = !net.lobbyengine->connectToHost(global.netlobby, 41135);
			startedconnect = true;
		}
		
		connection = net.lobbyengine->checkConnection(temp);
		if (connection < 0)
		{
			buffer_textout_centre_ex(big, "Connecting to server...", 400, 200, WHITE);
			if (connection != DY_BUSYUNREACHABLE)
			{
				err = true;
			}
		}
		else
		{
			DyBytes data;
			
			buffer_textout_centre_ex(big, "Retrieving server data...", 400, 200, WHITE);
			
			net.lobbythread = connection;
			connected = true;
			
			addID(data, CLIENT_NAME);  //the server won't do anything until it has a name
			data.appendString(global.orignetname);
			net.lobbyengine->sendTCP(net.lobbythread, data);
			
			data.clear();
			addID(data, CLIENT_VERSION);  //the server won't give us any lobby data until it knows our version
			data.appendString(TRICHROMIC_VERSION);
			net.lobbyengine->sendTCP(net.lobbythread, data);
			
			data.clear();
			addID(data, QUERY_MOTD);
			net.lobbyengine->sendTCP(net.lobbythread, data);  //ask server for MOTD
			
			data.clear();
			addID(data, QUERY_LOBBY_DATA);
			net.lobbyengine->sendTCP(net.lobbythread, data);  //ask server for list of active games
		}
	}
	else  //no errors, connected to server, sent messages asking for MOTD and game list
	{
		DyBytes data;
		
		buffer_textout_centre_ex(big, "Retrieving data...", 400, 200, WHITE);
		
		if (net.lobbyengine->getMessage(net.lobbythread, data))
		{
			string messageID = getID(data);
			
			if (messageID == SERVER_LOBBY_DATA)
			{
				if (data.decompress())
					get_lobby_data(data);
				
				got_list = true;
			}
			else if (messageID == SERVER_MOTD)
			{
				MOTD = data.getAsString();
				got_motd = true;
			}
			else if (messageID == SERVER_NEWNAME)
			{
				connectmsg = "There is already someone on the server with your name.  You are now "; 
				global.netname = data.getAsString();
				connectmsg += global.netname + ".";
			}
		}
		
		if (!net.lobbyengine->isConnected(net.lobbythread))
		{  //lost connection
			cerr << "DISCONNECTED!\n";
			err = true;
		}
	}

	cancel.check();
	if (cancel.clicked())
	{
		if (connected)
		{
			net.lobbyengine->disconnectHost(net.lobbythread);
			first = true;
			return NET_QUIT;
		}
		else if (!err)
		{
			net.lobbyengine->cancelConnection();
			first = true;
			return NET_QUIT;
		}
		else
		{
			first = true;
			return NET_QUIT;
		}
	}
	
	if (got_list && got_motd)
	{
		first = true;
		return NET_LOBBY;
	}
	
	draw_mouse();
	
	return NET_FIRST;
}

bool get_client_message(int thread, DyBytes &data)
{
	if ((thread < 0) || (thread >= MAX_CONNECTIONS)) 
		return false;
	
	if (net.engine->isConnected(thread))
		if ((net.engine->getMessage(thread, data)) && (data.size() > 0))
			return true;
	
	return false;
}

bool get_host_message(DyBytes &data)
{
	if ((net.thread < 0) || (net.thread >= MAX_CONNECTIONS))
		return false;
	
	if (net.engine->isConnected(net.thread))
		if ((net.engine->getMessage(net.thread, data)) && (data.size() > 0))
			return true;
	
	return false;
}

DyBytes mapdata()
{
	int i, j, n;
	DyBytes mapdata;
	
	mapdata.appendByte(worldmap.l);  //then send the actual map data in a separate blob
	mapdata.appendByte(worldmap.h);
	mapdata.appendByte(numplayers);
	
	j = 0;
	while (j < worldmap.h)
	{
		i = 0;
		while (i < worldmap.l)
		{
			mapdata.appendByte(worldmap.tile[i][j].get_set());
			mapdata.appendByte(worldmap.tile[i][j].get_number());
			mapdata.appendByte(worldmap.tile[i][j].get_owner());
			i++;
		}
		j++;
	}
	
	n = 0;
	i = 0;
	while (i < 4)
	{
		if (player[i].playing == 1)
		{
			n += player[i].units_in_play();
		}
		i++;
	}
	mapdata.appendByte(n);
	
	i = 0;
	while (i < 4)
	{
		if (player[i].playing == 1)
		{
			j = 0;
			while (j < 50)
			{
				if (player[i].unit[j].exists == 1)
				{
					mapdata.appendByte(player[i].unit[j].type);
					mapdata.appendByte(player[i].unit[j].tilex);
					mapdata.appendByte(player[i].unit[j].tiley);
					mapdata.appendByte(i);
				}
				j++;
			}
		}
		i++;
	}

	//done with map data, begin sending AI data
	for (i = 0; i < NUM_UNITS; i++)
	{
		mapdata.appendVariable(theai.weight[i]);
		mapdata.appendVariable(theai.limit[i]);
	}
	
	mapdata.appendString(worldmap.get_name());
	
	//now compress it all, prepend an ID, and it's ready to go
	mapdata.compress(9);
	mapdata.insertByte(NET_MAPDATA);
	
	return mapdata;
}

void send_mapdata(int thread)
{
	DyBytes data;
	
	data.appendByte(NET_MAPCHANGE);  //tell the connected players that the map has changed
	net.engine->sendTCP(thread, data);
	
	data.clear();
	data = mapdata();
	net.engine->sendTCP(thread, data);
}

void broadcast_mapdata()
{
	DyBytes data = mapdata();
	net.engine->broadcastTCP(data);
}

void load_mapdata(DyBytes *data)
{
	int i, j, t, x, y, p, o;
	
	data->decompress();
	worldmap.reset();
	player[0].clear_units();
	player[1].clear_units();
	player[2].clear_units();
	player[3].clear_units();
	
	worldmap.l = data->getByte(0);
	worldmap.h = data->getByte(1);
	numplayers = data->getByte(2);
	data->removeFront(3);
	
	i = 0;
	while (i < numplayers)
	{
		player[i].everplaying = 1;
		player[i].playing = 1;
		i++;
	}
	while (i < 4)
	{
		player[i].everplaying = 0;
		player[i].playing = 0;
		i++;
	}
	
	y = 0;
	while (y < worldmap.h)
	{
		x = 0;
		while (x < worldmap.l)
		{
			i = data->getByte(0);
			j = data->getByte(1);
			o = data->getByte(2);
			data->removeFront(3);
			worldmap.tile[x][y].change_type(i, j);
			//bytes are received as UNSIGNED, so -1 is interpreted as 255, which need correcting
			if ((o >= 0) && (o <= 3)) worldmap.tile[x][y].change_owner(o);
			else worldmap.tile[x][y].change_owner(-1);
			
			x++;
		}
		y++;
	}
	
	j = data->getByte(0);
	data->removeFront(1);
	i = 0;
	while (i < j)
	{
		t = data->getByte(0);
		x = data->getByte(1);
		y = data->getByte(2);
		p = data->getByte(3);
		data->removeFront(4);
		player[p].create_unit(t, x, y, p);
		i++;
	}
	
	for (i = 0; i < NUM_UNITS; i++)
	{
		data->extractVariable(0, theai.weight[i]);
		data->extractVariable(4, theai.limit[i]);
		data->removeFront(8);
	}
	
	worldmap.set_name(data->getAsString());
}

bool clientconnected(int client)
{
	if (client > 2) return false;
	return (net.clientthread[client] != -1);
}
