/*
 * MultiChat Server v1.0
 *
 * Servidor de Chat Multiusuario usando select() como multiplexor de E/S de los clientes
 *
 * + Atiende como maximo MAX_USERS usuarios, asi que es posible modificar esta constante
 * + Puedes comunicarte con este servidor por medio de cualquier cliente TCP/IP universal (telnet) al puerto SERVER_PORT
 * + Para enviar un mensaje a todos los usuarios conectados solamente escribe en el canal
 * + Para ver una lista con los usuarios conectados envia /list
 * + Para enviar un mensaje a un usuario especifico pon una / seguida del nick, da un espacio, y luego el mensaje, ejemplo:
 *   /nitr0us Este mensaje va solo para ti nitr0us...
 * + Para salir envia /exit
 * 
 * 
 * $gcc multichatserver.c -o multichatserver -Wall -O2
 * 
 * nitr0us [nitrousenador en gmail punto com]
 * Mexico - 19/Feb/07
 */

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<time.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

#define SERVER_PORT	5000
#define MAX_BUFFER	256
#define MAX_NICK	10
#define	MAX_USERS	5
#define PIDE_NICK	"Introduce tu nick(max. 10 caracteres): "
#define NICK_OCUPADO	"Nick ocupado!\n"
#define NICK_RESERVADO	"Nick invalido. Palabra reservada por el sistema!\n"
#define NICK_NOEXISTE	"El usuario no existe!\n"
#define NO_ATIMISMO	"No puedes enviarte mensajes a ti mismo!\n"
#define LISTA_NICKS	"Usuarios conectados:\n"
#define SERVER_LLENO	"\nServidor lleno!\n"
#define ADIOS		"\nAdios!\n"
#define BANNER		"\n\n################################################\n"\
			"##     Bienvenido a MultiChat Server v1.0     ##\n"\
			"################################################\n\n"\
			"\t/list\t\tLista los usuarios conectados\n"\
			"\t/exit\t\tSalir del sistema\n"\
			#\t/nick msg\tEnvia un mensaje a un usuario especifico\n\n"

typedef struct{
	int	fd;
	char	nick[MAX_NICK + 1];
} Usuario;

Usuario Users[MAX_USERS];

int sendall(int, char *, int *);
int nickOcupado(char *);
int ndxlibre();
int Userndx(int);
int Busca_nick(char *);
void Envia_a_todos_los_clientes(int, fd_set *, int, int, char *);

int main(){
	struct sockaddr_in	server, client;
	socklen_t		foo = sizeof(struct sockaddr);
	fd_set			master, readfds;
	time_t			fecha;
	int			sfd, cfd, fd = 0, one = 1, nrecv, fdmax, k, z, nusers = 0, tmpndx;
	char			nick[MAX_NICK + 1], buffer[MAX_BUFFER + MAX_NICK + 64], usermsgbuffer[MAX_BUFFER + 1], *ptr, *ptr2, *timeptr;

	if((sfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1){
		perror("socket");
		exit(EXIT_FAILURE);
	}

	/*** Evitar el 'Address already in use' al arrancar el servicio nuevamente ***/
	if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)) == -1){
		perror("setsockopt");
		exit(EXIT_FAILURE);
	}

	bzero(&server, sizeof(server));
	bzero(&client, sizeof(client));
	server.sin_family		= AF_INET;
	server.sin_port			= htons(SERVER_PORT);
	server.sin_addr.s_addr		= INADDR_ANY;

	if(bind(sfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1){
		perror("bind");
		exit(EXIT_FAILURE);
	}

	if(listen(sfd, 5) == -1){
		perror("listen");
		exit(EXIT_FAILURE);
	}

	bzero(&Users, sizeof(Users));

	time(&fecha);
	printf("Servidor iniciado: %s", asctime(localtime(&fecha)));

	FD_ZERO(&master);
	FD_ZERO(&readfds);

	FD_SET(sfd, &master);
	fdmax = sfd;

	for(;;){
		readfds = master;

		if(select(fdmax + 1, &readfds, NULL, NULL, NULL) == -1){
			perror("select");
			exit(EXIT_FAILURE);
		}

		for(k = 3; k <= fdmax; k++)
			if(FD_ISSET(k, &readfds)){
				bzero(usermsgbuffer, sizeof(usermsgbuffer));
				bzero(buffer, sizeof(buffer));
				bzero(nick, sizeof(nick));

				if(k == sfd){ /* Nuevas conexiones */
					if((cfd = accept(sfd, (struct sockaddr *)&client, &foo)) == -1)
						perror("accept");
					else{
						send(cfd, BANNER, strlen(BANNER), 0);

						nusers++;
						if(nusers > MAX_USERS){
							printf("Servidor lleno!\n");
							send(cfd, SERVER_LLENO, strlen(SERVER_LLENO), 0);
							send(cfd, ADIOS, strlen(ADIOS), 0);
							close(cfd);
							nusers--;
							continue;
						}

						send(cfd, PIDE_NICK, strlen(PIDE_NICK), 0);
						if((nrecv = recv(cfd, nick, MAX_NICK, 0)) == -1){
							perror("recv");
							close(cfd);
							nusers--;
							continue;
						}

						ptr = nick;
						while(*ptr != '\r' && *ptr != '\n')
							ptr++;
						*ptr = '\0';

						if(!strcmp(nick, "list") || !strcmp(nick, "exit")){
							send(cfd, NICK_RESERVADO, strlen(NICK_RESERVADO), 0);
							send(cfd, ADIOS, strlen(ADIOS), 0);
							close(cfd);
							nusers--;
							continue;
						}

						if(nickOcupado(nick)){
							printf("Nick ocupado: %s\n", nick);
							send(cfd, NICK_OCUPADO, strlen(NICK_OCUPADO), 0);
							send(cfd, ADIOS, strlen(ADIOS), 0);
							close(cfd);
							nusers--;
							continue;
						} else{
							if((tmpndx = ndxlibre()) == -1){
								fprintf(stderr, "ndxlibre: No hay indices libres!\n");
								exit(-1);
							}
							Users[tmpndx].fd = cfd;
							strncpy(Users[tmpndx].nick, nick, MAX_NICK);
						}

						snprintf(buffer, MAX_BUFFER, "Hola %s, hay %d usuarios conectados\n\n", nick, nusers);
						send(cfd, buffer, strlen(buffer), 0);

						bzero(buffer, sizeof(buffer));
						snprintf(buffer, MAX_BUFFER, "Usuario nuevo: %s\n", nick);

						FD_SET(cfd, &master);
						if(cfd > fdmax)
							fdmax = cfd;

						Envia_a_todos_los_clientes(fdmax, &master, sfd, cfd, buffer);
						
						printf("Nuevo Usuario: %s, conectado desde \"%s\" en el socket %d\n", nick, (char *) inet_ntoa(client.sin_addr), cfd);
					}
				} else /* Gestionar datos de los clientes */
					if((nrecv = recv(k, usermsgbuffer, MAX_BUFFER, 0)) <= 0){
						if(nrecv == 0)
							printf("socket %d hang up!\n", k);
						else
							perror("recv");

						nusers--;
						close(k);
						FD_CLR(k, &master);
					} else{
						time(&fecha);
						timeptr = asctime(localtime(&fecha));
						ptr = timeptr;
						while(*ptr != '\r' && *ptr != '\n')
							ptr++;
						*ptr = '\0';

						if((tmpndx = Userndx(k)) == -1){
							fprintf(stderr, "Userndx: Indice de usuario con el socket %d no encontrado!\n", k);
							exit(-1);
						}

						ptr = usermsgbuffer;

						if(ptr[0] == '/'){
							ptr++;

							if(ptr[0] == 'e' && ptr[1] == 'x' && ptr[2] == 'i' && ptr[3] == 't'){
								send(k, ADIOS, strlen(ADIOS), 0);
								close(k);
								FD_CLR(k, &master);

								snprintf(buffer, MAX_BUFFER, "Usuario %s salio del sistema\n", Users[tmpndx].nick);
								Envia_a_todos_los_clientes(fdmax, &master, sfd, k, buffer);
								printf("Usuario %s salio del sistema\n", Users[tmpndx].nick);
								Users[tmpndx].fd = 0;
								bzero(&Users[tmpndx].nick, sizeof(Users[tmpndx].nick));

								nusers--;
								continue;
							}

							if(ptr[0] == 'l' && ptr[1] == 'i' && ptr[2] == 's' && ptr[3] =='t'){
								send(k, LISTA_NICKS, strlen(LISTA_NICKS), 0);

								for(z = 0; z < MAX_USERS; z++)
									if(Users[z].fd != 0 && Users[z].fd != k){
										snprintf(buffer, sizeof(buffer) - 1, "%s\n", Users[z].nick);
										send(k, buffer, strlen(buffer), 0);
										bzero(buffer, sizeof(buffer));
									}

								continue;
							}

							static char	tmpbuff[sizeof(buffer)];
							strncpy(tmpbuff, ptr, sizeof(tmpbuff) - 1);

							if((fd = Busca_nick(strtok(ptr, " ")))){
								if(fd == k)
									send(fd, NO_ATIMISMO, strlen(NO_ATIMISMO), 0);
								else{
									ptr2 = (strchr(tmpbuff, (int) ' ') + 1);
									snprintf(buffer, sizeof(buffer) - 1, "[%s] /%s: %s", timeptr, Users[tmpndx].nick, ptr2);
									send(fd, buffer, strlen(buffer), 0);
								}
							} else{
								snprintf(buffer, sizeof(buffer) - 1, "%s: ", ptr);
								send(k, buffer, strlen(buffer), 0);
								send(k, NICK_NOEXISTE, strlen(NICK_NOEXISTE), 0);
							}

							bzero(tmpbuff, sizeof(tmpbuff));
							continue;
						}

						snprintf(buffer, sizeof(buffer) - 1, "[%s] %s: %s", timeptr, Users[tmpndx].nick, usermsgbuffer);
						Envia_a_todos_los_clientes(fdmax, &master, sfd, k, buffer);
					}
			}
	}

	return 0;
}

int sendall(int s, char *buf, int *len)
{
	int total = 0, bytesleft = *len, n = 0;

	while(total < *len){
		if((n = send(s, buf + total, bytesleft, 0)) == -1)
			break;

		total += n;
		bytesleft -= n;
	}

	*len = total;

	return n == -1 ? -1 : 0;
}

int nickOcupado(char *nick)
{
	int	k;

	for(k = 0; k < MAX_USERS; k++)
		if(!Users[k].fd)
			continue;
		else if(strcmp(Users[k].nick, nick) == 0)
			return 1;

	return 0;
}

int ndxlibre()
{
	int	k;

	for(k = 0; k < MAX_USERS; k++)
		if(Users[k].fd == 0)
			return k;

	return -1;
}

int Userndx(int fd)
{
	int	k;

	for(k = 0; k < MAX_USERS; k++)
		if(Users[k].fd == fd)
			return k;

	return -1;
}

int Busca_nick(char *nick)
{
	int	k;

	if(nick == NULL)
		return 0;

	for(k = 0; k < MAX_USERS; k++)
		if(!strcmp(Users[k].nick, nick))
			return Users[k].fd;

	return 0;
}

void Envia_a_todos_los_clientes(int fdmax, fd_set *master, int listener, int client, char *buffer)
{
	int	l, buflen;

	buflen = strlen(buffer);

	for(l = 3; l <= fdmax; l++)
		if(FD_ISSET(l, master))
			if(l != listener && l != client)
				if(sendall(l, buffer, &buflen) == -1)
					fprintf(stderr, "sendall: Solo se enviaron %d bytes de %d [%s]\n", buflen, strlen(buffer), buffer);

}
