/*****************************************************************************\
     Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
                This file is licensed under the Snes9x License.
   For further information, consult the LICENSE file in the root directory.
\*****************************************************************************/

/***********************************************************************************
  SNES9X for Mac OS (c) Copyright John Stiles

  Snes9x for Mac OS X

  (c) Copyright 2001 - 2011  zones
  (c) Copyright 2002 - 2005  107
  (c) Copyright 2002         PB1400c
  (c) Copyright 2004         Alexander and Sander
  (c) Copyright 2004 - 2005  Steven Seeger
  (c) Copyright 2005         Ryan Vogt
 ***********************************************************************************/


#include "snes9x.h"
#include "memmap.h"

#include <sys/time.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pthread.h>
#include <semaphore.h>

#include "mac-prefix.h"
#include "mac-controls.h"
#include "mac-dialog.h"
#include "mac-joypad.h"
#include "mac-keyboard.h"
#include "mac-os.h"
#include "mac-stringtools.h"
#include "mac-netplay.h"
#include "mac-server.h"

#ifdef SELF_TEST
#include <sys/un.h>
#endif

#define KeyIsPressed(km, k)	(1 & (((unsigned char *) km) [(k) >> 3] >> ((k) & 7)))

enum
{
	kNPSDialogNone,
	kNPSDialogInit,
	kNPSDialogProcess,
	kNPSDialogDone,
	kNPSDialogCancel
};

typedef struct
{
	volatile bool8	listenloop;
	volatile uint32 phasecount;
	volatile uint32 phasespan;
	volatile uint8  header;
	int				socket;
	int				numplayers;

	bool8			dialogcancel;
	int				dialogprocess;
}	serverState;

typedef struct
{
	volatile bool8	padloop;
	volatile bool8	exitsgn;
	bool8			online;
	bool8			ready;
	int				socket;
	int				client;
	int				player;
	char			ip[256];
	char			name[256];
}	clientsState;

static char n_sememu[NP_MAX_PLAYERS][30] =
{
	"/tmp/s9x_s_emu_semaphore1",
	"/tmp/s9x_s_emu_semaphore2",
	"/tmp/s9x_s_emu_semaphore3",
	"/tmp/s9x_s_emu_semaphore4",
	"/tmp/s9x_s_emu_semaphore5"
};

static char n_sempad[NP_MAX_PLAYERS][30] =
{
	"/tmp/s9x_s_pad_semaphore1",
	"/tmp/s9x_s_pad_semaphore2",
	"/tmp/s9x_s_pad_semaphore3",
	"/tmp/s9x_s_pad_semaphore4",
	"/tmp/s9x_s_pad_semaphore5"
};

static serverState  npserver;
static clientsState npplayer[NP_MAX_PLAYERS];

static uint32		npactvpad[NP_MAX_PLAYERS][64],	// [player number][]
					nprecvpad[NP_MAX_PLAYERS][64],	// [player number][]
					npsendpad[NP_MAX_PLAYERS][64],	// [player number][]
					npcachpad[64];

static sem_t		*sememu[NP_MAX_PLAYERS],
					*sempad[NP_MAX_PLAYERS];

static pthread_t	listenthread,
					processthread,
					padthread[NP_MAX_PLAYERS];

static int NPServerAcceptClient (int);
static int NPServerGetMesFromClient (int);
static void NPServerBeginListenLoop (void);
static void NPServerEndListenLoop (void);
static void NPServerDetachProcessThread (void);
static void NPServerShutdownClient (int);
static void NPServerAllotPlayers (void);
static void NPServerSendPlayerList (void);
static void NPServerWaitStartReply (void);
static void NPServerSetPhaseSpan (void);
static bool8 NPServerSendMesToClient (int, int);
static bool8 NPServerGetNameFromClient (int);
static bool8 NPServerSendROMInfoToClient (int);
static bool8 NPServerSendSRAMToClient (int);
static bool8 NPServerSendPlayerListToClient (int);
static void * NPServerListenLoop (void *);
static void * NPServerProcessThread (void *);
static void * NPServerNetPlayThread (void *);
static void NPServerDialogTimerHandler (EventLoopTimerRef, void *);
static OSStatus NPServerDialogEventHandler (EventHandlerCallRef, EventRef, void *);


bool8 NPServerDialog (void)
{
	OSStatus	err;
	IBNibRef	nibRef;

	npserver.dialogcancel = true;

	err = CreateNibReference(kMacS9XCFString, &nibRef);
	if (err == noErr)
	{
		WindowRef	tWindowRef;

		err = CreateWindowFromNib(nibRef, CFSTR("ClientList"), &tWindowRef);
		if (err == noErr)
		{
			EventHandlerRef		eref;
			EventLoopTimerRef	tref;
			EventHandlerUPP		eventUPP;
			EventLoopTimerUPP	timerUPP;
			EventTypeSpec		windowEvents[] = { { kEventClassCommand, kEventCommandProcess      },
												   { kEventClassCommand, kEventCommandUpdateStatus } };
			HIViewRef			ctl;
			HIViewID			cid = { 'Chse', 0 };

			npserver.dialogprocess = kNPSDialogInit;

			eventUPP = NewEventHandlerUPP(NPServerDialogEventHandler);
			err = InstallWindowEventHandler(tWindowRef, eventUPP, GetEventTypeCount(windowEvents), windowEvents, (void *) tWindowRef, &eref);

			timerUPP = NewEventLoopTimerUPP(NPServerDialogTimerHandler);
			err = InstallEventLoopTimer(GetCurrentEventLoop(), 0.0f, 0.1f, timerUPP, (void *) tWindowRef, &tref);

			HIViewFindByID(HIViewGetRoot(tWindowRef), cid, &ctl);
			HIViewSetVisible(ctl, false);

			MoveWindowPosition(tWindowRef, kWindowServer, false);
			ShowWindow(tWindowRef);
			err = RunAppModalLoopForWindow(tWindowRef);
			HideWindow(tWindowRef);
			SaveWindowPosition(tWindowRef, kWindowServer);

			err = RemoveEventLoopTimer(tref);
			DisposeEventLoopTimerUPP(timerUPP);

			err = RemoveEventHandler(eref);
			DisposeEventHandlerUPP(eventUPP);

			CFRelease(tWindowRef);
		}

		DisposeNibReference(nibRef);
	}

	return (!npserver.dialogcancel);
}

static void NPServerDialogTimerHandler (EventLoopTimerRef inTimer, void *userData)
{
	WindowRef	window = (WindowRef) userData;
	CFStringRef	ref;
	HIViewRef	ctl, root;
	HIViewID	cid;
	int			n = 0;

	root = HIViewGetRoot(window);

	for (int c = 0; c < NP_MAX_PLAYERS; c++)
	{
		cid.id = c;

		cid.signature = 'Pnum';
		HIViewFindByID(root, cid, &ctl);
		if (npplayer[c].ready)
		{
			char	num[4];

			num[0] = '1' + n;
			num[1] = 'P';
			num[2] = 0;
			SetStaticTextCStr(ctl, num, true);
			n++;
		}

		cid.signature = 'IP__';
		HIViewFindByID(root, cid, &ctl);
		if (npplayer[c].online)
		{
			ref = CFStringCreateWithCString(kCFAllocatorDefault, npplayer[c].ip, kCFStringEncodingUTF8);
			if (ref)
			{
				SetStaticTextCFString(ctl, ref, true);
				CFRelease(ref);
			}
			else
				SetStaticTextCFString(ctl, CFSTR("unknown"), true);
		}
		else
			SetStaticTextCFString(ctl, CFSTR(""), true);

		cid.signature = 'Name';
		HIViewFindByID(root, cid, &ctl);
		if (npplayer[c].online)
		{
			ref = CFStringCreateWithCString(kCFAllocatorDefault, npplayer[c].name, kCFStringEncodingUTF8);
			if (ref)
			{
				SetStaticTextCFString(ctl, ref, true);
				CFRelease(ref);
			}
			else
				SetStaticTextCFString(ctl, CFSTR("unknown"), true);
		}
		else
			SetStaticTextCFString(ctl, CFSTR(""), true);

		cid.signature = 'Stat';
		HIViewFindByID(root, cid, &ctl);
		if (npplayer[c].online)
		{
			if (npplayer[c].ready)
				ref = CFCopyLocalizedString(CFSTR("NPReady"), "NPReady");
			else
				ref = CFCopyLocalizedString(CFSTR("NPConnecting"), "NPConnecting");

			if (ref)
			{
				SetStaticTextCFString(ctl, ref, true);
				CFRelease(ref);
			}
			else
				SetStaticTextCFString(ctl, CFSTR("error"), true);
		}
		else
			SetStaticTextCFString(ctl, CFSTR(""), true);
	}

	switch (npserver.dialogprocess)
	{
		case kNPSDialogNone:
			break;

		case kNPSDialogInit:
			NPNotification("  kNPSDialogInit", -1);
			npserver.dialogprocess = kNPSDialogNone;
			NPServerBeginListenLoop();
			break;

		case kNPSDialogProcess:
			NPNotification("  kNPSDialogProcess", -1);
			npserver.dialogprocess = kNPSDialogNone;
			NPServerEndListenLoop();
			cid.id = 0;
			cid.signature = 'Chse';
			HIViewFindByID(root, cid, &ctl);
			HIViewSetVisible(ctl, true);
			NPServerDetachProcessThread();
			break;

		case kNPSDialogDone:
			NPNotification("  kNPSDialogDone", -1);
			npserver.dialogprocess = kNPSDialogNone;
			npserver.dialogcancel = false;
			QuitAppModalLoopForWindow(window);
			break;

		case kNPSDialogCancel:
			NPNotification("  kNPSDialogCancel", -1);
			npserver.dialogprocess = kNPSDialogNone;
			NPServerEndListenLoop();
			npserver.dialogcancel = true;
			QuitAppModalLoopForWindow(window);
			break;
	}
}

static OSStatus NPServerDialogEventHandler (EventHandlerCallRef inHandlerRef, EventRef inEvent, void *inUserData)
{
	OSStatus	err, result = eventNotHandledErr;
	WindowRef	tWindowRef = (WindowRef) inUserData;

	switch (GetEventClass(inEvent))
	{
		case kEventClassCommand:
			switch (GetEventKind(inEvent))
			{
				HICommand	tHICommand;

				case kEventCommandUpdateStatus:
					err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &tHICommand);
					if (err == noErr && tHICommand.commandID == 'clos')
					{
						UpdateMenuCommandStatus(false);
						result = noErr;
					}

					break;

				case kEventCommandProcess:
					err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &tHICommand);
					if (err == noErr)
					{
						switch (tHICommand.commandID)
						{
							case 'OKAY':
								HIViewRef	ctl, root;
								HIViewID	cid;

								root = HIViewGetRoot(tWindowRef);
								cid.id = 0;
								cid.signature = 'OKAY';
								HIViewFindByID(root, cid, &ctl);
								DeactivateControl(ctl);
								cid.signature = 'CNSL';
								HIViewFindByID(root, cid, &ctl);
								DeactivateControl(ctl);

								npserver.dialogprocess = kNPSDialogProcess;
								result = noErr;
								break;

							case 'CNSL':
								npserver.dialogprocess = kNPSDialogCancel;
								result = noErr;
								break;
						}
					}

					break;
			}

			break;
	}

	return (result);
}

void NPServerInit (void)
{
	npserver.listenloop = false;
	npserver.phasecount = 0;
	npserver.phasespan  = 0;
	npserver.header     = 0;
	npserver.socket     = -1;
	npserver.numplayers = 0;

	for (int i = 0; i < NP_MAX_PLAYERS; i++)
	{
		for (int j = 0; j < 64; j++)
		{
			npactvpad[i][j] = 0;
			nprecvpad[i][j] = 0;
			npsendpad[i][j] = 0;
		}
	}

	for (int j = 0; j < 64; j++)
		npcachpad[j] = 0;

	for (int c = 0; c < NP_MAX_PLAYERS; c++)
	{
		npplayer[c].padloop = false;
		npplayer[c].exitsgn = false;
		npplayer[c].online  = false;
		npplayer[c].ready   = false;
		npplayer[c].socket  = -1;
		npplayer[c].client  = 0;
		npplayer[c].player  = 0;
		npplayer[c].ip[0]   = 0;
		npplayer[c].name[0] = 0;
	}

	npplayer[0].online = true;
	npplayer[0].ready  = true;

	char	name[256];
	if (gethostname(name, 256) == 0)
	{
		struct hostent	*hn;
		if ((hn = gethostbyname(name)) != NULL)
		{
			struct in_addr	addr;
			memcpy(&addr, hn->h_addr_list[0], sizeof(struct in_addr));
			strcpy(npplayer[0].ip, inet_ntoa(addr));
		}
		else
			strcpy(npplayer[0].ip, "unknown");
	}
	else
		strcpy(npplayer[0].ip, "unknown");

	CFStringRef	ref;
	ref = CFCopyLocalizedString(CFSTR("NPServerName"), "NPServer");
	if (ref)
	{
		Boolean	r;
		r = CFStringGetCString(ref, npplayer[0].name, 256, kCFStringEncodingUTF8);
		if (!r)
			strcpy(npplayer[0].name, "unknown");

		CFRelease(ref);
	}
	else
		strcpy(npplayer[0].name, "unknown");
}

bool8 NPServerStartServer (int port)
{
#ifndef SELF_TEST
	struct sockaddr_in	address;
#else
	struct sockaddr_un	address;
#endif
	int					v = 1;

	NPNotification("Server: Starting server...", -1);

#ifndef SELF_TEST
	if ((npserver.socket = socket(PF_INET, SOCK_STREAM, 0)) < 0)
#else
	if ((npserver.socket = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
#endif
	{
		NPError("Server: Failed to create listening socket.", 1001);
		return (false);
	}

	if ((setsockopt(npserver.socket, SOL_SOCKET, SO_REUSEADDR, (char *) &v, sizeof(v))) < 0)
	{
		NPError("Server: Failed to set socket option.", 1002);
		return (false);
	}

	memset(&address, 0, sizeof(address));
#ifndef SELF_TEST
	address.sin_family      = AF_INET;
	address.sin_addr.s_addr = htonl(INADDR_ANY);
	address.sin_port        = htons(port);
#else
	address.sun_family      = AF_UNIX;
    strcpy(address.sun_path, SOCK_NAME);
#endif

#ifndef SELF_TEST
	if ((bind(npserver.socket, (struct sockaddr *) &address, sizeof(address))) < 0)
#else
	unlink(SOCK_NAME);
	if ((bind(npserver.socket, (struct sockaddr *) &address, sizeof(address))) < 0)
#endif
	{
		NPError("Server: Failed to bind socket to port number.", 1003);
		return (false);
	}

	if ((listen(npserver.socket, NP_MAX_CLIENTS)) < 0)
	{
		NPError("Server: Failed to get new socket to listen.", 1004);
		return (false);
	}

	npplayer[0].socket = npserver.socket;

	NPNotification("Server: Started server.", -1);
	return (true);
}

void NPServerStopServer (void)
{
	NPNotification("Server: Stopping server...", -1);

	for (int c = 1; c <= NP_MAX_CLIENTS; c++)
		NPServerShutdownClient(c);

	npplayer[0].online  = false;
	npplayer[0].ready   = false;
	npplayer[0].socket  = -1;
	npplayer[0].client  = 0;
	npplayer[0].player  = 0;
	npplayer[0].ip[0]   = 0;
	npplayer[0].name[0] = 0;

	if (npserver.socket != -1)
	{
		close(npserver.socket);
		npserver.socket = -1;
	}

	NPNotification("Server: Stopped server.", -1);
}

static void NPServerShutdownClient (int c)
{
	if (npplayer[c].online)
	{
		NPNotification("Server: Closing client %d connection...", c);

		if (npplayer[c].socket != -1)
		{
			close(npplayer[c].socket);
			npplayer[c].socket = -1;
		}

		npplayer[c].online  = false;
		npplayer[c].ready   = false;
		npplayer[c].client  = 0;
		npplayer[c].player  = 0;
		npplayer[c].ip[0]   = 0;
		npplayer[c].name[0] = 0;

		NPNotification("Server: Client %d has disconnected.", c);
	}
}

static int NPServerAcceptClient (int port)
{
#ifndef SELF_TEST
	struct sockaddr_in	address;
#else
	struct sockaddr_un	address;
#endif
	int					newfd;
	int					c;
	socklen_t			l;

	NPNotification("Server: Accepting new client connection...", -1);

	for (c = 1; c <= NP_MAX_CLIENTS; c++)
		if (!npplayer[c].online)
			break;

	if (c > NP_MAX_CLIENTS)
	{
		NPError("Server: Maximum number of clients have already connected.", 1101);
		return (-1);
	}

	l = sizeof(address);
	memset(&address, 0, l);

	if ((newfd = accept(port, (struct sockaddr *) &address, &l)) < 0)
	{
		NPError("Server: Can't accept client connection.", 1102);
		return (-1);
	}

	npplayer[c].online = true;
	npplayer[c].socket = newfd;

#ifndef SELF_TEST
	if (address.sin_family == AF_INET)
		strcpy(npplayer[c].ip, inet_ntoa(address.sin_addr));
	else
		strcpy(npplayer[c].ip, "unknown");
#else
		strcpy(npplayer[c].ip, "Unix");
#endif

	NPNotification("Server: new client %d has connected.", c);

	return (c);
}

static void NPServerBeginListenLoop (void)
{
	npserver.listenloop = true;
	pthread_create(&listenthread, NULL, NPServerListenLoop, NULL);
}

static void NPServerEndListenLoop (void)
{
	npserver.listenloop = false;
	pthread_join(listenthread, NULL);
}

static void * NPServerListenLoop (void *)
{
	struct timeval	timeout;
	fd_set			readfds;
	int				maxfd;

	NPNotification("Server: Entered listening loop.", -1);

	while (npserver.listenloop)
	{
		FD_ZERO(&readfds);
		maxfd = 0;

		if (npserver.socket != -1)
		{
			FD_SET(npserver.socket, &readfds);
			maxfd = npserver.socket;
		}

		for (int c = 1; c <= NP_MAX_CLIENTS; c++)
		{
			if (npplayer[c].online)
			{
				FD_SET(npplayer[c].socket, &readfds);
				if (maxfd < npplayer[c].socket)
					maxfd = npplayer[c].socket;
			}
		}

		timeout.tv_sec  = 0;
		timeout.tv_usec = 50000;

		if (select(maxfd + 1, &readfds, NULL, NULL, &timeout) > 0)
		{
			for (int c = 1; c <= NP_MAX_CLIENTS; c++)
			{
				if (npplayer[c].online)
				{
					if (FD_ISSET(npplayer[c].socket, &readfds))
					{
						switch (NPServerGetMesFromClient(c))
						{
							case kNPClientNameSent:
								if (!NPServerSendROMInfoToClient(c))
									NPServerShutdownClient(c);
								break;

							case kNPClientROMOpened:
								if (!NPServerSendSRAMToClient(c))
									NPServerShutdownClient(c);
								break;

							case kNPClientSRAMLoaded:
								npplayer[c].ready = true;
								break;

							default:
								NPServerShutdownClient(c);
								break;
						}
					}
				}
			}

			if (FD_ISSET(npserver.socket, &readfds))
			{
				int	client;

				if ((client = NPServerAcceptClient(npserver.socket)) != -1)
				{
					if (!NPServerGetNameFromClient(client))
						NPServerShutdownClient(client);
				}
			}
		}
	}

	NPNotification("Server: Exited listening loop.", -1);

	return (NULL);
}

static bool8 NPServerSendMesToClient (int c, int num)
{
	uint8	mes[2];

	mes[0] = NP_SERVER_MAGIC;
	mes[1] = num;

	if (socket_write(npplayer[c].socket, mes, 2) != 2)
		return (false);

	return (true);
}

static int NPServerGetMesFromClient (int c)
{
	uint8	mes[2];

	if (socket_read(npplayer[c].socket, mes, 2) != 2)
		return (-1);

	if (mes[0] != NP_CLIENT_MAGIC)
		return (-1);

	return ((int) mes[1]);
}

static bool8 NPServerGetNameFromClient (int c)
{
	if (!npplayer[c].online)
		return (false);

	NPNotification("Server: Receiving player name from client %d...", c);

	if (NPServerSendMesToClient(c, kNPServerNameRequest) == false)
	{
		NPError("Server: Failed to send messsage to client.", 1201);
		return (false);
	}

	uint8	mes[4];
	uint32	l;

	if (socket_read(npplayer[c].socket, mes, 4) != 4)
	{
		NPError("Server: Failed to receive name size from client.", 1202);
		return (false);
	}

	l = READ_LONG(mes + 0);

	if (socket_read(npplayer[c].socket, (uint8 *) npplayer[c].name, l) != (int) l)
	{
		NPError("Server: Failed to receive name from client.", 1203);
		return (false);
	}

	npplayer[c].name[l] = 0;

	if (NPServerSendMesToClient(c, kNPServerNameReceived) == false)
	{
		NPError("Server: Failed to send messsage to client.", 1204);
		return (false);
	}

	NPNotification("Server: Received player name from client %d.", c);
	return (true);

	// next: kNPClientNameSent
}

static bool8 NPServerSendROMInfoToClient (int c)
{
	if (!npplayer[c].online)
		return (false);

	NPNotification("Server: Sending ROM information to client %d...", c);

	if (NPServerSendMesToClient(c, kNPServerROMInfoWillSend) == false)
	{
		NPError("Server: Failed to send messsage to client.", 1301);
		return (false);
	}

	if (NPServerGetMesFromClient(c) != kNPClientROMInfoWaiting)
	{
		NPError("Server: Failed to receive messsage from client.", 1302);
		return (false);
	}

	uint8	mes[16];
	uint32	l;
	char	drive[_MAX_DRIVE + 1], dir[_MAX_DIR + 1], fname[_MAX_FNAME + 1], ext[_MAX_EXT + 1];

	_splitpath(Memory.ROMFilename, drive, dir, fname, ext);
	l = strlen(fname);

	WRITE_LONG(mes + 0,  Memory.ROMCRC32);
	WRITE_LONG(mes + 4,  deviceSetting);
	WRITE_BYTE(mes + 8,  0);	// reserved
	WRITE_BYTE(mes + 9,  0);	// reserved
	WRITE_BYTE(mes + 10, 0);	// reserved
	WRITE_BYTE(mes + 11, 0);	// reserved
	WRITE_LONG(mes + 12, l);

	if (socket_write(npplayer[c].socket, mes, 16) != 16)
	{
		NPError("Server: Failed to send ROM information to client.", 1303);
		return (false);
	}

	if (socket_write(npplayer[c].socket, (uint8 *) fname, l) != (int) l)
	{
		NPError("Server: Failed to send ROM name to client.", 1304);
		return (false);
	}

	NPNotification("Server: Sent ROM information to client %d.", c);
	return (true);

	// next: kNPClientROMOpened
}

static bool8 NPServerSendSRAMToClient (int c)
{
	if (!npplayer[c].online)
		return (false);

	NPNotification("Server: Sending SRAM to client %d...", c);

	if (NPServerSendMesToClient(c, kNPServerSRAMWillSend) == false)
	{
		NPError("Server: Failed to send messsage to client.", 1401);
		return (false);
	}

	if (NPServerGetMesFromClient(c) != kNPClientSRAMWaiting)
	{
		NPError("Server: Failed to receive messsage from client.", 1402);
		return (false);
	}

	uint8	mes[4];
	uint32	sramsize;

	sramsize = Memory.SRAMSize ? (1 << (Memory.SRAMSize + 3)) * 128 : 0;

	WRITE_LONG(mes + 0, sramsize);

	if (socket_write(npplayer[c].socket, mes, 4) != 4)
	{
		NPError("Server: Failed to send SRAM size to client.", 1403);
		return (false);
	}

	if (sramsize && (socket_write(npplayer[c].socket, Memory.SRAM, sramsize) != (int) sramsize))
	{
		NPError("Server: Failed to send SRAM to client.", 1404);
		return (false);
	}

	NPNotification("Server: Sent SRAM to client %d.", c);
	return (true);

	// next: kNPClientSRAMLoaded
}

static void NPServerDetachProcessThread (void)
{
	pthread_create(&processthread, NULL, NPServerProcessThread, NULL);
	pthread_detach(processthread);
}

static void * NPServerProcessThread (void *)
{
	NPNotification("Server: Entered process thread.", -1);

	NPServerAllotPlayers();
	NPServerSendPlayerList();
	NPServerSetPhaseSpan();
	NPServerWaitStartReply();

	npserver.dialogprocess = kNPSDialogDone;

	NPNotification("Server: Exited process thread.", -1);
	return (NULL);
}

static void NPServerAllotPlayers (void)
{
	int	n = 1;

	for (int c = 1; c <= NP_MAX_CLIENTS; c++)
	{
		if (npplayer[c].ready)
		{
			npplayer[c].client = c;
			npplayer[c].player = n++;
		}
		else
			NPServerShutdownClient(c);
	}

	npplayer[0].client = 0;
	npplayer[0].player = 0;

	npserver.numplayers = n;

	NPNotification("Server: Number of players: %d", n);
}

static bool8 NPServerSendPlayerListToClient (int c)
{
	if (!npplayer[c].online || !npplayer[c].ready)
		return (false);

	NPNotification("Server: Sending player list to client %d...", c);

	if (NPServerSendMesToClient(c, kNPServerPlayerWillSend) == false)
	{
		NPError("Server: Failed to send messsage to client.", 1601);
		return (false);
	}

	if (NPServerGetMesFromClient(c) != kNPClientPlayerWaiting)
	{
		NPError("Server: Failed to receive messsage from client.", 1602);
		return (false);
	}

	for (int i = 0; i < NP_MAX_PLAYERS; i++)
	{
		uint8	mes[10];
		uint32	l;

		l = npplayer[i].ready ? strlen(npplayer[i].name) : 0;

		WRITE_BYTE(mes + 0, (i == c));
		WRITE_BYTE(mes + 1, npplayer[i].ready);
		WRITE_LONG(mes + 2, npplayer[i].player);
		WRITE_LONG(mes + 6, l);

		if (socket_write(npplayer[c].socket, mes, 10) != 10)
		{
			NPError("Server: Failed to send name size to client.", 1603);
			return (false);
		}

		if (l && (socket_write(npplayer[c].socket, (uint8 *) npplayer[i].name, l) != (int) l))
		{
			NPError("Server: Failed to send name to client.", 1604);
			return (false);
		}
	}

	NPNotification("Server: Sent player list to client %d.", c);
	return (true);
}

static void NPServerSendPlayerList (void)
{
	for (int c = 1; c <= NP_MAX_CLIENTS; c++)
	{
		if (npplayer[c].ready)
		{
			if (NPServerSendPlayerListToClient(c) == false)
				NPServerShutdownClient(c);
		}
	}
}

static void NPServerSetPhaseSpan (void)
{
	struct timeval  tv1, tv2;
	uint8			mes[21];
	uint32			dus, dusmax;
	int				l = npserver.numplayers * 4 + 1;

	NPNotification("Server: Testing sending / receiving pad states...", -1);

	dusmax = 0;

	for (int n = 0; n < 5; n++)
	{
		gettimeofday(&tv1, NULL);

		for (int c = 1; c <= NP_MAX_CLIENTS; c++)
		{
			if (npplayer[c].ready)
			{
				if (socket_write(npplayer[c].socket, mes, l) != l)
					NPServerShutdownClient(c);
			}
		}

		for (int c = 1; c <= NP_MAX_CLIENTS; c++)
		{
			if (npplayer[c].ready)
			{
				if (socket_read(npplayer[c].socket, mes, 5) != 5)
					NPServerShutdownClient(c);
			}
		}

		gettimeofday(&tv2, NULL);

		dus = (tv2.tv_sec * 1000000 + tv2.tv_usec) - (tv1.tv_sec * 1000000 + tv1.tv_usec);

		NPNotification("  %d [usec]", dus);

		if (dusmax < dus)
			dusmax = dus;

		usleep(50000);
	}

	NPNotification("Server: Tested sending / receiving pad states.", -1);

	npserver.phasespan = (uint32) Memory.ROMFramesPerSecond * dusmax / 1000000 + 1;
	if (npserver.phasespan > (uint32) Memory.ROMFramesPerSecond)
		npserver.phasespan = (uint32) Memory.ROMFramesPerSecond;

	NPNotification("  phase span: %d (frames)", npserver.phasespan);

	NPNotification("Server: Sending phase span value to clients...", -1);

	WRITE_LONG(mes + 0, npserver.phasespan);

	for (int c = 1; c <= NP_MAX_CLIENTS; c++)
	{
		if (npplayer[c].ready)
		{
			if (socket_write(npplayer[c].socket, mes, 4) != 4)
				NPServerShutdownClient(c);
		}
	}

	NPNotification("Server: Sent phase span value to clients.", -1);
}

static void NPServerWaitStartReply (void)
{
	struct timeval	timeout;
	fd_set			readfds;
	int				maxfd;
	bool8			allok, flag[NP_MAX_PLAYERS];

	NPNotification("Server: Waiting clients reply to start...", -1);

	allok = false;
	for (int c = 1; c <= NP_MAX_CLIENTS; c++)
		flag[c] = false;

	while (!allok)
	{
		FD_ZERO(&readfds);
		maxfd = 0;

		for (int c = 1; c <= NP_MAX_CLIENTS; c++)
		{
			if (npplayer[c].ready)
			{
				FD_SET(npplayer[c].socket, &readfds);
				if (maxfd < npplayer[c].socket)
					maxfd = npplayer[c].socket;
			}
		}

		timeout.tv_sec  = 0;
		timeout.tv_usec = 50000;

		if (select(maxfd + 1, &readfds, NULL, NULL, &timeout) > 0)
		{
			for (int c = 1; c <= NP_MAX_CLIENTS; c++)
			{
				if (npplayer[c].ready)
				{
					if (FD_ISSET(npplayer[c].socket, &readfds))
					{
						if (NPServerGetMesFromClient(c) == kNPClientStartWait)
							flag[c] = true;
						else
							NPServerShutdownClient(c);
					}
				}
			}
		}

		allok = true;
		for (int c = 1; c <= NP_MAX_CLIENTS; c++)
			if (npplayer[c].ready && !flag[c])
				allok = false;
	}

	NPNotification("Server: All clients are ready to start netplay.", -1);
}

void NPServerDetachNetPlayThread (void)
{
	NPNotification("Server: Detaching pad threads...", -1);

	for (int c = 1; c <= NP_MAX_CLIENTS; c++)
	{
		if (npplayer[c].ready)
		{
			npplayer[c].padloop = true;
			npplayer[c].exitsgn = false;

			sememu[c] = sem_open(n_sememu[c], O_CREAT, 0600, 0);
			sempad[c] = sem_open(n_sempad[c], O_CREAT, 0600, 0);

			pthread_create(&padthread[c], NULL, NPServerNetPlayThread, &(npplayer[c].client));
			pthread_detach(padthread[c]);
		}
	}

	NPNotification("Server: Detached pad threads.", -1);
}

void NPServerStopNetPlayThread (void)
{
	NPNotification("Server: Stopping pad threads...", -1);

	for (int c = 1; c <= NP_MAX_CLIENTS; c++)
	{
		if (npplayer[c].ready)
		{
			npplayer[c].padloop = false;
			sem_post(sempad[c]);

			while (!npplayer[c].exitsgn)
				sleep(0);

			sem_unlink(n_sememu[c]);
			sem_unlink(n_sempad[c]);
			sem_close(sememu[c]);
			sem_close(sempad[c]);
		}
	}

	NPNotification("Server: Stopped pad threads.", -1);
}

void NPServerStartClients (void)
{
	NPNotification("Server: Sending start flag to clients...", -1);

	for (int c = 1; c <= NP_MAX_CLIENTS; c++)
	{
		if (npplayer[c].ready)
		{
			if (NPServerSendMesToClient(c, kNPServerStart) == false)
				NPServerShutdownClient(c);
		}
	}

	NPNotification("Server: Sent start flag to clients.", -1);

	npserver.phasecount = 0;
	npserver.header     = 0;

	for (int c = 1; c <= NP_MAX_CLIENTS; c++)
		if (npplayer[c].ready)
			sem_post(sempad[c]);

	NPNotification("Server: Netplay started.", -1);
}

static void * NPServerNetPlayThread (void *p)
{
	uint8	mes[NP_MAX_PLAYERS * 64 * 4 + 1];
	uint8	count = 0;
	int 	c = *((int *) p), l;

	NPNotification("Server: Entered pad thread for client %d.", c);

	while (npplayer[c].padloop)
	{
		sem_wait(sempad[c]);

		WRITE_BYTE(mes + 0, count | npserver.header);

		for (int i = 0; i < npserver.numplayers; i++)
			for (uint32 j = 0; j < npserver.phasespan; j++)
				WRITE_LONG(mes + (i * npserver.phasespan + j) * 4 + 1, npsendpad[i][j]);

		l = npserver.numplayers * npserver.phasespan * 4 + 1;
		if (socket_write(npplayer[c].socket, mes, l) != l)
		{
			NPServerShutdownClient(c);
			npplayer[c].exitsgn = true;
			sem_post(sememu[c]);
			pthread_exit(NULL);
		}

		l = npserver.phasespan * 4 + 1;
		if (socket_read(npplayer[c].socket, mes, l) != l)
		{
			NPServerShutdownClient(c);
			npplayer[c].exitsgn = true;
			sem_post(sememu[c]);
			pthread_exit(NULL);
		}

		for (uint32 j = 0; j < npserver.phasespan; j++)
			nprecvpad[npplayer[c].player][j] = READ_LONG(mes + j * 4 + 1);

		if ((mes[0] & 0xF) != count)
			NPNotification("Server: Warning: Failed to synchronize client %d.", c);

		count = (count + 1) & 0xF;

		sem_post(sememu[c]);
	}

	npplayer[c].exitsgn = true;

	NPNotification("Server: Exited pad thread for client %d.", c);
	return (NULL);
}

void NPServerProcessInput (void)
{
	static uint8	header = 0;
	static uint32	pos    = 0;
	KeyMap  		myKeys;

	if (npserver.phasecount == 0)
	{
		for (int c = 1; c <= NP_MAX_CLIENTS; c++)
			if (npplayer[c].ready)
				sem_wait(sememu[c]);

		for (uint32 j = 0; j < npserver.phasespan; j++)
			nprecvpad[0][j] = npcachpad[j];

		for (int i = 0; i < npserver.numplayers; i++)
		{
			for (uint32 j = 0; j < npserver.phasespan; j++)
			{
				npactvpad[i][j] = npsendpad[i][j];
				npsendpad[i][j] = nprecvpad[i][j];
			}
		}

		if (npserver.header & 0x80)
		{
			for (int i = 0; i < npserver.numplayers; i++)
			{
				npactvpad[i][npserver.phasespan] = 0;
				npsendpad[i][npserver.phasespan] = 0;
			}

			npserver.phasespan++;
			if (npserver.phasespan > (uint32) Memory.ROMFramesPerSecond)
				npserver.phasespan = (uint32) Memory.ROMFramesPerSecond;

			char	str[256];
			sprintf(str, "delay: %d", npserver.phasespan);
			S9xMessage(0, 0, str);
		}
		else
		if (npserver.header & 0x40)
		{
			npserver.phasespan--;
			if (npserver.phasespan == 0)
				npserver.phasespan = 1;

			char	str[256];
			sprintf(str, "delay: %d", npserver.phasespan);
			S9xMessage(0, 0, str);
		}

		npserver.header = header;
		header = 0;
		pos = 0;
	}

 	for (int i = 0; i < npserver.numplayers; i++)
	{
		controlPad[i] = npactvpad[i][pos];
		ControlPadFlagsToS9xReportButtons(i, controlPad[i]);
	}

	GetKeys(myKeys);

	if (ISpKeyIsPressed(kISpEsc) || KeyIsPressed(myKeys, keyCode[kKeyEsc]))
	{
		if (s9xthreadrunning)
		{
			if (!eventQueued)
			{
				PostQueueToSubEventLoop();
				eventQueued = true;
			}
		}
		else
			running = false;

		return;
	}

	uint32	pad = 0;

	JoypadScanDirection(0, &pad);
	if (ISpKeyIsPressed(kISp1PR     ))	pad |= 0x0010;
	if (ISpKeyIsPressed(kISp1PL     ))	pad |= 0x0020;
	if (ISpKeyIsPressed(kISp1PX     ))	pad |= 0x0040;
	if (ISpKeyIsPressed(kISp1PA     ))	pad |= 0x0080;
	if (ISpKeyIsPressed(kISp1PStart ))	pad |= 0x1000;
	if (ISpKeyIsPressed(kISp1PSelect))	pad |= 0x2000;
	if (ISpKeyIsPressed(kISp1PY     ))	pad |= 0x4000;
	if (ISpKeyIsPressed(kISp1PB     ))	pad |= 0x8000;

	if (KeyIsPressed(myKeys, keyCode[k1PR]     ))	pad |= 0x0010;
	if (KeyIsPressed(myKeys, keyCode[k1PL]     ))	pad |= 0x0020;
	if (KeyIsPressed(myKeys, keyCode[k1PX]     ))	pad |= 0x0040;
	if (KeyIsPressed(myKeys, keyCode[k1PA]     ))	pad |= 0x0080;
	if (KeyIsPressed(myKeys, keyCode[k1PRight] ))	pad |= 0x0100;
	if (KeyIsPressed(myKeys, keyCode[k1PLeft]  ))	pad |= 0x0200;
	if (KeyIsPressed(myKeys, keyCode[k1PDown]  ))	pad |= 0x0400;
	if (KeyIsPressed(myKeys, keyCode[k1PUp]    ))	pad |= 0x0800;
	if (KeyIsPressed(myKeys, keyCode[k1PStart] ))	pad |= 0x1000;
	if (KeyIsPressed(myKeys, keyCode[k1PSelect]))	pad |= 0x2000;
	if (KeyIsPressed(myKeys, keyCode[k1PY]     ))	pad |= 0x4000;
	if (KeyIsPressed(myKeys, keyCode[k1PB]     ))	pad |= 0x8000;

	npcachpad[pos] = pad;

	if (KeyIsPressed(myKeys, keyCode[k2PR]))	header |= 0x80;
	if (KeyIsPressed(myKeys, keyCode[k2PL]))	header |= 0x40;

	if (npserver.phasecount == 0)
	{
		npserver.phasecount = npserver.phasespan;

		for (int c = 1; c <= NP_MAX_CLIENTS; c++)
			if (npplayer[c].ready)
				sem_post(sempad[c]);
	}

	npserver.phasecount--;
	pos++;
}
