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

#ifdef NETPLAY_SUPPORT

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

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

typedef struct
{
    Socket *socket;
    InetAddress addr;
    int size;
} ServerData;
#else

#include <netdb.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#ifdef __SVR4
#include <sys/stropts.h>
#endif
#endif

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

static void *server = (void *) -1;
static int server_sequence_no = 0;
static uint32 NetPlayJoypads [5] = {0};

void S9xNetPlayClientHeader (char *header, uint8 opcode, uint32 len)
{
    strcpy (header, NETPLAY_MAGIC);
    header [NETPLAY_CLIENT_VERSION_OFFSET] = NETPLAY_VERSION;
    header [NETPLAY_CLIENT_OPCODE_OFFSET] = opcode;
    WRITE_LONG (&header [NETPLAY_CLIENT_LEN_OFFSET], len);
    WRITE_LONG (&header [NETPLAY_CLIENT_SEQUENCE_NO_OFFSET], server_sequence_no);
}

bool8 S9xNetPlayConnectToServer (const char *hostname, int port,
				 const char *rom_name, int &player_number)
{
#ifdef __DJGPP__
    static bool8 first_time = TRUE;
    if (first_time)
    {
	printf ("Initialising socket library...");
	fflush (stdout);
	SocketInit ();
	first_time = FALSE;
    }
    InetAddress addr;
    memset (&addr, 0, sizeof (InetAddress));
    addr.family = AF_INET;
    addr.port = htons (port);

#if 1
    int a, b, c, d;
    if (sscanf (hostname, "%d.%d.%d.%d", &a, &b, &c, &d) != 4)
    {
	fprintf (stderr, "Can't extract IP address from '%s'\n", hostname);
	return (FALSE);
    }
    addr.address = dotaddr (a, b, c, d);
#else
    int a = Resolve ((char *) hostname);
    addr.address = htonl (a);
#endif

    Socket *s = new Socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

    PrintIP (addr);
    int z = 16384;
    s->SetOption (&z, SOL_SOCKET, SO_SNDBUF, 4);
    s->SetOption (&z, SOL_SOCKET, SO_RCVBUF, 4);

    s->Connect (&addr, sizeof (InetAddress));

    printf ("\nSnes9x waiting for server connection to establish...");
    fflush (stdout);
    s->_ErrorDisplay = 0;

    InetAddress peer;
    do
    {
	//__dpmi_yield ();
	s->GetPeerName (&peer, sizeof (InetAddress));
    } while (!kbhit () && s->_Error == WSAENOTCONN);

    ServerData *srv = new ServerData;
    srv->socket = s;
    srv->addr = addr;
    srv->size = sizeof (InetAddress);
    server = (void *) srv;
#else
    struct sockaddr_in address;
    struct hostent *hostinfo;
    unsigned int addr;
    
    address.sin_family = AF_INET;
    address.sin_port = htons (port);
    if ((addr = inet_addr (hostname)) == -1)
    {
	if (hostinfo = gethostbyname (hostname))
	{
	    memcpy ((char *)&address.sin_addr, hostinfo->h_addr,
		    hostinfo->h_length);
	}
	else
	{
	    fprintf (stderr, "Can't resolve IP address of '%s'\n", hostname);
	    return (FALSE);
	}
    }
    else
    {
	memcpy ((char *)&address.sin_addr, &addr, sizeof (addr));
    }

    int s;

    if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0)
    {
	perror ("Cannot create socket to talk to server");
	return (FALSE);
    }

    server = (void *) s;
    if (connect (s, (struct sockaddr *) &address, sizeof (address)) < 0)
    {
	perror ("Can't connect to server");
	S9xNetPlayDisconnect ();
	return (FALSE);
    }
#endif

    /* Send the server a NETPLAY_CLIENT_HELLO */
    int len = NETPLAY_CLIENT_TO_SERVER_HEADER_SIZE + 4 +
	      strlen (rom_name) + 1;
    char *tmp = new char [len];
    S9xNetPlayClientHeader (tmp, NETPLAY_CLIENT_HELLO, len);
    WRITE_LONG (&tmp [NETPLAY_CLIENT_TO_SERVER_HEADER_SIZE],
		Settings.FrameTime);
    strcpy (tmp + NETPLAY_CLIENT_TO_SERVER_HEADER_SIZE + 4, rom_name);

    if (!S9xNetPlaySend (server, tmp, len))
    {
	perror ("Can't send 'hello' message to server");
	S9xNetPlayDisconnect ();
	delete tmp;
	return (FALSE);
    }
    delete tmp;

again:
    char *hello = S9xNetPlayReceivePacket (server, len);
    if (!hello)
    {
	perror ("No server 'hello' message received");
	S9xNetPlayDisconnect ();
	return (FALSE);
    }

    if (strncmp (hello, NETPLAY_MAGIC, 3) != 0 ||
	hello [NETPLAY_SERVER_VERSION_OFFSET] != NETPLAY_VERSION)
    {
	fprintf (stderr, "NetPlay error: Incorrect format message received.\n");
	delete hello;
	S9xNetPlayDisconnect ();
	return (FALSE);
    }

    if (hello [NETPLAY_SERVER_OPCODE_OFFSET] != NETPLAY_SERVER_HELLO)
    {
	delete hello;
	goto again;
    }
    if (strcmp (hello + NETPLAY_SERVER_TO_CLIENT_HEADER_SIZE + 1, rom_name) != 0)
    {
	fprintf (stderr, "\
NetPlay error: You're running the wrong game!\n\
The server is playing \"%s\", you're playing \"%s\"\n",
		 hello + NETPLAY_SERVER_TO_CLIENT_HEADER_SIZE + 1, rom_name);
	S9xNetPlayDisconnect ();
	delete hello;
	return (FALSE);
    }
    player_number = hello [NETPLAY_SERVER_TO_CLIENT_HEADER_SIZE];
    delete hello;

    return (TRUE);
}

bool8 S9xNetPlayCheckForHeartBeat ()
{
    char *data;
    int len;
    int arg = 0;

again:
#ifdef __DJGPP__
    ((ServerData *) server)->socket->Ioctl (FIONREAD, arg);
    if (arg == 0)
	return (FALSE);
#else
#ifdef I_NREAD
    if (ioctl ((int) server, I_NREAD, &arg) < 0 || !arg)
	return (FALSE);
#else
    if (ioctl ((int) server, FIONREAD, &arg) < 0 || !arg)
	return (FALSE);
#endif
#endif

    if (data = S9xNetPlayReceivePacket (server, len))
    {
	if (data [NETPLAY_SERVER_OPCODE_OFFSET] != NETPLAY_SERVER_JOYPAD_UPDATE)
	{
	    switch (data [NETPLAY_SERVER_OPCODE_OFFSET])
	    {
	    case NETPLAY_SERVER_RESET:
		S9xReset ();
		delete data;
		return (TRUE);

	    case NETPLAY_SERVER_PAUSE:
	    case NETPLAY_SERVER_ROM_NAME:
		break;
	    }
	    delete data;
	    goto again;
	}

	/* Update the Joypad status */
	for (int i = 0; i < 5; i++)
	{
	    READ_LONG (&data [NETPLAY_SERVER_JOYPAD_OFFSET + i * sizeof (uint32)],
		       NetPlayJoypads [i]);
	}
	delete data;
	return (TRUE);
    }
    return (FALSE);
}

uint32 S9xNetPlayGetJoypad (int which1)
{
    if (Settings.NetPlay && which1 < 5)
	return (NetPlayJoypads [which1]);

    return (0);
}

bool8 S9xNetPlaySendJoypadUpdate (uint32 joypad)
{
    int len = NETPLAY_CLIENT_TO_SERVER_HEADER_SIZE + 4;
    char *tmp = new char [len];

    S9xNetPlayClientHeader (tmp, NETPLAY_CLIENT_JOYPAD_UPDATE, len);
    joypad |= 0x80000000;

    WRITE_LONG (&tmp [NETPLAY_CLIENT_TO_SERVER_HEADER_SIZE], joypad);
    if (!S9xNetPlaySend (server, tmp, len))
    {
	perror ("Can't send 'joypad update' message to server");
	S9xNetPlayDisconnect ();
	delete tmp;
	return (FALSE);
    }
    delete tmp;
    return (TRUE);
}

void S9xNetPlayDisconnect ()
{
#ifdef __DJGPP__
    if (server)
    {
	ServerData *srv = (ServerData *) server;
	srv->socket->ShutDown (0);
	delete srv->socket;
	srv->socket = 0;
	delete srv;
	server = (void *) NULL;
    }
#else    
    close ((int) server);
    server = (void *) -1;
#endif
}

bool8 S9xNetPlaySend (void *fd, const char *data, int len)
{
#ifdef __DJGPP__
    ServerData *srv = (ServerData *) fd;

    do
    {
	int sent;
	sent = srv->socket->Send ((void *) data, len, 0, (void *) &srv->addr, 
				  srv->size);
	if (sent == 0)
	    return (FALSE);
	data += sent;
	len -= sent;
    } while (len > 0);

#else
    do
    {
	int sent;
	sent = write ((int) fd, data, len);
	if (sent < 0)
	{
	    if (errno == EINTR 
#ifdef EAGAIN
		|| errno == EAGAIN
#endif
#ifdef EWOULDBLOCK
		|| errno == EWOULDBLOCK
#endif
		)
		continue;
	    perror ("Write to server failed");
	    return (FALSE);
	}
	else
	if (sent == 0)
	    return (FALSE);
	len -= sent;
	data += sent;
    } while (len > 0);
#endif

    return (TRUE);
}

char *S9xNetPlayReceivePacket (void *fd, int &len_return)
{
    char header [NETPLAY_SERVER_TO_CLIENT_HEADER_SIZE];
    char *ptr = header;
    int done = 0;
    int len = NETPLAY_SERVER_TO_CLIENT_HEADER_SIZE;
#ifdef __DJGPP__
    ServerData *srv = (ServerData *) fd;
#endif

    do
    {
#ifdef __DJGPP__
	int got = srv->socket->Recv ((void *) ptr, len, 0, 
				     (void *) &srv->addr, srv->size);
	if (got < 0)
	{
	    fprintf (stderr, "Error while receiving header from server\n");
	    return (NULL);
	}
#else
	int got = read ((int) 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 server");
	    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_SERVER_VERSION_OFFSET] != NETPLAY_VERSION)
    {
	return (NULL);
    }
    
    READ_LONG (&header [NETPLAY_SERVER_LEN_OFFSET], len);
    READ_LONG (&header [NETPLAY_SERVER_SEQUENCE_NO_OFFSET], server_sequence_no);
    len_return = len;
    char *data = new char [len];
    memmove (data, header, NETPLAY_SERVER_TO_CLIENT_HEADER_SIZE);
    ptr = &data [NETPLAY_SERVER_TO_CLIENT_HEADER_SIZE];
    len -= NETPLAY_SERVER_TO_CLIENT_HEADER_SIZE;

    if (len > 0)
    {
	do
	{
#ifdef __DJGPP__
	    int got = srv->socket->Recv ((void *) ptr, len, 0, (void *) &srv->addr, 
					 srv->size);
	    if (got < 0)
	    {
		fprintf (stderr, "Error while receiving data from server\n");
		delete data;
		return (NULL);
	    }
#else
	    int got = read ((int) 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 server");
		delete data;
		return (NULL);
	    }
	    else
	    if (got == 0)
	    {
		delete data;
		return (NULL);
	    }
#endif // __DJGPP__
	    len -= got;
	    ptr += got;
	} while (len > 0);
    }

    return (data);
}
#endif
