#define _XOPEN_SOURCE	1

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <err.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/wait.h>

#define LISTEN_BACKLOG 10

#define FPAS_FORM "for port %s and service %s\n"
#define FPAS_P	cfg->items[i].port
#define FPAS_S	cfg->items[i].cmd

struct config_it{
	char * port;
	char wait;
	char * cmd;
	char ** args;
};

struct config{
	int len;
	struct config_it * items;
};

int sigpipe[2];

void usage(void){
	printf("Usage: ./inetd <config>\n");
}

struct config * parse_config(char * filename){
	FILE * f;
	f = fopen(filename,"r");
	if (f==NULL){
		printf("Could not open config file %s\n",filename);
		exit(1);
	}
	int ch;
	int lines = 0;
	ch = fgetc(f);
	while (ch != EOF){
		if (ch == '\n')
			lines++;
		
		ch = fgetc(f);
	}
	
	rewind(f);

	struct config_it * cfg_its;
	cfg_its = calloc(lines,sizeof(struct config_it));
	for (int i=0;i<lines;i++){
		cfg_its[i].port = malloc(10);
		fscanf(f,"%s",cfg_its[i].port);
		char waitstr[10];
		fscanf(f,"%s",waitstr);
		if (!strcmp(waitstr,"wait"))
			cfg_its[i].wait = 1;
		else
			cfg_its[i].wait = 0;
		cfg_its[i].cmd = malloc(100);
		fscanf(f,"%s",cfg_its[i].cmd);
		while(fgetc(f)!='\n'){}
	}
	struct config * cfg;
	cfg = malloc(sizeof(struct config));
	cfg->len = lines;
	cfg->items = cfg_its;
	return cfg;
}

void dump_config(struct config * cfg){
	printf("port\twait\tcmd\n");
	for (int i=0;i<cfg->len;i++){
		printf("%s\t%d\t%s\n",cfg->items[i].port,cfg->items[i].wait,cfg->items[i].cmd);
	}
}

void handle_sigchld(int sig){
	write(sigpipe[1],"!",1);
}

void handle_error(int mem_errno,int n_srv, int * l_socks, char * msg){
	for (int j=0;j<=n_srv;j++){
		close(l_socks[j]);
	}
	errno = mem_errno;
	err(1,msg);
}

void run(struct config * cfg){
	printf("Running inetd...\n");
	int n_srv;
	n_srv = cfg->len;

	int * l_socks;
	struct addrinfo * addrinfo;
	struct addrinfo hints;
	l_socks = malloc(n_srv*sizeof(int));
	memset(&hints,0,sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;

	for (int i=0;i<n_srv;i++){
		int gai_res;
		if ((gai_res=getaddrinfo(NULL,cfg->items[i].port,&hints,&addrinfo)) != 0){
			fprintf(stderr,"Error: \"%s\""FPAS_FORM,gai_strerror(gai_res),FPAS_P,FPAS_S);
			exit(1);
		}
		struct addrinfo * orig_ainfo;
		for (orig_ainfo = addrinfo; addrinfo!=NULL; addrinfo = addrinfo->ai_next){
			errno = 0;
			l_socks[i]=socket(addrinfo->ai_family, addrinfo->ai_socktype, addrinfo->ai_protocol);
			if (l_socks[i]==-1){
				warn("Error in socket()"FPAS_FORM,FPAS_P,FPAS_S);
				continue;
			}
			errno = 0;
			if (bind(l_socks[i],addrinfo->ai_addr,addrinfo->ai_addrlen)!=0){
				warn("Error in bind()"FPAS_FORM,FPAS_P,FPAS_S);
				continue;
			}
			break;
		}
		if (addrinfo==NULL){
			for (int j=0;j<=i;j++){
				close(l_socks[j]);
			}
			err(1,"Exitting because of errors above");
		}
		freeaddrinfo(orig_ainfo);
		
		int opt = 1;
		errno = 0;
		setsockopt(l_socks[i], SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
		if (errno){
			handle_error(errno,n_srv,l_socks,"Error while setting REUSEADDR");	
		}

		errno = 0;
		if (listen(l_socks[i],LISTEN_BACKLOG)!=0){
			int mem_errno = errno;
			for (int j=0;j<=i;j++){
				close(l_socks[j]);
			}
			errno = mem_errno;
			err(1,"Error in listen()"FPAS_FORM,FPAS_P,FPAS_S);
		}

	}

	errno = 0;
	pipe(sigpipe);
	fcntl(sigpipe[1],F_SETFL, fcntl(sigpipe[1], F_GETFL) | O_NONBLOCK);
	if (errno)
		handle_error(errno,n_srv,l_socks,"Error while setting signal pipe");
	
	struct sigaction sa;
	memset(&sa, 0, sizeof (sa));
	sa.sa_handler = handle_sigchld;
//	sa.sa_flags = SA_RESTART;
	if (sigaction(SIGCHLD, &sa, NULL) == -1) {
		handle_error(errno,n_srv,l_socks,"Error while setting signal");
	}
	
	struct pollfd * pollfds;
	pid_t * children;
	pollfds = malloc((n_srv+1)*sizeof(struct pollfd));
	children = malloc(n_srv*sizeof(pid_t));

	for (int i=0;i<n_srv;i++){
		pollfds[i].fd = l_socks[i];
		pollfds[i].events = POLLIN;
		children[i]=-1;
	}
	pollfds[n_srv].fd=sigpipe[0];
	pollfds[n_srv].events = POLLIN;

	while (1){
		errno = 0;
		int pol_res;
		pol_res = poll(pollfds,n_srv+1,1000);
		if (pol_res == 0){
			printf("Poll has timed out\n");
			continue;
		}else if (pol_res > 0){
			printf("Got %d connections, serving them\n",pol_res);
		}else if (errno & EINTR){
			printf("Interrupted in poll\n");
			continue;
		}else {
			handle_error(errno,n_srv,l_socks,"Poll failed");
		}

		for (int i=0;i<n_srv;i++){
			if (!(pollfds[i].revents & POLLIN))
				continue;

			printf("Serving connection - port: %s wait: %d cmd: %s\n",FPAS_P,cfg->items[i].wait,FPAS_S);
			
			if (cfg->items[i].wait){
				if (children[i]!=-1){ // Child may be running
					printf("Child is running and we are polling!!!\n");
				}
				printf("FORK!\n");
				pid_t pid;
				if ((pid = fork())!=0){ // We are parent
				//	printf("Parent\n");
					children[i]=pid;
					pollfds[i].fd = -pollfds[i].fd;
					continue;
				}
				// We are child
				for (int j=0;j<n_srv;j++){
					if (j!=i)
						close(l_socks[j]);
				}
				errno = 0;
				close(0); dup(l_socks[i]);
				//close(1); dup(l_socks[i]);
				//close(2); dup(l_socks[i]);
				close(l_socks[i]);
				if (errno)
					err(1,"Error while preparing file descriptors");

				printf("EXEC!\n");
				errno = 0;
				execl(cfg->items[i].cmd,cfg->items[i].cmd,(char *)NULL);
				err(1,"Error while running %s",cfg->items[i].cmd);
			

			}else { // nowait
				int rw_sock;
				rw_sock = accept(l_socks[i],NULL,NULL);
				if (rw_sock == -1){
					err(1,"Error when accepting connection");
				}
				printf("FORK!\n");
				if(fork()!=0){ // We are parent
					close(rw_sock);
					printf("Parent\n");
					continue;
				}
				printf("Child\n");
				// We are child

				for (int j=0;j<n_srv;j++){
					close(l_socks[j]);
				}
				errno = 0;
				close(0); dup(rw_sock);
				close(1); dup(rw_sock);
				close(2); dup(rw_sock);
				close(rw_sock);
				if (errno)
					err(1,"Error while preparing file descriptors");

				printf("EXEC!\n");
				errno = 0;
				execl(cfg->items[i].cmd,cfg->items[i].cmd,(char *)NULL);
				err(1,"Error while running %s",cfg->items[i].cmd);
			}
			
		}
		if (pollfds[n_srv].revents & POLLIN){
			char ch;
			read(sigpipe[0],&ch,1);
			pid_t dead;
			dead = waitpid(-1,NULL,WNOHANG);
			if (dead < 0){
				handle_error(errno,n_srv,l_socks,"Waitpid failed");
			}
			if (dead == 0){
				printf("All alive!\n");
			}
			printf("Child %d died%c\n",dead,ch);
			for (int i=0;i<n_srv;i++){
				if (children[i]==dead){
					children[i]=-1;
					pollfds[i].fd = -pollfds[i].fd;
				}
			}

		}
		
	}

	
	for (int i=0;i<n_srv;i++){
		close(l_socks[i]);
	}


}

int main (int argc, char ** argv){
	
	if (argc != 2){
		usage();
		printf("No config file supplied\n");
		exit(1);
	}

	struct config * cfg;

	cfg = parse_config(argv[1]);
	dump_config(cfg);
	run(cfg);

	return 0;
}