////////////////////////////////////////////////////////////////////////////////
//    Scorched3D (c) 2000-2003
//
//    This file is part of Scorched3D.
//
//    Scorched3D 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 2 of the License, or
//    (at your option) any later version.
//
//    Scorched3D 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 Scorched3D; if not, write to the Free Software
//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
////////////////////////////////////////////////////////////////////////////////

#include <server/ServerBrowserInfo.h>
#include <server/ScorchedServer.h>
#include <server/ServerState.h>
#include <server/ServerTooFewPlayersStimulus.h>
#include <common/OptionsGame.h>
#include <common/OptionsTransient.h>
#include <common/Defines.h>
#include <coms/NetInterface.h>
#include <tank/TankContainer.h>
#include <wx/wx.h>
#include <wx/utils.h>
#include <string.h>

ServerBrowserInfo *ServerBrowserInfo::instance_ = 0;

ServerBrowserInfo *ServerBrowserInfo::instance()
{
	if (!instance_)
	{
		instance_ = new ServerBrowserInfo();
	}
	return instance_;
}

ServerBrowserInfo::ServerBrowserInfo() : udpsock_(0)
{
	packetV_ = SDLNet_AllocPacketV(4, 10000);
	packetVOut_ = SDLNet_AllocPacketV(20, 10000);
}

ServerBrowserInfo::~ServerBrowserInfo()
{
}

bool ServerBrowserInfo::start()
{
	udpsock_ = SDLNet_UDP_Open(ScorchedServer::instance()->getOptionsGame().getPortNo() + 1);
	if(!udpsock_) return false;
	return true;
}

void ServerBrowserInfo::processMessages()
{ 
	if (!udpsock_) return;

	int numrecv = SDLNet_UDP_RecvV(udpsock_, packetV_);
	if(numrecv <=0) return;
	
	int numsent = 0;
	for(int i=0; i<numrecv; i++) 
	{
		if (packetV_[i]->len == 0) packetV_[i]->data[0] = '\0';

		//printf("Packet = %s\n", (char *) packetV_[i]->data);
		NetInterface::getBytesIn() += packetV_[i]->len;
		NetInterface::getPings() ++;

		// Only compare the first few characters so a NULL is not needed
		std::list<std::string> reply;
		const char *wrapper = "pong";
		if (0 == strncmp((char *) packetV_[i]->data, "status", 6))
		{
			wrapper = "status";
			processStatusMessage(reply);
		}
		else if (0 == strncmp((char *) packetV_[i]->data, "info", 4))
		{
			wrapper = "info";
			processInfoMessage(reply);
		}
		else if (0 == strncmp((char *) packetV_[i]->data, "players", 7))
		{
			wrapper = "players";
			processPlayerMessage(reply);
		}

		bool first = true;
		while (!reply.empty() || first)
		{
			first = false;
			
			std::string buffer = "<";
			buffer += wrapper;
			buffer += " ";
			while (!reply.empty())
			{
				std::string next = reply.front();
				reply.pop_front();
				if (strlen(next.c_str()) <= 75)
				{
					buffer += next;
				}
			
				if (strlen(buffer.c_str()) > 800) break;
			}
			buffer += "/>";
			
			int len = (int) strlen(buffer.c_str())+1;
			memcpy(packetVOut_[numsent]->data, buffer.c_str(), len);
			packetVOut_[numsent]->len = len;
			packetVOut_[numsent]->address.host = packetV_[i]->address.host;
			packetVOut_[numsent]->address.port = packetV_[i]->address.port;
			packetVOut_[numsent]->channel = packetV_[i]->channel;
			
			NetInterface::getBytesOut() += len;
			//printf("Packet len = %i\n", packetVOut_[numsent]->len);
			//printf("Packet = %s\n", packetVOut_[numsent]->data);
			
			if (++numsent > 20) break;
		}
	}
	
	//printf("Sending %i packets\n", numsent);
	SDLNet_UDP_SendV(udpsock_, packetVOut_, numsent);
}

void ServerBrowserInfo::processStatusMessage(std::list<std::string> &reply)
{
	char *serverName = (char *) ScorchedServer::instance()->getOptionsGame().getServerName();
	char version[256];
	sprintf(version, "%s (%s)", ScorchedVersion, ScorchedProtocolVersion);
	unsigned currentState = ScorchedServer::instance()->getGameState().getState();
	bool started = !ServerTooFewPlayersStimulus::instance()->acceptStateChange(0, 0, 0.0f);
	char players[25];
	sprintf(players, "%i", ScorchedServer::instance()->getTankContainer().getNoOfTanks());
	char maxplayers[25];
	sprintf(maxplayers, "%i", ScorchedServer::instance()->getOptionsGame().getNoMaxPlayers());
	char type[100];
	sprintf(type, "%s (%s)", 
		ScorchedServer::instance()->getOptionsTransient().getGameType(),
		((ScorchedServer::instance()->getOptionsGame().getTeams() > 1)?"Teams":"No Teams"));
	wxString osDesc = ::wxGetOsDescription();

	reply.push_back(addTag("gametype", type));
	reply.push_back(addTag("state", (started?"Started":"Waiting")));
	reply.push_back(addTag("servername", serverName));
	reply.push_back(addTag("fullversion", version));
	reply.push_back(addTag("version", ScorchedVersion));
	reply.push_back(addTag("protocolversion", ScorchedProtocolVersion));
	reply.push_back(addTag("mod", 
		ScorchedServer::instance()->getOptionsGame().getMod()));
	reply.push_back(addTag("password", 
		ScorchedServer::instance()->getOptionsGame().getServerPassword()[0]?"On":"Off"));
	reply.push_back(addTag("noplayers", players));
	reply.push_back(addTag("maxplayers", maxplayers));
	reply.push_back(addTag("round", formatString("%i/%i",
		ScorchedServer::instance()->getOptionsTransient().getCurrentRoundNo(),
		ScorchedServer::instance()->getOptionsGame().getNoRounds())));
	reply.push_back(addTag("os", osDesc.c_str()));
}

void ServerBrowserInfo::processInfoMessage(std::list<std::string> &reply)
{
	// Add all of the other tank options
	// Currently nothing on the client uses this info
	std::list<OptionEntry *> &options = ScorchedServer::instance()->getOptionsGame().getOptions();
	std::list<OptionEntry *>::iterator optionItor;
	for (optionItor = options.begin();
		optionItor != options.end();
		optionItor ++)
	{
		OptionEntry *entry = (*optionItor);
		if (!(entry->getData() & OptionEntry::DataProtected) &&
			!(entry->getData() & OptionEntry::DataDepricated))
		{
			reply.push_back(
				addTag(entry->getName(), entry->getValueAsString()));
		}
	}	
}

void ServerBrowserInfo::processPlayerMessage(std::list<std::string> &reply)
{
	// Add all of the player information
	std::map<unsigned int, Tank *> &tanks =
		ScorchedServer::instance()->getTankContainer().getPlayingTanks();
	std::map<unsigned int, Tank *>::iterator tankItor;
	int i=0;
	for (tankItor =  tanks.begin();
		tankItor != tanks.end();
		tankItor++, i++)
	{
		Tank *tank = (*tankItor).second;
		static char tmp[128];

		sprintf(tmp, "pn%i", i);
		reply.push_back(addTag(tmp, tank->getName()));

		sprintf(tmp, "ps%i", i);
		reply.push_back(addTag(tmp, tank->getScore().getScoreString()));

		sprintf(tmp, "pt%i", i);
		reply.push_back(addTag(tmp, tank->getScore().getTimePlayedString()));

		sprintf(tmp, "pm%i", i);
		switch (tank->getTeam())
		{
		case 1: reply.push_back(addTag(tmp, "Red")); break;
		case 2: reply.push_back(addTag(tmp, "Blue")); break;
		case 3: reply.push_back(addTag(tmp, "Green")); break;
		case 4: reply.push_back(addTag(tmp, "Yellow")); break;
		default: reply.push_back(addTag(tmp, "None")); break;
		}

		sprintf(tmp, "pr%i", i);
		reply.push_back(addTag(tmp, tank->getScore().getStatsRank()));
	}

}

const char *ServerBrowserInfo::addTag(const char *name, const char *value)
{
	static char buffer[10000];
	static char newvalue[10000];
	strcpy(newvalue, value);
	for (char *a=newvalue; *a; a++)
	{
		if (*a == '\'') *a='\"';
		else if (*a == '>') *a=' ';
		else if (*a == '<') *a=' ';
	}

	strcpy(buffer, name); strcat(buffer, "='");
	strcat(buffer, newvalue); strcat(buffer, "' ");
	return buffer;
}
