#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() :
	server (false),
	lobbythread (-1),
	thread (-1),
	chatthread (-1),
	client_pnum (-1),
	hostnum (-1),
	err (""),
	engine (0),
	chatengine (0),
	lobbyengine (0)
{
	clientthread[0] = -1;
	clientthread[1] = -1;
	clientthread[2] = -1;
	address[0] = "";
	address[1] = "";
	address[2] = "";
	address[3] = "";
}

_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_THEME);
			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;

	if (first)
	{
		stop_music();
		cancel.init(global.screen_width / 2 - 40, global.screen_height / 2 + 60, 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...", global.screen_width / 2, global.screen_height / 2 - 20, WHITE);
		return NET_FIRST;
	}
	
	glb.draw_fullscreen(54);
	
	if (err)
	{
		cancel.rename("Back");
		
		buffer_rectfill(0, global.screen_height / 2 - 30, global.screen_width, global.screen_height / 2 + 40, RED, 0.7);
		buffer_textout_centre_ex(big, "Error connecting to server", global.screen_width / 2, global.screen_height / 2 - 20, WHITE);
		buffer_textout_centre_ex(normal, net.lobbyengine->getErrMsg(), global.screen_width / 2, global.screen_height / 2 + 10, WHITE);
	}
	else if (!initialized)
	{
		err = !net.init(false);
		initialized = !err;
		
		if (initialized)
			buffer_textout_centre_ex(big, "Connecting to server...", global.screen_width / 2, global.screen_height / 2 - 20, 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...", global.screen_width / 2, global.screen_height / 2 - 20, WHITE);
			if (connection != DY_BUSYUNREACHABLE)
			{
				err = true;
			}
		}
		else
		{
			DyBytes data;
			
			buffer_textout_centre_ex(big, "Retrieving server data...", global.screen_width / 2, global.screen_height / 2 - 20, 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...", global.screen_width / 2, global.screen_height / 2 - 20, 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 n;
	DyBytes mapdata;
	
	mapdata.appendByte(worldmap.get_length());  //then send the actual map data in a separate blob
	mapdata.appendByte(worldmap.get_height());
	mapdata.appendByte(worldmap.get_num_players());
	
	for (int j = 0; j < worldmap.get_height(); j++)
	{
		for (int i = 0; i < worldmap.get_length(); i++)
		{
			mapdata.appendByte(worldmap.get_tile(i, j)->get_set());
			mapdata.appendByte(worldmap.get_tile(i, j)->get_number());
			mapdata.appendByte(worldmap.get_tile(i, j)->get_owner());
		}
	}
	
	n = 0;
	for (int i = 0; i < 4; i++)
	{
		if (player[i].playing == 1)
		{
			n += player[i].units_in_play();
		}
	}
	mapdata.appendByte(n);
	
	for (int i = 0; i < 4; i++)
	{
		if (player[i].playing == 1)
		{
			for (int j = 0; j < 50; j++)
			{
				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);
				}
			}
		}
	}

	//done with map data, begin sending AI data
	for (int 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);
}

bool load_mapdata(DyBytes *data)
{
	int t, x, y, p;
	
	if (!data->decompress())
		return false;
	
	if (data->size() < 3)
		return false;
	
	worldmap.close();
	player[0].clear_units();
	player[1].clear_units();
	player[2].clear_units();
	player[3].clear_units();
	
	x = data->getByte(0);
	y = data->getByte(1);
	worldmap = _map(x, y);
	
	t = data->getByte(2);  // <- was number of players on the map
	data->removeFront(3);
	
	if (data->size() < unsigned(worldmap.get_height() * worldmap.get_length() * 3))
		return false;
	
	for (y = 0; y < worldmap.get_height(); y++)
	{
		for (x = 0; x < worldmap.get_length(); x++)
		{
			int i = data->getByte(0);
			int j = data->getByte(1);
			int o = data->getByte(2);
			data->removeFront(3);
			worldmap.get_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.get_tile(x, y)->change_owner(o);
			else worldmap.get_tile(x, y)->change_owner(-1);
		}
	}
	
	if (data->size() < 1)
		return false;
	
	int j = data->getByte(0);
	data->removeFront(1);
	
	if (data->size() < unsigned(j * 4))
		return false;
	
	for (int i = 0; i < j; i++)
	{
		t = data->getByte(0);
		x = data->getByte(1);
		y = data->getByte(2);
		p = data->getByte(3);
		data->removeFront(4);
		
		if (x >= worldmap.get_length())
			return false;
		if (y >= worldmap.get_height())
			return false;
		
		player[p].create_unit(t, x, y, p);
	}
	
	if (data->size() < unsigned(NUM_UNITS * 8))
		return false;
	
	for (int 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());
	worldmap.refresh_numplayers();
	worldmap.camera.jump_to((worldmap.get_length() / 2) - (XTILES / 2), (worldmap.get_height() / 2) - (YTILES / 2));  //center the camera
	
	for (int i = 0; i < worldmap.get_num_players(); i++)
	{
		player[i].everplaying = 1;
		player[i].playing = 1;
	}
	for (int i = worldmap.get_num_players(); i < 4; i++)
	{
		player[i].everplaying = 0;
		player[i].playing = 0;
	}
	
	return true;
}

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