/*
   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>
#else
#include <windows.h>
#endif

#include <iostream>
#include <sstream>

#include "sys/sys.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;

	// 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(fd());
		
		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 the game server
			if (client->state() == NetClient::Connected)	
				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;
	timeout.tv_usec = 2500;
	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_debug << "client_connect " << host << ":" << port << "\n";

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

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

void NetServer::client_initialize(NetClient *client) {

	// send welcome message
	std::string welcome("^B");
	welcome.append(Cvar::sv_name->str());
	send_message(client, "info", welcome);
	client->transmit(fd());

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

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

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

// send updates to one client
void NetServer::client_frame(NetClient *client, float timestamp, float previoustimestamp)
{
	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, previoustimestamp);
		
			// 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() && !(entity->flags() & Entity::Static) ) {	
		
						// 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);
				}
			}
		}
	}
}

// run a network server frame, send updates to clients
void NetServer::frame(float timestamp, float previoustimestamp)
{
	/* 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(fd());

		if (client->state() == NetClient::Connected)
			client_frame(client, timestamp, previoustimestamp);

		// update player info always gets through
		if (client->player()->dirty() || client->player()->zonechange()) {

			send_player_update(client);

			client->player()->player_dirty = false;
			client->player()->player_zonechange = false;
		}
		client->transmit(fd());
	}
}

// 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>
 * 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>
 */

// broadcast a "msg <channel>" message to all clients
void NetServer::broadcast_message(const char *channel, std::string const & message, Player *ignore_player)
{
	if (!channel)
		return;

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

	for (Clients::iterator it = clients.begin(); it != clients.end(); it++) {
		if (((*it)->player() && (*it)->player() != ignore_player) && ((*it)->state() == NetClient::Connected)) {
			(*it)->send_raw(msg);
		}
	}
}

// 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(netserver_fd);
	client->abort();
}

// send a "frame" message to a client
void NetServer::send_frame_marker(NetClient *client, float timestamp, float previoustimestamp)
{
	std::ostringstream msg("");
	msg << "frame " << timestamp << " " << previoustimestamp << "\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 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());
	}
}

// broadcast a "sup" server update entity message to all clients
void NetServer::send_entity_update(NetClient *client, Entity *entity)
{
	if ((client->state() == NetClient::Connected) && !entity->serverside()) {
		std::ostringstream msg;
		msg << "sup " << entity->id() << " ";
		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());
}

// parse incoming client messages

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

	std::stringstream msgstream(message);
	
	std::string command;
	msgstream >> command;
	
	// disconnect
	if (command == "disconnect") {
		client->abort();
		return;
	}

	// connection request
	// connect is the first command expected from the client
	if (command == "connect") {
		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;
	}

	// pif - update player information
	// client connection is completed on the first pif
	if (command == "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);
		}
	}

	if (command == "ping") {
		return;
	}

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

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

	// cup - client update entity
	if (command == "cup") {
		//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 not-owned entity " << id << "\n";
				return;
			}

			entitycontrolable->entity_dirty = true;
			entitycontrolable->receive_client_update(msgstream);	
		}
		return;
	}

	// say
	if (command == "say") {		
		if (message.size() > command.size()+1) {
			server()->say(client->player(), message.substr(command.size()+1));
		}
		return;
	}

	// priv
	if (command == "priv") {
		if (message.size() > command.size()+1) {
			server()->private_message(client->player(), message.substr(command.size()+1));
		}
		return;
	}
	
}

}