//include <libnet.h>

#include "allegro.h"

#ifdef _WIN32
#include "winalleg.h"

#include <winsock.h>
#else
#define SOCKET_ERROR -1
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <string.h>
#endif

#include "defs.h"
#include "console.h"
#include "gfx.h"
#include "engine.h"
#include "network.h"
#include "packet.h"
#include "player.h"
#include "rules.h"
#include "server.h"
#include "sound.h"

#include "demos.h"

/* The listening connection. */
int listenSocket;
/* A connection for every client. */
int ClientConn[MAX_CLIENTS] = {-1, -1, -1, -1, -1, -1, -1, -1};
int ClientNum = 0;

static unsigned long myAddr = INADDR_ANY;

char localAddr[256] = {0, };

/* Get local IP address -- taken from Quake source code */
void getLocalAddr()
{
	struct hostent	*local = NULL;
	char			buff[256];
	unsigned long	addr;

	if (myAddr != INADDR_ANY)
		return;

	if (gethostname(buff, 255) == SOCKET_ERROR)
		return;

	local = gethostbyname(buff);
	if (local == NULL)
		return;

	myAddr = *(int *)local->h_addr_list[0];

	addr = ntohl(myAddr);
	sprintf(localAddr, "%d.%d.%d.%d", (int) ((addr >> 24) & 0xff), (int) ((addr >> 16) & 0xff), (int) ((addr >> 8) & 0xff), (int) (addr & 0xff));
}

/* Initialize the server. */
int ServerInit() {

//	SOCKADDR_IN my_addr; // Our address and port information
	struct sockaddr_in my_addr; // Our address and port information

    Console.WriteF("Starting the server...");
	if(NetworkInit()) return -1;
    Console.WriteF("Network initialized...");

	// Create the socket
	listenSocket = socket (PF_INET, SOCK_STREAM, 0);
	if (listenSocket == -1)
		return -1;

	// Bind it to the listen port
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons (Engine.network.serverPort);
	my_addr.sin_addr.s_addr = INADDR_ANY;
	memset (&my_addr.sin_zero, 0, 8);
	if (bind(listenSocket, ((struct sockaddr *) &my_addr), sizeof (my_addr)))
		return -1;
	// Listen for incoming connections
	if (listen(listenSocket, BACKLOG))
		return -1;

    getLocalAddr();
	
    Console.WriteF("Server done");
	return 0;
}

void serverRegister()
{
    unsigned char request[255] = {0, };
    struct hostent *he;
    struct sockaddr_in their_addr;
    int LobbyConn;
    struct timeval tv = {1, 0};
	fd_set readfds;
	char *match;

    Console.WriteF("Registering the server online...");
    he = gethostbyname("www.wurmz.liero.org.pl");
    if (he == NULL)
    {
        return;
    }

    if ((LobbyConn = socket(PF_INET, SOCK_STREAM, 0)) == -1)
    {
        return;
    }

	if(!LobbyConn) {
		/* Error: Unable to open server connection. */
		return;
	}

    their_addr.sin_family = AF_INET;
    their_addr.sin_port = htons(80);
    their_addr.sin_addr = *((struct in_addr *)he->h_addr);
	memset (&their_addr.sin_zero, 0, 8);

    if (connect(LobbyConn, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)))
    {
		return;
    }

    FD_ZERO(&readfds);

    FD_SET (LobbyConn, &readfds);

	select (LobbyConn+1, NULL, &readfds, NULL, &tv);
	// Check if there are new players connecting
	while (!FD_ISSET (LobbyConn, &readfds)) {
	   rest(1);
	   select (LobbyConn+1, NULL, &readfds, NULL, &tv);
	}

    sprintf((char *)request, "/servers/wurmz!server.php?map=%s server:%s:%d:%d:%d:%d:%s", players[0].playerName, mapName, GP_NUM_PLAYERS, GP_MAX_PLAYERS, gameType, teamplay+2*tactics, VERSION);
    while (match = strchr((char *)request, ' '))
       match[0] = '+';
    sprintf(buf, "GET %s HTTP/1.0\nHost: www.wurmz.liero.org.pl\n\n", request);

    net_send_rdm(LobbyConn, (unsigned char *)buf, strlen(buf));

    FD_ZERO(&readfds);
    
    FD_SET (LobbyConn, &readfds);

	select (LobbyConn+1, &readfds, NULL, NULL, &tv);

    closesocket(LobbyConn);
    Console.WriteF("Registered");
}

/* Shut the server down. */
void ServerExit() {
   NetShutdown();
}

void ServerStopListening()
{
   Console.WriteF("Stopping the server...");
   closesocket(listenSocket);
}

void playerConnected(SOCKET s)
{
   for (ClientNum=0; ClientNum<GP_MAX_PLAYERS; ClientNum++)
   if (ClientConn[ClientNum] == -1) break;

   ClientConn[ClientNum] = s;
   NetSetNoDelay(s);
}

/* Check for connecting clients.
   0 = ok, got new connection
   1 = no new connection yet */
int ServerListen() {
	fd_set readfds;
    struct timeval tv = {0, 0};

    FD_ZERO(&readfds);

    FD_SET (listenSocket, &readfds);

	select (listenSocket+1, &readfds, NULL, NULL, &tv);
	// Check if there are new players connecting
	if (FD_ISSET (listenSocket, &readfds)) {
		playerConnected (accept (listenSocket, NULL, NULL));
		return 0;
	}

	return 1;
}

int serverCanGet(SOCKET s, int size)
{
	fd_set readfds;
    struct timeval tv = {0, 0};
    int retval;

    FD_ZERO(&readfds);
    
    FD_SET (s, &readfds);

	select (s+1, &readfds, NULL, NULL, &tv);
	if (FD_ISSET (s, &readfds)) {
		retval = recv (s, (char *)packetbuffer, size, MSG_PEEK);
        if (retval == SOCKET_ERROR)
           return 0;
        if (retval == size) return 1;
	}
    return 0;
}

int serverGetMessageId(SOCKET s)
{
	fd_set readfds;
    struct timeval tv = {0, 0};
    int retval;
    char value;

    FD_ZERO(&readfds);
    
    FD_SET (s, &readfds);

	select (s+1, &readfds, NULL, NULL, &tv);
	if (FD_ISSET (s, &readfds)) {
		retval = recv (s, &value, 1, MSG_PEEK);
        if (retval == 0)
           return -2;
        if (retval == SOCKET_ERROR)
           return -1;
        return value;
	}
    return -1;
}

void serverGetMessage(SOCKET s, int size)
{
	fd_set readfds;
    struct timeval tv = {0, 0};
    int retval;
    char value;

    FD_ZERO(&readfds);
    
    FD_SET (s, &readfds);

	select (s+1, &readfds, NULL, NULL, &tv);
	if (FD_ISSET (s, &readfds)) {
		retval = recv (s, (char *)packetbuffer, size, 0);
	}
}

void serverProcessMessage(int who, int id)
{
   if (id == -2)
   {
      int i, j;
      closesocket(ClientConn[who]);
      ClientConn[who] = -1;
      if (worms[who].active)
      {
         removePlayer(who);
         Packet(PACKET_QUIT);
         PacketAddByte(who);
         ServerBroadcast(packetsend, PACKET_QUIT_SIZE);
         sprintf(buf, "%s has quit...", worms[who].name);
         Console.Write(buf);
      }
      if (gameStart)
      {
         j = 0;
         for (i=0; i < GP_MAX_PLAYERS; i++)
         {
            if (ClientConn[i] != -1) j = 1;
         }
         if (!j) serverRestart = 1;
      }
   }
   if (id < 0) return;
   switch(id)
   {
   case CLIENT_WEAP:
      if (serverCanGet(ClientConn[who], CLIENT_WEAP_SIZE))
      {
         int i;
         serverGetMessage(ClientConn[who], CLIENT_WEAP_SIZE);
         if (!gameStart)
         {
            for (i=0; i<5; i++)
            {
               worms[who].weaps[i] = packetbuffer[1+i];
            }
			Packet(PACKET_WEAP);
			PacketAddByte(who);
            for (i=0; i<5; i++)
            {
               PacketAddByte(worms[who].weaps[i]);
            }
			ServerBroadcast(packetsend, PACKET_WEAP_SIZE);
            worms[who].active = 2;
         }   
      }
      break;
   case CLIENT_JNRQ:
      if (serverCanGet(ClientConn[who], CLIENT_JNRQ_SIZE))
      {
         int i;
         serverGetMessage(ClientConn[who], CLIENT_JNRQ_SIZE);
         if (!gameStart)
         if (!worms[who].active)
         {
            worms[who].team = packetbuffer[1];
            worms[who].skin = packetbuffer[2];
            addPlayer(who);
            renderSkin(who, worms[who].skin, worms[who].team);
            for (i=0; i<21; i++)
            {
               worms[who].name[i] = packetbuffer[3+i];
            }
            Packet(PACKET_JOIN);
            PacketAddByte(who);
            PacketAddByte(worms[who].team);
            PacketAddByte(worms[who].skin);
            for (i=0; i<21; i++)
            {
               PacketAddByte(worms[who].name[i]);
            }
            ServerBroadcast(packetsend, PACKET_JOIN_SIZE);
            
            if (who >= GP_NUM_PLAYERS)
               GP_NUM_PLAYERS++;
            Packet(PACKET_PLNM);
            PacketAddByte(GP_NUM_PLAYERS);
            ServerBroadcast(packetsend, PACKET_PLNM_SIZE);
            
            Packet(PACKET_LOCL);
            PacketAddByte(who);
            ServerSendTo(packetsend, PACKET_LOCL_SIZE, who);
            
            serverRegister();
         }
      }
      break;
   case CLIENT_MOVE:      
      if (serverCanGet(ClientConn[who], CLIENT_MOVE_SIZE))
      {
         serverGetMessage(ClientConn[who], CLIENT_MOVE_SIZE);
         if (!worms[who].sentKeys)
         {
            bufor[who] = packetbuffer[1];
            worms[who].sentKeys = 1;
         }
      }
      break;
   case CLIENT_QUIT:
      if (serverCanGet(ClientConn[who], CLIENT_QUIT_SIZE))
      {
         serverGetMessage(ClientConn[who], CLIENT_QUIT_SIZE);
         if (worms[who].active)
         {
            removePlayer(who);
            Packet(PACKET_QUIT);
            PacketAddByte(who);
            ServerBroadcast(packetsend, PACKET_QUIT_SIZE);
            Console.WriteF("%s has quit...", worms[who].name);
         }
      }
      break;
   case CLIENT_CHAT:
      if (serverCanGet(ClientConn[who], CLIENT_CHAT_SIZE))
      {
         int i;
         
         serverGetMessage(ClientConn[who], CLIENT_CHAT_SIZE);
         Packet(PACKET_CHAT);
         PacketAddByte(who);
         for (i=0; i<50; i++)
         PacketAddByte(packetbuffer[i+1]);
         ServerBroadcast(packetsend, PACKET_CHAT_SIZE);
         sprintf(buf, "%s: %s", worms[who].name, &packetbuffer[1]);
         Console.Write(buf);
         Sound.playSound(talkSnd, worms[viewport[0].player].coords.x, worms[viewport[0].player].coords.y);
      }
      break;
   case CLIENT_BEGIN:
      if (serverCanGet(ClientConn[who], CLIENT_BEGIN_SIZE))
      {
         serverGetMessage(ClientConn[who], CLIENT_BEGIN_SIZE);
         if (isDedicated)
         if (!gameStart)
         {
            gameStart = 1;
            Packet(PACKET_STRT);
            ServerBroadcast(packetsend, PACKET_STRT_SIZE);
         }
      }
      break;
   }
}

/* Send data to a specific client. */
void ServerSendTo(unsigned char *data, int len, int who) {

	net_send_rdm(ClientConn[who], data, len);

}

/* Send data to all connected clients. */
void ServerBroadcast(unsigned char *data, int len) {
	int n;
#ifdef DEMOS
    if (isDemoMode)
       savePacket((char *)data, len);
#endif
	for(n = 0; n < GP_MAX_PLAYERS; n++) {
		if(ClientConn[n] != -2)
		if(ClientConn[n] != -1) {
			net_send_rdm(ClientConn[n], data, len);
		}
	}
}

