#include <errno.h>
#include <poll.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define LEN 512

int readall(FILE *src, FILE *dest) {
	char buf[LEN];
	while (fgets(buf, LEN, src) != NULL)
		if (fputs(buf, dest) == EOF)
			return 1;
	return 0;
}

bool exec(FILE *f, char *fp) {
	pid_t p;
	switch ((p = fork())) {
		case -1:
			perror("fork");
			return false;
		case 0:
			fflush(f);
			dup2(fileno(f), 1); // dup stdout
			dup2(fileno(f), 2); // dup stderr
			execlp(fp, fp);
			break;
		default:;
			pid_t res = waitpid(p, NULL, 0);
			printf("done %d\n", p);
			fflush(f);
			return res;
	}
	// make gcc happy
	return false;
}

static void handle_events(int fd, int wd, char *dir, FILE *f) {
	char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event))));
	const struct inotify_event *event;
	ssize_t len;

	/* Loop while events can be read from inotify file descriptor. */

	for (;;) {
		/* Read some events. */
		len = read(fd, buf, sizeof(buf));
		if (len == -1 && errno != EAGAIN) {
			perror("read");
			exit(EXIT_FAILURE);
		}

		/* If the nonblocking read() found no events to read, then
		   it returns -1 with errno set to EAGAIN. In that case,
		   we exit the loop. */

		if (len <= 0)
			break;

		/* Loop over all events in the buffer */
		for (char *ptr = buf; ptr < buf + len;
				ptr += sizeof(struct inotify_event) + event->len) {

			event = (const struct inotify_event *)ptr;

			/* Print event type */

			if (event->len && (event->mask & IN_ISDIR) || !(event->mask & IN_CLOSE))
				break;

			bool addSlash = dir[strlen(dir) - 1] != '/';
			char *s = (char *)malloc((strlen(dir) + event->len + (addSlash ? 1 : 0)) *
					sizeof(char));
			strncpy(s, dir, strlen(dir));
			if (addSlash)
				s[strlen(dir)] = '/';
			strncpy(s + strlen(dir) + (addSlash ? 1 : 0), event->name,
					event->len); // copy trailing \0
			fprintf(f, "(%s)\n", s);
			if (event->len)
				printf("! (%s)\n", event->name);

			struct stat st;
			if (stat(s, &st)) {
				perror("stat");
				exit(EXIT_FAILURE);
			}
			if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
				if (!exec(f, s)) {
					perror("ececlp");
					exit(EXIT_FAILURE);
				}
			} else {
				FILE *src = fopen(s, "r");
				if (src == NULL) {
					perror("fopen");
					exit(EXIT_FAILURE);
				}

				if (readall(src, f))
					exit(EXIT_FAILURE);
				fclose(src);
			}
			fprintf(f, "\n");
			if (unlink(s)) {
				perror("unlink");
				exit(EXIT_FAILURE);
			}
			free(s);
		}
	}
}

int main(int argc, char *argv[]) {
	char buf;
	int fd, i, poll_num;
	int wd;
	nfds_t nfds;
	FILE *f;
	struct pollfd fds[2];

	if (argc < 3) {
		printf("Usage: %s DIR OUT\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	printf("Press ENTER key to terminate.\n");

	/* Create the file descriptor for accessing the inotify API */

	fd = inotify_init1(IN_NONBLOCK);
	if (fd == -1) {
		perror("inotify_init1");
		exit(EXIT_FAILURE);
	}

	f = fopen(argv[2], "a");
	if (f == NULL) {
		perror("fopen");
		exit(EXIT_FAILURE);
	}

	wd = inotify_add_watch(fd, argv[1],
			/* IN_OPEN | IN_CLOSE | IN_CREATE */ IN_ALL_EVENTS);
	if (wd == -1) {
		fprintf(stderr, "Cannot watch '%s': %s\n", argv[1], strerror(errno));
		exit(EXIT_FAILURE);
	}

	/* Prepare for polling */

	nfds = 2;

	/* Console input */

	fds[0].fd = STDIN_FILENO;
	fds[0].events = POLLIN;

	/* Inotify input */

	fds[1].fd = fd;
	fds[1].events = POLLIN;

	/* Wait for events and/or terminal input */

	printf("Listening for events.\n");
	while (1) {
		poll_num = poll(fds, nfds, -1);
		if (poll_num == -1) {
			if (errno == EINTR)
				continue;
			perror("poll");
			exit(EXIT_FAILURE);
		}

		if (poll_num > 0) {

			if (fds[0].revents & POLLIN) {

				/* Console input is available. Empty stdin and quit */

				while (read(STDIN_FILENO, &buf, 1) > 0 && buf != '\n')
					continue;
				break;
			}

			if (fds[1].revents & POLLIN) {

				/* Inotify events are available */

				handle_events(fd, wd, argv[1], f);
			}
		}
	}

	printf("Listening for events stopped.\n");

	close(fd);
	fflush(f);
	fclose(f);

	exit(EXIT_SUCCESS);
}