/*
 * Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
 *
 * (c) Copyright 1996, 1997, 1998 Gary Henderson (gary@daniver.demon.co.uk) and
 *                                Jerremy Koot (jkoot@snes9x.com)
 *
 * Super FX C emulator code 
 * (c) Copyright 1997, 1998 Ivar (Ivar@snes9x.com) and
 *                          Gary Henderson.
 * Super FX assembler emulator code (c) Copyright 1998 zsKnight and _Demo_.
 *
 * DSP1 emulator code (c) Copyright 1998 Ivar, _Demo_ and Gary Henderson.
 * DOS port code contains the works of other authors. See headers in
 * individual files.
 *
 * Snes9x homepage: www.snes9x.com
 *
 * Permission to use, copy, modify and distribute Snes9x in both binary and
 * source form, for non-commercial purposes, is hereby granted without fee,
 * providing that this license information and copyright notice appear with
 * all copies and any derived work.
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event shall the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Snes9x is freeware for PERSONAL USE only. Commercial users should
 * seek permission of the copyright holders first. Commercial use includes
 * charging money for Snes9x or software derived from Snes9x.
 *
 * The copyright holders request that bug fixes and improvements to the code
 * should be forwarded to them so everyone can benefit from the modifications
 * in future versions.
 *
 * Super NES and Super Nintendo Entertainment System are trademarks of
 * Nintendo Co., Limited and its subsidiary companies.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <memory.h>
#include <sys/types.h>
#include <sys/time.h>

#define MAX_CLIENTS 5

#ifdef __DJGPP__
#include <dpmi.h>
#include <netinet/winsock.h>
#include <netinet/bootp.h>
#include <conio.h>

#define	timercmp(tvp, uvp, cmp)	\
	(((tvp)->tv_sec == (uvp)->tv_sec && (tvp)->tv_usec cmp (uvp)->tv_usec) \
	|| (tvp)->tv_sec cmp (uvp)->tv_sec)

typedef struct
{
    Socket *socket;
    InetAddress addr;
    int size;
} ClientData;
typedef ClientData *Client;
typedef Socket *Incomming;

static Client clients [MAX_CLIENTS] = {0};
#define NO_CONNECTION 0
#else
#include <netdb.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <signal.h>

#ifdef __SVR4
#include <sys/stropts.h>
#endif
typedef int Client;
typedef int Incomming;

static Client clients [MAX_CLIENTS] = {0};
#define NO_CONNECTION -1
#endif

#include "snes9x.h"
#include "netplay.h"

#define MAX_HOSTNAME_LEN 256
static int32 frame_time = 16669;
static uint32 sequence_num = 0;
static uint32 joypads [5] = { 0 };
static uint32 num_connected_clients = 0;
static char hostnames [MAX_CLIENTS][MAX_HOSTNAME_LEN];
static char ROMName [30];
static struct timeval now;
static struct timeval next;

#define COMPUTE_NEXT(N) \
if ((N.tv_usec += frame_time) >= 1000000) \
{ \
    N.tv_sec += next.tv_usec / 1000000; \
    N.tv_usec %= 1000000; \
}

void S9xNetPlaySendToAllClients (char *data, int len);

void S9xNetPlayServerHeader (char *header, uint8 opcode, uint32 len)
{
    strcpy (header, NETPLAY_MAGIC);
    header [NETPLAY_SERVER_VERSION_OFFSET] = NETPLAY_VERSION;
    header [NETPLAY_SERVER_OPCODE_OFFSET] = opcode;
    WRITE_LONG (header + NETPLAY_SERVER_LEN_OFFSET, len);
    WRITE_LONG (header + NETPLAY_SERVER_SEQUENCE_NO_OFFSET, sequence_num);
}

void S9xNetPlayShutdownClient (int which1)
{
#ifdef __DJGPP__
    ClientData *c = clients [which1];
    if (c != NO_CONNECTION)
    {
	c->socket->ShutDown (0);
	delete c->socket;
	c->socket = 0;
	delete c;
	clients [which1] = NO_CONNECTION;
    }
#else
    close (clients [which1]);
    clients [which1] = NO_CONNECTION;
#endif

    joypads [which1] = 0;
    if (num_connected_clients)
	num_connected_clients--;

    printf ("Player %d on %s has disconnected.\n", which1 + 1,
	    hostnames [which1]);
}

bool8 S9xNetPlaySend (Client fd, const char *data, int len)
{
#ifdef __DJGPP__
    do
    {
	int sent;
	sent = fd->socket->Send ((void *) data, len, 0, (void *) &fd->addr, 
				 fd->size);
	if (sent < 0)
	    return (FALSE);
	data += sent;
	len -= sent;
    } while (len > 0);
#else
    do
    {
	int sent;
	sent = write (fd, data, len);
	if (sent <= 0)
	{
	    if (errno == EINTR 
#ifdef EAGAIN
		|| errno == EAGAIN
#endif
#ifdef EWOULDBLOCK
		|| errno == EWOULDBLOCK
#endif
		)
		continue;
	    perror ("Write to client failed");
	    return (FALSE);
	}
	else
	if (sent == 0)
	    return (FALSE);
	len -= sent;
	data += sent;
    } while (len > 0);
#endif

    return (TRUE);
}

char *S9xNetPlayReceivePacket (Client fd, int &len_return)
{
    char header [NETPLAY_CLIENT_TO_SERVER_HEADER_SIZE];
    char *ptr = header;
    int done = 0;
    int len = NETPLAY_CLIENT_TO_SERVER_HEADER_SIZE;

    do
    {
#ifdef __DJGPP__
	int got = fd->socket->Recv ((void *) ptr, len, 0, (void *) &fd->addr, 
				    fd->size);
	if (got < 0)
	{
	    fprintf (stderr, "Error while receiving header from client\n");
	    return (NULL);
	}
#else
	int got = read (fd, ptr, len);
	if (got < 0)
	{
	    if (errno == EINTR
#ifdef EAGAIN
		|| errno == EAGAIN
#endif
#ifdef EWOULDBLOCK
		|| errno == EWOULDBLOCK
#endif
		)
		continue;
	    perror ("while receiving header from client");
	    return (NULL);
	}
	else
	if (got == 0)
	    return (NULL);
#endif
	len -= got;
	ptr += got;
    } while (len > 0);

    if (strncmp (header, NETPLAY_MAGIC, 3) != 0 ||
	header [NETPLAY_CLIENT_VERSION_OFFSET] != NETPLAY_VERSION)
    {
	return (NULL);
    }
    
    READ_LONG (&header [NETPLAY_CLIENT_LEN_OFFSET], len);
    READ_LONG (&header [NETPLAY_CLIENT_SEQUENCE_NO_OFFSET], sequence_num);
    len_return = len;
    char *data = new char [len];
    memmove (data, header, NETPLAY_CLIENT_TO_SERVER_HEADER_SIZE);
    ptr = &data [NETPLAY_CLIENT_TO_SERVER_HEADER_SIZE];
    len -= NETPLAY_CLIENT_TO_SERVER_HEADER_SIZE;

    do
    {
#ifdef __DJGPP__
	int got = fd->socket->Recv ((void *) ptr, len, 0, (void *) &fd->addr, 
				    fd->size);
	if (got < 0)
	{
	    delete data;
	    fprintf (stderr, "Error while receiving data from client\n");
	    return (NULL);
	}
#else
	int got = read (fd, ptr, len);
	if (got < 0)
	{
	    if (errno == EINTR
#ifdef EAGAIN
		|| errno == EAGAIN
#endif
#ifdef EWOULDBLOCK
		|| errno == EWOULDBLOCK
#endif
		)
		continue;
	    perror ("while receiving data from client");
	    delete data;
	    return (NULL);
	}
	else
	if (got == 0)
	{
	    delete data;
	    return (NULL);
	}
#endif
	len -= got;
	ptr += got;
    } while (len > 0);

    return (data);
}

void S9xNetPlaySendHeartBeat ()
{
    int len = NETPLAY_SERVER_TO_CLIENT_HEADER_SIZE + 5 * sizeof (uint32);
    char data [NETPLAY_SERVER_TO_CLIENT_HEADER_SIZE + 5 * sizeof (uint32)];
    int i;

    S9xNetPlayServerHeader (data, NETPLAY_SERVER_JOYPAD_UPDATE, len);

    for (i = 0; i < 5; i++)
    {
	char *ptr = &data [NETPLAY_SERVER_JOYPAD_OFFSET + i * sizeof (uint32)];
	WRITE_LONG (ptr, joypads [i]);
    }
    S9xNetPlaySendToAllClients (data, len);
}

void S9xNetPlaySendToAllClients (char *data, int len)
{
    int i;

    for (i = 0; i < MAX_CLIENTS; i++)
    {
	if (clients [i] != NO_CONNECTION)
	{
	    if (!S9xNetPlaySend (clients [i], data, len))
	    {
		S9xNetPlayShutdownClient (i);
		break;
	    }
	}
    }
}

void S9xNetPlayProcessClient (int which1)
{
    int len;
    char *data = S9xNetPlayReceivePacket (clients [which1], len);
    char *hello;

    if (data)
    {
	switch (data [NETPLAY_CLIENT_OPCODE_OFFSET])
	{
	case NETPLAY_CLIENT_HELLO:
	    if (num_connected_clients == 1)
	    {
		READ_LONG (&data [NETPLAY_CLIENT_TO_SERVER_HEADER_SIZE], 
			   frame_time);
		strncpy (ROMName, &data [NETPLAY_CLIENT_TO_SERVER_HEADER_SIZE + 4],
			 29);
		ROMName [29] = 0;
	    }
	    len = NETPLAY_SERVER_TO_CLIENT_HEADER_SIZE + 1 + strlen (ROMName) + 1;
	    hello = new char [len];
	    S9xNetPlayServerHeader (hello, NETPLAY_SERVER_HELLO, len);
	    hello [NETPLAY_SERVER_TO_CLIENT_HEADER_SIZE] = which1;
	    strcpy (hello + NETPLAY_SERVER_TO_CLIENT_HEADER_SIZE + 1, ROMName);
	    if (!S9xNetPlaySend (clients [which1], hello, len))
		S9xNetPlayShutdownClient (which1);

	    delete hello;
	    if (num_connected_clients > 1)
	    {
		// Send a RESET to all clients
		char reset [NETPLAY_SERVER_TO_CLIENT_HEADER_SIZE];
		S9xNetPlayServerHeader (reset, NETPLAY_SERVER_RESET,
					NETPLAY_SERVER_TO_CLIENT_HEADER_SIZE);

		S9xNetPlaySendToAllClients (reset, NETPLAY_SERVER_TO_CLIENT_HEADER_SIZE);
		gettimeofday (&now, NULL);
		next = now;
		COMPUTE_NEXT(next);
	    }
	    break;

	case NETPLAY_CLIENT_JOYPAD_UPDATE:
	    READ_LONG (&data [NETPLAY_CLIENT_TO_SERVER_HEADER_SIZE], 
		       joypads [which1]);
	    break;
	case NETPLAY_CLIENT_GOODBYE:
	    S9xNetPlayShutdownClient (which1);
	    break;
	}
	delete data;
    }
    else
	S9xNetPlayShutdownClient (which1);
}

void S9xNetPlayAcceptClient (Incomming incomming, bool8 block)
{
#ifdef __DJGPP__
    bool8 accepted = FALSE;

    do
    {
	InetAddress addr;
	Socket *s = new Socket (*incomming, (void *) &addr, sizeof (addr));

	if (s->_Socket)
	{
	    int a = 16384;

	    s->SetOption (&a, SOL_SOCKET, SO_SNDBUF, 4);
	    s->SetOption (&a, SOL_SOCKET, SO_RCVBUF, 4);

	    for (int i = 0; i < MAX_CLIENTS; i++)
	    {
		if (clients [i] == NO_CONNECTION)
		{
		    num_connected_clients++;
		    if (num_connected_clients == 1)
			sequence_num = 0;
		    clients [i] = new ClientData;
		    clients [i]->socket = s;
		    clients [i]->addr = addr;
		    clients [i]->size = sizeof (addr);
		    accepted = TRUE;
		    InetAddress peer;
		    s->GetPeerName (&peer, sizeof (InetAddress));
		    sprintf (hostnames [i], "%u.%u.%u.%u\r\n",
			     peer.address & 0xff, (peer.address >> 8) & 0xff,
			     (peer.address >> 16) & 0xff,
			     (peer.address >> 24) & 0xff);
		    printf ("Player %d on %s connected\n", i + 1, 
			    hostnames [i]);
		    return;
		}
	    }
	    delete s;
	    return;
	}
	else
	    delete s;
	if (block)
	    __dpmi_yield ();
    } while (!accepted && block);

#else
    struct sockaddr_in remote_address;
    struct linger val2;
    struct hostent *host;
    int new_fd;
    int len;
    int i;
    
    memset (&remote_address, 0, sizeof (remote_address));
    len = sizeof (remote_address);
    new_fd = accept (incomming, (struct sockaddr *) &remote_address, &len);
    val2.l_onoff = 1;
    val2.l_linger = 0;
    if (setsockopt (new_fd, SOL_SOCKET, SO_LINGER,
		    (char *) &val2, sizeof (val2)) < 0)
    {
	perror ("setsockopt LINGER");
	exit (1);
    }

    for (i = 0; i < MAX_CLIENTS; i++)
    {
	if (clients [i] == NO_CONNECTION)
	{
	    clients [i] = new_fd;
	    num_connected_clients++;
	    break;
	}
    }
    if (i >= MAX_CLIENTS)
    {
	close (new_fd);
	return;
    }

    if (num_connected_clients == 1)
	sequence_num = 0;

    strcpy (hostnames [i], "unknown");
    if (remote_address.sin_family == AF_INET)
    {
	host = gethostbyaddr ((char *) &remote_address.sin_addr,
			      sizeof (remote_address.sin_addr), AF_INET);
	if (host)
	{
	    printf ("Player %d on %s connected\n", i + 1, host->h_name);
	    strncpy (hostnames [i], host->h_name, MAX_HOSTNAME_LEN - 1);
	    hostnames [i][MAX_HOSTNAME_LEN - 1] = 0;
	}
    }
#endif
}

static void Usage ()
{
    printf ("s9xserver: Usage: s9xserver [-port socket_port_number]\n");
    exit (1);
}

int main (int argc, char **argv)
{
#ifndef __DJGPP__
    fd_set read_fds;
    struct sockaddr_in address;
    int incomming;
    int max_fd;
#endif

    struct timeval timeout;
    int val;
    int res;
    int i;
    int port = NETPLAY_DEFAULT_PORT;
    char *port_env;
    
    if (port_env = getenv ("S9XPORT"))
	port = atoi (port_env);

    for (i = 1; i < argc; i++)
    {
	if (strcasecmp (argv [i], "-port") == 0 ||
	    strcasecmp (argv [i], "-p") == 0)
	{
	    if (i + 1 < argc)
		port = atoi (argv [++i]);
	    else
		Usage ();
	}
	else
	if (strcasecmp (argv [i], "-help") == 0)
	{
	    printf ("S9xserver v%d\n", NETPLAY_VERSION);
	    printf ("\
Allows up to five snes9x SNES emulators to connect to it to enable\n\
multi-player networked game play of SNES games which were originally\n\
designed for multi-player game play.\n");
	    exit (0);
	}
	else
	    Usage ();
    }

    for (i = 0; i < MAX_CLIENTS; i++)
	clients [i] = NO_CONNECTION;
	
    num_connected_clients = 0;
    sequence_num = 0;

#ifdef __DJGPP__
    SocketInit ();

    InetAddress addr;

    memset (&addr, 0, sizeof (InetAddress));
    addr.family = AF_INET;
    addr.port = htons (port);
    addr.address = htonl (0);

    Socket *incomming = new Socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
    incomming->Bind (&addr, sizeof (addr));
    incomming->Listen (5);

    do
    {
	if (num_connected_clients == 0)
	{
	    S9xNetPlayAcceptClient (incomming, TRUE);

	    gettimeofday (&now, NULL);
	    next = now;
	    COMPUTE_NEXT(next);
	}

	gettimeofday (&now, NULL);
	if (timercmp (&now, &next, >))
	{
	    COMPUTE_NEXT(next);
	    S9xNetPlaySendHeartBeat ();
	    gettimeofday (&now, NULL);
	}
	if (next.tv_usec > now.tv_usec)
	{
	    timeout.tv_usec = 1000000 + next.tv_usec - now.tv_usec;
	    timeout.tv_sec = next.tv_sec - 1 - now.tv_sec;
	}
	else
	{
	    timeout.tv_usec = next.tv_usec - now.tv_usec;
	    timeout.tv_sec = next.tv_sec - now.tv_sec;
	}
	S9xNetPlayAcceptClient (incomming, FALSE);
	for (i = 0; i < MAX_CLIENTS; i++)
	{
	    if (clients [i] != NO_CONNECTION)
	    {
		int arg = 0;
		clients [i]->socket->Ioctl (FIONREAD, arg);
		if (arg)
		    S9xNetPlayProcessClient (i);
	    }
	}
	__dpmi_yield ();
    } while (!kbhit ());
    
    for (i = 0; i < MAX_CLIENTS; i++)
	S9xNetPlayShutdownClient (i);

    delete incomming;

    return (0);
#else
    signal (SIGPIPE, SIG_IGN);
    if ((incomming = socket (AF_INET, SOCK_STREAM, 0)) < 0)
    {
	perror ("NetPlay Server: Can't create listening socket");
	exit (1);
    }
    
    val = 1;
    setsockopt (incomming, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof (val));

    memset (&address, 0, sizeof (address));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = htonl (INADDR_ANY);
    address.sin_port = htons (port);
    if (bind (incomming, (struct sockaddr *) &address, sizeof (address)) < 0)
    {
	perror ("NetPlay Server: Can't bind socket to port number");
	exit (1);
    }

    if (listen (incomming, MAX_CLIENTS) < 0)
    {
	perror ("NetPlay Server: Can't get new socket to listen");
	exit (1);
    }

    do
    {
	if (num_connected_clients == 0)
	{
	    S9xNetPlayAcceptClient (incomming, TRUE);

	    gettimeofday (&now, NULL);
	    next = now;
	    COMPUTE_NEXT(next);
	}

	max_fd = incomming;
	
	FD_ZERO (&read_fds);
	FD_SET (incomming, &read_fds);
	for (i = 0; i < MAX_CLIENTS; i++)
	{
	    if (clients [i] != NO_CONNECTION)
	    {
		FD_SET (clients [i], &read_fds);
		if (clients [i] > max_fd)
		    max_fd = clients [i];
	    }
	}

	gettimeofday (&now, NULL);
	if (timercmp (&now, &next, >))
	{
	    COMPUTE_NEXT(next);
	    S9xNetPlaySendHeartBeat ();
	    gettimeofday (&now, NULL);
	}
	if (next.tv_usec > now.tv_usec)
	{
	    timeout.tv_usec = 1000000 + next.tv_usec - now.tv_usec;
	    timeout.tv_sec = next.tv_sec - 1 - now.tv_sec;
	}
	else
	{
	    timeout.tv_usec = next.tv_usec - now.tv_usec;
	    timeout.tv_sec = next.tv_sec - now.tv_sec;
	}
	res = select (max_fd + 1, &read_fds, NULL, NULL, &timeout);
	if (res == 0)
	{
	    COMPUTE_NEXT(next);
	    S9xNetPlaySendHeartBeat ();
	}
	else
	if (res > 0)
	{
	    if (FD_ISSET (incomming, &read_fds))
		S9xNetPlayAcceptClient (incomming, FALSE);
	    for (i = 0; i < MAX_CLIENTS; i++)
	    {
		if (clients [i] != NO_CONNECTION && FD_ISSET (clients [i], &read_fds))
		    S9xNetPlayProcessClient (i);
	    }
	}
    } while (1);
    
    close (incomming);

    return (0);
#endif
}
