#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

#define SZ 20*1024

int server_fd;
FILE* logfile;

struct user {
	char name[20];
	int fd;
	int readbuflen;
	char readbuf[SZ];
	int writebuflen;
	char writebuf[SZ];
};

void warn(char *msg) {
	fprintf(stderr,"%s: %s\n",msg, strerror(errno));
}

void err(int code,char *msg) {
	warn(msg);
	exit(code);
}


int nusers=0;
struct user users[200];

void create_server_fd() {
	struct sockaddr_in addr;
	int yes= 1;
	server_fd= socket(AF_INET, SOCK_STREAM, 0);
	if (server_fd<0)
		err(3,"can not create socket");
	setsockopt( server_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
	memset(&addr, 0, sizeof(addr));
	addr.sin_port= htons(20001);
	addr.sin_family= AF_INET;
	if (bind(server_fd, (struct sockaddr*) &addr, sizeof(addr)))
		err(3,"can not bind");
	if (listen(server_fd,5))
		err(3,"can not listen");
}

void set_fd(fd_set *set, int fd, int *maxfd) {
	FD_SET(fd,set);
	if (fd>*maxfd)
		*maxfd= fd;
}

void write_user(int u,char *b,int l) {
	if (l+users[u].writebuflen>=SZ) {
		users[u].writebuflen= SZ-l-1;
	}
	memcpy(users[u].writebuf+users[u].writebuflen,b,l);
	users[u].writebuflen+= l;
}

void pass() {
	int i;
	int maxfd=0;
	fd_set rs,ws;
	FD_ZERO(&rs);
	FD_ZERO(&ws);

	if (nusers<sizeof(users)/sizeof(users[0]))
		set_fd(&rs, server_fd, &maxfd);
	for(i=0;i<nusers;i++) {
		set_fd(&rs, users[i].fd, &maxfd);
		if (users[i].writebuflen)
			set_fd(&ws, users[i].fd, &maxfd);
	}
	select(maxfd+1,&rs, &ws, NULL,NULL);
	if (FD_ISSET(server_fd, &rs)) {
		struct sockaddr_in addr;
		socklen_t sz= sizeof(addr);
		int fd= accept(server_fd, (struct sockaddr*) &addr, &sz);
		if (fd>0) {
			memset(users+nusers, 0, sizeof(users[0]));
			users[nusers].fd= fd;
			char *msg="Welcome to chat. Enter your name:";
			write_user(nusers, msg,strlen(msg));
			nusers++;
		}
	}
	for(i=0;i<nusers;i++)
		if (FD_ISSET(users[i].fd,&rs)) {
			int r;
			r= recv(users[i].fd, users[i].readbuf+users[i].readbuflen, SZ-users[i].readbuflen-1, 0);
			if (r<0) {
				if (errno!=EINTR) {
					close(users[i].fd);
					users[i]= users[nusers-1];
					nusers--;
					i--;
					continue;
				} else {
					i--;
					continue;
				}
			}
			users[i].readbuflen+= r;
			users[i].readbuf[users[i].readbuflen]=0;
			while (strchr(users[i].readbuf,'\n')) {
				char *z= strchr(users[i].readbuf,'\n');
				char *flush="flush";
				if (!strncmp(users[i].readbuf, flush,strlen(flush)))
					fflush(0);
				z++;
				if (users[i].name[0]) {
					int j;
					for(j=0;j<nusers;j++) {
						write_user(j, users[i].name, strlen(users[i].name));
						write_user(j, ": ",2);
						write_user(j, users[i].readbuf, z-users[i].readbuf);
					}
					if (logfile) {
						fprintf(logfile,"%s: ",users[i].name);
						fwrite(users[i].readbuf,1,z-users[i].readbuf,logfile);
					}
				} else {
					int j;
					int cpy;
					char *y= strchr(users[i].readbuf,'\r');
					if (!y || y>z)
						y= z;
					cpy= y-users[i].readbuf;
					if (cpy>19)
						cpy=19;
					memcpy(users[i].name, users[i].readbuf, cpy);
					users[i].name[cpy]=0;
					for(j=0;j<nusers;j++) {
						char *msg1= "user ";
						char *msg2= " logged in\n";
						write_user(j, msg1, strlen(msg1));
						write_user(j, users[i].name, strlen(users[i].name));
						write_user(j, msg2, strlen(msg2));
					}
					if (logfile)
						fprintf(logfile,"user %s logged in\r\n", users[i].name);
				}
				if (z!=users[i].readbuflen+users[i].readbuf)
					memmove(users[i].readbuf, z, users[i].readbuflen- (z-users[i].readbuf));
				users[i].readbuflen-= z-users[i].readbuf;
				users[i].readbuf[users[i].readbuflen]=0;
			}
		}
	for(i=0;i<nusers;i++)
		if (FD_ISSET(users[i].fd, &ws)) {
			int s;
			if (users[i].writebuflen==0)
				continue;
			s= send(users[i].fd, users[i].writebuf, users[i].writebuflen,0);
			if (s<0) {
				if (errno==EINTR) {
					i--;
					continue;
				} else {
					close(users[i].fd);
					users[i]= users[nusers-1];
					nusers--;
					i--;
					continue;
				}
			}
			if (s!=users[i].writebuflen)
				memmove(users[i].writebuf, users[i].writebuf+s, users[i].writebuflen-s);
			users[i].writebuflen-= s;
		}
}


int main() {
	logfile= fopen("logfile.txt","w");
	if (!logfile)
		warn("fopen(logfile.txt)");
	create_server_fd();
	signal(SIGPIPE, SIG_IGN);
	while(1)
		pass();
}
