/*
   net/netserver.h
   This file is part of the Osirion project and is distributed under
   the terms of the GNU General Public License version 2
*/

#include <string.h>
#include <unistd.h>
#include <errno.h>

#ifndef _WIN32
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#endif

#include <iostream>
#include <sstream>

#include "sys/sys.h"
#include "core/application.h"
#include "core/gameserver.h"
#include "core/netclient.h"
#include "core/netserver.h"
#include "core/cvar.h"
#include "core/func.h"
#include "core/core.h"
#include "core/stats.h"
#include "core/zone.h"

#ifdef _WIN32
typedef int socklen_t;
#endif

namespace core
{

NetServer::NetServer(std::string const host, unsigned int const port)
{
	con_print << "^BInitializing network server..." << std::endl;
	
	con_debug << "  protocol version " << PROTOCOLVERSION << std::endl;

	// initialize variables
	netserver_fd = -1;
	netserver_error = true;

	// initialize socket
	netserver_fd = ::socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (netserver_fd == -1) {
		con_error << "Network can't create socket!" << std::endl;
		//perror("socket");
		return;
	}


	// Get the local adress to bind to
	netserver_addr.sin_family = AF_INET;
	netserver_addr.sin_port = htons(port);
	if (host.size()) {
		netserver_addr.sin_addr.s_addr = inet_addr(host.c_str());
		if (netserver_addr.sin_addr.s_addr == INADDR_NONE) {
			con_error << "Network invalid address " << host << "!" << std::endl;
			return;
		}
	} else {
		netserver_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	}
	memset(netserver_addr.sin_zero, '\0', sizeof(netserver_addr.sin_zero));

	// bind the local address to the socket ( note the typecast)
	if (::bind(netserver_fd, (struct sockaddr *) &netserver_addr, sizeof(struct sockaddr)) == -1) {
		con_error << "Network can't bind to local address!" << std::endl;
		//perror("bind");
		return;
	}

	con_print << "  listening on " << inet_ntoa(netserver_addr.sin_addr) <<	":" << ntohs(netserver_addr.sin_port) << std::endl;

	// add the listening socket to the file descriptor set
	FD_ZERO(&serverset);
#ifdef _WIN32
	FD_SET((unsigned int) netserver_fd, &serverset);
#else
	FD_SET(netserver_fd, &serverset);
#endif
	netserver_error = false;
}

NetServer::~NetServer()
{
	con_print << "^BShutting down network server..." << std::endl;

	std::string netmsg("disconnect\n");

	// delete all clients
	Clients:: iterator it;
	for (it = clients.begin(); it != clients.end(); it++) {

		// notify the game server
		if ((*it)->state() == NetClient::Connected)
			server()->player_disconnect((*it)->player());

		(*it)->send_raw(netmsg);
		(*it)->transmit();

		delete(*it);
	}
	clients.clear();

	if (valid()) {
#ifdef _WIN32
		closesocket(fd());
#else
		close(fd());
#endif
	}
}

void NetServer::abort()
{
	netserver_error = true;
}

// find the client corresponding to a player
NetClient *NetServer::find_client(Player const *player)
{
	for (Clients::iterator it = clients.begin(); it != clients.end(); it++) {
		if ((*it)->player() == player) {
			return (*it);
		}
	}
	return 0;
}

// remove disconnected clients
void NetServer::reap()
{
	for (Clients:: iterator it = clients.begin(); it != clients.end();) {
		NetClient *client = *it;

		if (client->client_timeout + NETTIMEOUT < application()->time()) {
			// client timed out, send a disconnect
			send_disconnect((*it));

			// print a message
			std::string message("^B");
			message.append(client->player()->name());
			message.append(" ^Btimed out.");

			if (client->state() == NetClient::Connected) {
				server()->broadcast(message, client->player());
			} else {
				con_print << message << std::endl;
			}
		}

		if (client->error()) {

			// notify other clients
			for (Clients::iterator cit = clients.begin(); cit != clients.end(); cit++) {
				if ((*cit) != (*it)) {
					send_player_disconnect_info((*cit), (*it)->player());
				}
			}

			if (client->state() == NetClient::Connected) {
				// notify the game server
				server()->player_disconnect((*it)->player());
			}

			// remove the client
			delete client;
			clients.erase(it++);

		} else {
			++it;
		}
	}
}

void NetServer::receive()
{
	if (error())
		return;

	timeval timeout;
	timeout.tv_sec = 0;
	
	// 1,000,000 microseconds = 1 second, default is 2500	
	timeout.tv_usec = (long) Cvar::net_selecttimeout->value(); 
	if (timeout.tv_usec < 0)
		timeout.tv_usec = 0;
	else if (timeout.tv_usec > 1000000) {
		timeout.tv_sec = timeout.tv_usec / 1000000;
		timeout.tv_usec %= 1000000;
	}
	
	fd_set readset = serverset;

	int nb = select(fd() + 1, &readset, NULL, NULL, &timeout);
	if (nb == -1) {
#ifndef _WIN32
		// ncurses needs SIGWINCH catched
		if (errno == EINTR) {
			return;
		}
#endif
		con_error << "Network error on select()" << std::endl;
		this->abort();
		return;
	}

	if (nb && FD_ISSET(fd(), &readset)) {

		// receive incoming data
		struct sockaddr_in 	client_addr;
		socklen_t		client_addr_len = sizeof(client_addr);
		memset(recbuf, '\0', sizeof(recbuf));
		ssize_t bytes_received  = ::recvfrom(fd(), recbuf, FRAMESIZE - 1, 0, (struct sockaddr *) & client_addr, &client_addr_len);
		if (bytes_received == -1) {
			con_error << "Network error on recvfrom()!" << std::endl;
			this->abort();
			return;
		} else {
			//con_debug << "Incoming data '" << recbuf <<  "'"<< bytes_received << " bytes" << std::endl;
		}
		Stats::network_bytes_received += bytes_received;

		// originator
		std::string client_host(inet_ntoa(client_addr.sin_addr));
		unsigned int client_port = ntohs(client_addr.sin_port);

		// get messages from clients
		bool msg_received = false;

		for (Clients::iterator it = clients.begin(); it != clients.end() && !msg_received; it++) {
			NetClient *client = *it;

			if ((client->host() == client_host) && (client->port() == (int) client_port)) {
				// receive data
				client->receive(recbuf);

				// process parsed messages
				while (client->has_messages()) {
					std::string message;
					client->retreive(message);
					parse_incoming_message(client, message);
				}

				msg_received = true;
			}
		}

		if (!msg_received) {
			// new connection
			// FIXME maxclients
			NetClient *client = client_connect(client_host , client_port);

			if (client) {
				// receive data
				client->receive(recbuf);

				// process parsed messages
				while (client->has_messages()) {
					std::string message;
					client->retreive(message);
					parse_incoming_message(client, message);
				}
			}
		}

	}

	// remove dead connections
	reap();
}


NetClient * NetServer::client_connect(std::string const host, int const port)
{
	//con_print << "Client " << host << ":" << port << " connected\n";

	NetClient *client = new NetClient(host, port, fd());
	if (client->error()) {
		con_warn << client->host() << ":" << client->port() << " connection failed!\n";
		delete(client);
		return 0;
	}

	clients.push_back(client);
	client->player()->set_dirty(false);
	return client;
}

void NetServer::client_initialize(NetClient *client)
{
	// send welcome message
	std::string welcome("^B");
	welcome.append(Cvar::sv_name->str());
	client->player()->send(welcome);
	client->transmit();

	// send info types	
	send_infotypes(client);
	client->transmit();
	
	// send zones
	for (Zone::Registry::iterator it = Zone::registry().begin();  it != Zone::registry().end(); it++) {
		send_zone_update(client, (*it).second);
	}

	// send player list
	for (GameInterface::Players::iterator it = server()->players().begin(); it != server()->players().end(); it++) {
		if (((*it)->id() != client->player()->id())) {
			send_player_update(client, (*it));
		}
	}

	//  send connect completed
	std::string connect("connect\n");
	client->send_raw(connect);
	client->transmit();

	// set client state to pending
	client->client_state = NetClient::Pending;
}

// send updates to one client
void NetServer::client_frame(NetClient *client, unsigned long timestamp)
{
	if (client->state() != NetClient::Connected)
		return;

	Zone *zone = client->player()->zone();

	if (zone) {
		
		if (client->player()->zonechange()) {

			// send zone info
			send_zone_update(client, zone);

			// send entities in zone
			if (client->player()->zone()) {
				for (Zone::Content::iterator it = zone->content().begin(); it != zone->content().end(); it++) {
					Entity *entity = (*it);

					if (!entity->entity_destroyed) {
						send_entity_create(client, entity);
					}
				}
			}

			// the actual zone change is send by the "pif" message

		} else {
			// send a server frame marker
			send_frame_marker(client, timestamp);

			// send updates for entities in the zone
			for (Entity::Registry::iterator it = Entity::registry().begin(); it != Entity::registry().end(); it++) {
				Entity *entity = (*it).second;

				if (entity->zone() == zone) {
					if (entity->entity_destroyed) {
						if (!entity->entity_created) {
							send_entity_delete(client, entity);
						}
					} else 	if (entity->entity_created) {
						send_entity_create(client, entity);

					} else if (entity->oldzone()) {
						// this entity has entered the zone
						send_entity_create(client, entity);

					} else if (entity->dirty()) {

						// FIXME only within visual range
						send_entity_update(client, entity);
					}
				} else if (entity->oldzone() == zone) {
					// the entity has left the zone
					send_entity_delete(client, entity);
				}
			}
		}
	}

	// send inventory update for control
	// FIXME this should be done for all player assets
	if (client->player()->control() && client->player()->control()->inventory() && client->player()->control()->inventory()->dirty()) {
		//con_debug << "SERVER Sending inventory for entity " << client->player()->control()->id() << " server timestamp " << game()->timestamp() << std::endl;		
		send_inventory_update(client, client->player()->control(), client->player()->control()->inventory()->timestamp());
	}
	
	// send inventory updates for view
	if (client->player()->view() && client->player()->view()->inventory() && client->player()->view()->inventory()->dirty()) {
		//con_debug << "SERVER Sending inventory for entity " << client->player()->view()->id() << " server timestamp " << game()->timestamp() << std::endl;
		send_inventory_update(client, client->player()->view(), client->player()->view()->inventory()->timestamp());
	}
	
	// send updates for other players
	for (GameInterface::Players::iterator it = server()->players().begin(); it != server()->players().end(); it++) {
		if (((*it)->id() != client->player()->id()) && ((*it)->dirty())) {
			send_player_update(client, (*it));
		}
	}
}

// run a network server frame, send updates to clients
void NetServer::frame(unsigned long timestamp)
{
	/* FIXME
		Only entities within visual range should send updates (1024 game units?)
		Fix reliable messages
		It would be nice if players with the highest ping got their updates first
	*/

	// send updates to each client
	for (Clients::iterator it = clients.begin(); it != clients.end(); it++) {
		NetClient *client = *it;

		client->transmit();

		// send game state changes
		if (client->state() == NetClient::Connected)
			client_frame(client, timestamp);

		if (client->player()->dirty() || client->player()->zonechange()) {
			send_player_update(client);
		}

		client->transmit();
	}

	// clear dirty state
	for (Clients::iterator it = clients.begin(); it != clients.end(); it++) {
		NetClient *client = *it;
		if (client->player()->dirty() || client->player()->zonechange()) {
			client->player()->set_dirty(false);
			client->player()->set_zonechange(false);
		}
	}
}

// send outgoing messages to clients

/**
 * The following messages can be send to a client
 *
 * frame <timestamp> <previous timestamp>
 * ent <id> <entity create data>
 * die <id> <entity data>
 * inf <id>
 * inv <id>
 * ping <timestamp>
 * sup <entity update data>
 *
 * msg <channel> <text>
 * 	supported message channels are "info" "public" "rcon" and "snd"
 * 	"snd" is a special channel to transmit sound events
 * zone <id> <zone create/update data>
 */

// send a "msg <channel>" message to one client
void NetServer::send_message(NetClient *client, const char *channel, std::string const & message)
{
	if (!channel)
		return;

	std::string msg("msg ");
	msg.append(channel);
	msg += ' ';
	msg.append(message);
	msg += '\n';

	client->send_raw(msg);
}

// disconnect a client
void NetServer::send_disconnect(NetClient *client)
{
	client->send_raw("disconnect\n");
	client->transmit();
	client->abort();
}

// send a "frame" message to a client
void NetServer::send_frame_marker(NetClient *client, unsigned long timestamp)
{
	std::ostringstream msg("");
	msg << "frame " << timestamp << "\n";

	if (client->state() == NetClient::Connected) {
		client->send_raw(msg.str());
	}

}

// send a "ent" create entity message to all clients
void NetServer::send_entity_create(NetClient *client, Entity *entity)
{
	if ((client->state() == NetClient::Connected) && !entity->serverside()) {
		std::ostringstream msg;
		msg << "ent " << entity->id() << " " << entity->type() << " ";
		entity->serialize_server_create(msg);
		msg << '\n';
		client->send_raw(msg.str());

		// send entity menus
		for (Entity::Menus::iterator it = entity->menus().begin(); it != entity->menus().end(); it++) {
			msg.clear();
			msg.str("");
			msg << "menu " << entity->id() << " ";
			Descriptions::serialize((*it), msg);
			msg << '\n';
			client->send_raw(msg.str());
			// con_debug << "sending menu " << entity->label() << " " << (*it)->label() << std::endl;
		}
	}
}

// send a "die" delete entity message to a client
void NetServer::send_entity_delete(NetClient *client, Entity *entity)
{
	if ((client->state() == NetClient::Connected) && !entity->serverside()) {
		std::ostringstream msg("");
		msg << "die " << entity->id() <<  '\n';
		client->send_raw(msg.str());
	}
}

// send a "sup" server update entity message to a client
void NetServer::send_entity_update(NetClient *client, Entity *entity)
{
	if ((client->state() == NetClient::Connected) && !entity->serverside()) {
		std::ostringstream msg;
		msg << "sup " << entity->id() << " " << entity->type() << " ";
		entity->serialize_server_update(msg);
		msg << '\n';

		client->send_raw(msg.str());
	}
}

// send a "zone" update zone message to a client
void NetServer::send_zone_update(NetClient *client, Zone *zone)
{
	std::ostringstream msg;
	msg << "zone " << zone->id() << " ";
	zone->serialize_server_update(msg);
	msg << '\n';
	client->send_raw(msg.str());
}


// send a "pif" update player information to a single player
void NetServer::send_player_update(NetClient *client)
{
	std::ostringstream msg;
	msg << "pif 0 ";
	client->player()->serialize_server_update(msg);
	msg << '\n';
	client->send_raw(msg.str());
}

// send a short "pif" update player information to a single player
void NetServer::send_player_update(NetClient *client, Player *player)
{
	std::ostringstream msg;
	msg << "pif " << player->id() << ' ';
	player->serialize_short_server_update(msg);
	msg << '\n';
	client->send_raw(msg.str());
}

// send a "pid" player disconnect information
void NetServer::send_player_disconnect_info(NetClient *client, Player *player)
{
	std::ostringstream msg;
	msg << "pid " << player->id() << '\n';
	client->send_raw(msg.str());
}

// send a "inf" info record
void NetServer::send_info_update(NetClient *client, Info *info)
{
	if (!info || !info->type())
		return;
	
	std::ostringstream msg;
	msg << "inf " << info->id() <<  " \"" <<  info->type()->label() << "\" \"" << info->label() << "\" ";
	info->serialize_server_update(msg);
	msg << '\n';
	client->send_raw(msg.str());
}

// send "inf" info types
void NetServer::send_infotypes(NetClient *client)
{
	std::ostringstream msg;
	msg << "inf 0 " << InfoType::registry().size();
	
	for (InfoType::Registry::const_iterator it = InfoType::registry().begin(); it != InfoType::registry().end(); it++) {
		msg << " \"" << (*it)->label() << "\"";
	}
	msg << '\n';
	client->send_raw(msg.str());
}

// send a "inv" inventory update
void NetServer::send_inventory_update(NetClient *client, Entity *entity, const unsigned long timestamp)
{
	if (!entity || !entity->inventory())
		return;
	
	std::ostringstream msg;
	msg << "inv " << entity->id() << " " << game()->timestamp() << " ";
	
	size_t nbitems = 0;
	
	std::ostringstream itemstr;
	for (Inventory::Items::const_iterator it = entity->inventory()->items().begin(); it != entity->inventory()->items().end(); it++) {
		const Item *item = (*it);
		if (item->timestamp() >= timestamp) {
			itemstr << item->info()->id() << " ";
			item->serialize_server_update(itemstr);
			nbitems++;
		}
	}
	
	msg << nbitems;
	
	if (nbitems) {
		msg << " " << itemstr.str();
	}
	msg << '\n';
	client->send_raw(msg.str());
}

// parse incoming client messages

/**
 * The following incoming protocol messages are parsed;
 *
 * disconnect
 * cmd <game command>
 * cup
 * inf
 * pif
 * ping
 * msg <channel> <text>
 * info <id>
 * req <id>
 * inv <id>
 *
 */
void NetServer::parse_incoming_message(NetClient *client, const std::string & message)
{
	if (!message.size())
		return;

	std::stringstream msgstream(message);

	std::string command;
	msgstream >> command;

	if (command.compare("disconnect") == 0 ) {
		// disconnect
		client->abort();
		return;
		
	} else if (command.compare("connect") == 0) {
		// connection request
		// connect is the first command expected from the client
	
		if (client->state() != NetClient::Connecting)
			return;

		unsigned int protover;
		if (msgstream >> protover) {
			if (protover != PROTOCOLVERSION) {
				// set protocol version mismatch notification
				std::stringstream netmsgstream("");
				netmsgstream << "^WProtocol version mismatch: ";
				netmsgstream << "client " << protover << " server " << PROTOCOLVERSION << "!\n";

				con_print << client->host() << ":" << client->port() << " " << netmsgstream.str() << std::endl;
				send_message(client, "info", netmsgstream.str());

				send_disconnect(client);
			} else {
				client_initialize(client);
			}
		} else {
			std::string message("Unknown client protocol version!");
			con_print << client->host() << ":" << client->port() << " " << message << std::endl;

			send_message(client, "info", message);
			send_disconnect(client);
		}
		return;
		
	} else if (command.compare("pif") == 0) {
		// pif - update player information
		// client connection is completed on the first pif
	
		std::string oldname(client->player()->name());
		client->player()->receive_client_update(msgstream);

		if (client->state() == NetClient::Pending) {	
			client->client_state = NetClient::Connected;
			server()->player_connect(client->player());

		} else if ((client->state() == NetClient::Connected) && (client->player()->name() != oldname)) {

			std::string netmsg("^B");
			netmsg.append(oldname);
			netmsg.append(" ^Brenamed to ");
			netmsg.append(client->player()->name());
			server()->broadcast(netmsg);
		}
		
	} else if (command.compare("ping") == 0) {
		unsigned long timestamp;
		if (msgstream >> timestamp) {
			client->player()->set_ping(application()->timestamp() - server()->startup() - timestamp);
		}
		return;
	}

	if (client->state() != NetClient::Connected)
		return;

	if (command.compare("cmd") == 0) {
		if (message.size() > command.size() + 1) {
			std::string cmdline(message.substr(command.size() + 1));
			server()->exec(client->player(), cmdline);
		}
		return;

	} else if (command.compare("cup") == 0) {
		// cup - client update entity

		//con_debug << message << "\n";
		unsigned int id;
		if (msgstream >> id) {
			Entity *entity = Entity::find(id);
			if (!entity) {
				con_warn << client->host() << ":" << client->port() << " update for unknown entity " << id << "\n";
				return;
			}

			if (entity->type() != Entity::Controlable) {
				con_warn << client->host() << ":" << client->port() << " update for non-controlable entity " << id << "\n";
				return;
			}

			EntityControlable *entitycontrolable = (EntityControlable *)entity;

			if (entitycontrolable->owner() != client->player()) {
				con_warn << client->host() << ":" << client->port() << " update for non-owned entity " << id << "\n";
				return;
			}

			entitycontrolable->set_dirty(true);
			entitycontrolable->receive_client_update(msgstream);
		}
		return;

	} else if (command.compare("req") == 0) {
		
		// request entity
		unsigned int id;

		if (!(msgstream >> id)) {
			con_warn << "^B" << client->player()->name() << "^W invalid entity request" << std::endl;
			return;
		}

		Entity *entity = Entity::find(id);
		if (entity) {
			send_entity_create(client, entity);
		} else {
			con_warn << "^B" << client->player()->name() << "^W entity request for unkown entity " << id << std::endl;
		}
		return;
		
	} else if (command.compare("inf") == 0) {
		
		// request information record
		unsigned int id;

		if (!(msgstream >> id)) {
			con_warn << "^B" << client->player()->name() << "^W invalid info request" << std::endl;
			return;
		}

		Info *info = 0;
		if (id) {
			info = Info::find(id);	
			if (info) {
				//con_debug << "Sending info for " << info->id() << " " << info->type()->label() << ":" << info->label() << std::endl;			
				send_info_update(client, info);
				client->transmit();
			}
		}
		return;
		
	} else if (command.compare("inv") == 0) {

		// request information record
		unsigned int id;
		unsigned long client_timestamp;

		if (!(msgstream >> id >> client_timestamp)) {
			con_warn << "^B" << client->player()->name() << "^W invalid inventory request" << std::endl;
			return;
		}
		
		Entity *entity = Entity::find (id);
		if (!entity) {
			con_warn << "^B" << client->player()->name() << "^W invalid request for non-existing entity " << id << std::endl;
			return;
		}
		if (!entity->inventory()) {
			con_warn << "^B" << client->player()->name() << "^W invalid request for entity " << id << " without inventory" << std::endl;
			return;
		}
		
		//con_debug << "SERVER Sending inventory for entity " << id << " client timestamp " << client_timestamp << std::endl;
		
		send_inventory_update(client, entity, client_timestamp);
		
		client->transmit();		

	} else if (command.compare("rcon") == 0) {
		if ((message.size() > command.size() + 1) && Cvar::sv_password->str().size()) {
			if ((Cvar::sv_password->str().compare(client->player()->rconpassword()) == 0)) {
				con_print << "^B" << client->player()->name() << "^F rcon: " << message.substr(command.size() + 1) << std::endl;

				core::CommandBuffer::exec();
				core::console()->set_rcon(true);

				core::cmd() << message.substr(command.size() + 1) << "\n";
				core::CommandBuffer::exec();

				while (console()->rconbuf().size()) {
					send_message(client, "rcon", (*console()->rconbuf().begin()));
					core::console()->rconbuf().pop_front();
				}

				// disable rcon buffering
				console()->set_rcon(false);

			} else {
				send_message(client, "info", "rcon access denied");
				con_warn << "^B" << client->player()->name() << "^W rcon access denied" << std::endl;
			}
		}
		return;

	} else if (command.compare("msg") == 0 ) {
		std::string channel;
		msgstream >> channel;
		
		if (!channel.size())
			return;
		
		const size_t subpos = command.size() + channel.size() + 2;
		if (message.size() <= subpos)
			return;
		
		if (channel.compare("public") == 0) {
			server()->shout(client->player(), message.substr(subpos));
			
		} else if (channel.compare("local") == 0) {
			server()->say(client->player(), message.substr(subpos));
			
		} else if (channel.compare("private") == 0) {
			
			server()->say(client->player(), message.substr(subpos));
		}
		return;
	}

}

}