#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include "SDL.h"
#include "SDL_net.h"
#include "tcputil.h"

typedef struct {
	TCPsocket sock;
	char *name;
} Client;

int running=1;
Client *clients=NULL;
int num_clients=0;
TCPsocket server;

#ifdef WIN32
#define strcasecmp _stricmp
#define strncasecmp _strnicmp

char *strsep(char **stringp, const char *delim)
{
	char *p;
	
	if(!stringp)
		return(NULL);
	p=*stringp;
	while(**stringp && !strchr(delim,**stringp))
		(*stringp)++;
	if(**stringp)
	{
		**stringp='\0';
		(*stringp)++;
	}
	else
		*stringp=NULL;
	return(p);
}

#endif

void send_all(char *buf);
int find_client_name(char *name);

/* this is a simplistic printf-like function the format only takes a string of */
/* chars that represent datatypes. */
/* it mallocs space and prints into that buffer. */
/* it uses a static pointer so that the user doesn't have to free the buffer */
char *mformat(char *format,...)
{
	va_list ap;
	Uint32 len=0;
	static char *str=NULL;
	char *p, *s;
	char c;
	int d;
	unsigned int u;

	if(str)
	{
		free(str);
		str=NULL;
	}
	if(!format)
		return(NULL);
	va_start(ap,format);
	for(p=format; *p; p++)
	{
		switch(*p)
		{
			case 's': /* string */
				s=va_arg(ap, char*);
				str=(char*)realloc(str,((len+strlen(s)+4)/4)*4);
				sprintf(str+len,"%s",s);
				break;
			case 'c': /* char */
				c=(char)va_arg(ap, int);
				str=(char*)realloc(str,len+4);
				sprintf(str+len,"%c",c);
				break;
			case 'd': /* int */
				d=va_arg(ap, int);
				str=(char*)realloc(str,((len+64)/4)*4);
				sprintf(str+len,"%d",d);
				break;
			case 'u': /* unsigned int */
				u=va_arg(ap, unsigned int);
				str=(char*)realloc(str,((len+64)/4)*4);
				sprintf(str+len,"%u",u);
				break;
		}
		/* set len to the new string length */
		if(str)
			len=strlen(str);
		else
			len=0;
	}
	va_end(ap);
	return(str);
}

/* terminate the nick at "bad" characters... */
void fix_nick(char *s)
{
	unsigned int i;

	if((i=strspn(s,"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ|_=+.,:;/\\?!@#$%^&*()~`"))!=strlen(s))
		s[i]='\0';
}

/* test for nice name uniqueness among already connected users */
int unique_nick(char *s)
{
	return(find_client_name(s)==-1);
}

/* add a client into our array of clients */
Client *add_client(TCPsocket sock, char *name)
{
	fix_nick(name);
	if(!strlen(name))
	{
		putMsg(sock,"Invalid Nickname...bye bye!");
		SDLNet_TCP_Close(sock);
		return(NULL);
	}
	if(!unique_nick(name))
	{
		putMsg(sock,"Duplicate Nickname...bye bye!");
		SDLNet_TCP_Close(sock);
		return(NULL);
	}
	clients=(Client*)realloc(clients, (num_clients+1)*sizeof(Client));
	clients[num_clients].name=name;
	clients[num_clients].sock=sock;
	num_clients++;
	/* server side info */
	printf("--> %s\n",name);
	/* inform all clients, including the new one, of the joined user */
	send_all(mformat("ss","--> ",name));
	return(&clients[num_clients-1]);
}

/* find a client in our array of clients by it's socket. */
/* the socket is always unique */
int find_client(TCPsocket sock)
{
	int i;
	for(i=0;i<num_clients;i++)
		if(clients[i].sock==sock)
			return(i);
	return(-1);
}

/* find a client in our array of clients by it's socket. */
/* the name is always unique */
int find_client_name(char *name)
{
	int i;
	for(i=0;i<num_clients;i++)
		if(!strcasecmp(clients[i].name,name))
			return(i);
	return(-1);
}

/* remove a client from our array of clients */
void remove_client(int i)
{
	char *name=clients[i].name;

	if(i<0 && i>=num_clients)
		return;
	
	/* close the old socket, even if it's dead... */
	SDLNet_TCP_Close(clients[i].sock);
	
	num_clients--;
	if(num_clients>i)
		memmove(&clients[i], &clients[i+1], (num_clients-i)*sizeof(Client));
	clients=(Client*)realloc(clients, num_clients*sizeof(Client));
	/* server side info */
	printf("<-- %s\n",name);
	/* inform all clients, excluding the old one, of the disconnected user */
	send_all(mformat("ss","<-- ",name));
	if(name)
		free(name);
}

/* create a socket set that has the server socket and all the client sockets */
SDLNet_SocketSet create_sockset()
{
	static SDLNet_SocketSet set=NULL;
	int i;

	if(set)
		SDLNet_FreeSocketSet(set);
	set=SDLNet_AllocSocketSet(num_clients+1);
	if(!set) {
		printf("SDLNet_AllocSocketSet: %s\n", SDLNet_GetError());
		exit(1); /*most of the time this is a major error, but do what you want. */
	}
	SDLNet_TCP_AddSocket(set,server);
	for(i=0;i<num_clients;i++)
		SDLNet_TCP_AddSocket(set,clients[i].sock);
	return(set);
}

/* send a buffer to all clients */
void send_all(char *buf)
{
	int cindex;

	if(!buf || !num_clients)
		return;
	cindex=0;
	while(cindex<num_clients)
	{
		/* putMsg is in tcputil.h, it sends a buffer over a socket */
		/* with error checking */
		if(putMsg(clients[cindex].sock,buf))
			cindex++;
		else
			remove_client(cindex);
	}
}

void do_command(char *msg, Client *client)
{
	char *command,*p;
	int len;

	if(!msg || !strlen(msg) || !client)
		return;
	len=strlen(msg);
	p=msg;
	command=strsep(&p," \t");
	/* /NICK : change the clients name */
	if(!strcasecmp(command,"NICK"))
	{
		if(p && strlen(p))
		{
			char *old_name=client->name;
			
			fix_nick(p);
			if(!strlen(p))
			{
				putMsg(client->sock,"--- Invalid Nickname!");
				return;
			}
			if(!unique_nick(p))
			{
				putMsg(client->sock,"--- Duplicate Nickname!");
				return;
			}
			client->name=strdup(p);
			send_all(mformat("ssss","--- ",old_name," --> ",p));
			free(old_name);
		}
		else
			putMsg(client->sock,"--- /NICK nickname");
		return;
	}
	/* MSG : client to client message */
	if(!strcasecmp(command,"MSG"))
	{
		char *name;
		int to;

		if(p)
		{
			name=strsep(&p," ");
			to=find_client_name(name);
			if(to<0)
			{
				putMsg(client->sock, mformat("sss","--- /MSG nickname ",name," not found!"));
				return;
			}
			else if(p && strlen(p))
			{
				putMsg(client->sock,mformat("ssss",">",clients[to].name,"< ",p));
				putMsg(clients[to].sock,mformat("ssss",">",client->name,"< ",p));
				return;
			}
		}
		putMsg(client->sock,"--- /MSG nickname message...");
		return;
	}
	/* /ME : emote! to everyone */
	if(!strcasecmp(command,"ME"))
	{
		if(p && strlen(p))
		{
			send_all(mformat("sss",client->name," ",p));
		}
		else
			putMsg(client->sock,"--- /ME message...");
		return;
	}
	/* /QUIT : quit the server with a message */
	if(!strcasecmp(command,"QUIT"))
	{
		if(!p || strcasecmp(p,"-h"))
		{
			if(p)
				send_all(mformat("ssss","--- ",client->name," quits : ",p));
			else
				send_all(mformat("sss","--- ",client->name," quits"));
			remove_client(find_client(client->sock));
		}
		else
			putMsg(client->sock,"--- /QUIT [message...]");
		return;
	}
	/* /WHO : list the users online back to the client */
	if(!strcasecmp(command,"WHO"))
	{
		int i;
		IPaddress *ipaddr;
		Uint32 ip;
		const char *host=NULL;
		
		putMsg(client->sock,"--- Begin /WHO ");
		for(i=0;i<num_clients;i++)
		{
			ipaddr=SDLNet_TCP_GetPeerAddress(clients[i].sock);
			if(ipaddr)
			{
				ip=SDL_SwapBE32(ipaddr->host);
				host=SDLNet_ResolveIP(ipaddr);
				putMsg(client->sock,mformat("sssssdsdsdsdsd","--- ",clients[i].name,
						" ",host?host:"",
						"[",ip>>24,".", (ip>>16)&0xff,".", (ip>>8)&0xff,".", ip&0xff,
						"] port ",(Uint32)ipaddr->port));
			}
		}
		putMsg(client->sock,"--- End /WHO");
		return;
	}
	/* /HELP : tell the client all the supported commands */
	if(!strcasecmp(command,"HELP"))
	{
		putMsg(client->sock,"--- Begin /HELP");
		putMsg(client->sock,"--- /HELP : this text");
		putMsg(client->sock,"--- /ME message... : emote!");
		putMsg(client->sock,"--- /MSG nickname message... : personal messaging");
		putMsg(client->sock,"--- /NICK nickname : change nickaname");
		putMsg(client->sock,"--- /QUIT [message...] : disconnect this client");
		putMsg(client->sock,"--- /WHO : list who is logged on");
		putMsg(client->sock,"--- End /HELP");
		return;
	}

	/* invalid command...respond appropriately */
	putMsg(client->sock,mformat("sss","--- What does the '",command,"' command do?"));
}

int main(int argc, char **argv)
{
	IPaddress ip;
	TCPsocket sock;
	SDLNet_SocketSet set;
	char *message=NULL;
	const char *host=NULL;
	Uint32 ipaddr;
	Uint16 port;
	
	/* check our commandline */
	if(argc<2)
	{
		printf("%s port\n",argv[0]);
		exit(0);
	}
	
	/* initialize SDL */
	if(SDL_Init(0)==-1)
	{
		printf("SDL_Init: %s\n",SDL_GetError());
		exit(1);
	}

	/* initialize SDL_net */
	if(SDLNet_Init()==-1)
	{
		printf("SDLNet_Init: %s\n",SDLNet_GetError());
		SDL_Quit();
		exit(2);
	}

	/* get the port from the commandline */
	port=(Uint16)strtol(argv[1],NULL,0);

	/* Resolve the argument into an IPaddress type */
	if(SDLNet_ResolveHost(&ip,NULL,port)==-1)
	{
		printf("SDLNet_ResolveHost: %s\n",SDLNet_GetError());
		SDLNet_Quit();
		SDL_Quit();
		exit(3);
	}

	/* perform a byte endianess correction for the next printf */
	ipaddr=SDL_SwapBE32(ip.host);

	/* output the IP address nicely */
	printf("IP Address : %d.%d.%d.%d\n",
			ipaddr>>24,
			(ipaddr>>16)&0xff,
			(ipaddr>>8)&0xff,
			ipaddr&0xff);

	/* resolve the hostname for the IPaddress */
	host=SDLNet_ResolveIP(&ip);

	/* print out the hostname we got */
	if(host)
		printf("Hostname   : %s\n",host);
	else
		printf("Hostname   : N/A\n");

	/* output the port number */
	printf("Port       : %d\n",port);

	/* open the server socket */
	server=SDLNet_TCP_Open(&ip);
	if(!server)
	{
		printf("SDLNet_TCP_Open: %s\n",SDLNet_GetError());
		SDLNet_Quit();
		SDL_Quit();
		exit(4);
	}

	while(1)
	{
		int numready,i;
		set=create_sockset();
		numready=SDLNet_CheckSockets(set, (Uint32)-1);
		if(numready==-1)
		{
			printf("SDLNet_CheckSockets: %s\n",SDLNet_GetError());
			break;
		}
		if(!numready)
			continue;
		if(SDLNet_SocketReady(server))
		{
			numready--;
			/*printf("Connection...\n"); */
			sock=SDLNet_TCP_Accept(server);
			if(sock)
			{
				char *name=NULL;

				/*printf("Accepted...\n"); */
				if(getMsg(sock, &name))
				{
					Client *client;
					client=add_client(sock,name);
					if(client)
						do_command("WHO",client);
				}
				else
					SDLNet_TCP_Close(sock);
			}
		}
		for(i=0; numready && i<num_clients; i++)
		{
			if(SDLNet_SocketReady(clients[i].sock))
			{
				if(getMsg(clients[i].sock, &message))
				{
					char *str;
					
					numready--;
					printf("<%s> %s\n",clients[i].name,message);
					/* interpret commands */
					if(message[0]=='/' && strlen(message)>1)
					{
						do_command(message+1,&clients[i]);
					}
					else /* it's a regular message */
					{
						/* forward message to ALL clients... */
						str=mformat("ssss","<",clients[i].name,"> ",message);
						if(str)
							send_all(str);
					}
					free(message);
					message=NULL;
				}
				else
					remove_client(i);
			}
		}
	}

	/* shutdown SDL_net */
	SDLNet_Quit();

	/* shutdown SDL */
	SDL_Quit();

	return(0);
}
