#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#define VERSION "1.1"

enum {
	ERR = 0,
	WARN,
	INFO
};

extern int errno;
extern char *optarg;
extern int optind, opterr, optopt;

volatile int flag_hup = 0;

struct opt_t {
	int cont;
	int stdin;
	int recurse;
	int bufsz;
	int verbose;
	int fasthelp;
	int version;
} opt;

struct tab_t {
	unsigned int rpos;
	char *name;
	int allocated;
};

int tab_comp (const void * const t1, const void * const t2)
{
	return ((struct tab_t *) t1)->rpos - ((struct tab_t *) t2)->rpos;
}

void version (void)
{
	printf ("randcat v%s (C) 2006 Adam Wysocki <gophi at chmurka.net>\n", VERSION);
	printf ("Get latest version from http://www.chmurka.net/p/randcat.c\n");
}

void fasthelp (void)
{
	static const char msg[] = 
		"-c\t\tContinuous mode (repeat until interrupted)\n"
		"-s\t\tRead names from stdin (default if no names given)\n"
		"-r level\tMaximum directory recurse level (default 64)\n"
		"-b size\t\tBuffer size in kB (default 32)\n"
		"-v level\tVerbosity level (default 1)\n"
		"-h\t\tThis fasthelp\n"
		"-V\t\tVersion information\n"
		"--\t\tEnd of options\n"
		"\n"
		"-r 0\t\tDon't recurse directories\n"
		"-r -1\t\tDon't limit recurse level\n"
		"\n"
		"-v 0\t\tPrint errors\n"
		"-v 1\t\tPrint warnings\n"
		"-v 2\t\tPrint notices\n";

	printf ("%s", msg);
}

void verbose (int level, const char *fmt, ...)
{
	va_list args;
	static const char *levels[] = {
		"Error",
		"Warning",
		"Notice"
	};

	if (level > opt.verbose)
		return;

	fprintf (stderr, "randcat: %s: ", levels[level]);
	va_start (args, fmt);
	vfprintf (stderr, fmt, args);
	va_end (args);
	fprintf (stderr, "\n");
}

void sighup (int signo)
{
	flag_hup++;
	signal (signo, sighup);
}

void parse_opts (int ac, char * const av[])
{
	int done = 0;
	static char optstr[] = "csr:b:v:hV";

	memset (&opt, 0, sizeof(opt));
	opt.recurse = 64;
	opt.verbose = 1;
	opt.bufsz = 32;

	while (!done) {
		switch (getopt(ac, av, optstr)) {
			case 'c':
				opt.cont = 1;
				break;
			case 's':
				opt.stdin = 1;
				break;
			case 'r':
				opt.recurse = atoi(optarg);
				break;
			case 'b':
				opt.bufsz = atoi(optarg);
				break;
			case 'v':
				opt.verbose = atoi(optarg);
				break;
			case 'h':
				opt.fasthelp = 1;
				break;
			case 'V':
				opt.version = 1;
				break;
			case -1:
				done = 1;
				break;
			default:
				exit (EXIT_FAILURE);
		}
	}

	if (optind == ac)
		opt.stdin = 1;
}

int do_cat (const char * const fname, int bufsz)
{
	char *buf;
	int fd;

	verbose (INFO, "Processing file: %s", fname);

	fd = open(fname, O_RDONLY);

	if (fd == -1) {
		verbose (WARN, "open(%s): %m", fname);
		return -1;
	}

	assert ((buf = (char *) malloc(bufsz)));

	for (;;) {
		char *bptr = buf;
		ssize_t rs;

		rs = read(fd, buf, bufsz);
		if (rs == -1) {
			if (errno == EINTR)
				continue;
			verbose (WARN, "read(%s): %m", fname);
			goto err;
		} else if (!rs)
			break;

		for (;;) {
			ssize_t rs2 = write(1, bptr, rs);
			if (rs2 == -1) {
				if (errno == EINTR)
					continue;
				verbose (WARN, "write(stdout): %m");
				goto err;
			} else if (!rs2) {
				verbose (WARN, "write(stdout): EOF?");
				goto err;
			} else if (rs2 > rs) {
				verbose (WARN, "write(stdout): Returned nonsense (%d gt %d)", rs2, rs);
				goto err;
			} else {
				rs -= rs2;
				bptr += rs2;
				if (!rs)
					break;
			}
		}

		if (flag_hup) {
			verbose (INFO, "%s: SIGHUP received.", fname);
			flag_hup = 0;
			break;
		}
	}

	free (buf);
	close (fd);
	return 0;

err:
	free (buf);
	close (fd);
	return -1;
}

void add_file (struct tab_t **tab, size_t *tabsz, char *name, int allocate)
{
	verbose (INFO, "Adding file %s%s", name, allocate ? " (alloc)" : "");

	assert ((*tab = (struct tab_t *) realloc(*tab, (*tabsz + 1) * 
	    sizeof(struct tab_t))));
	if (allocate) {
		(*tab)[*tabsz].allocated = 1;
		assert (((*tab)[(*tabsz)++].name = strdup(name)));
	} else {
		(*tab)[*tabsz].allocated = 0;
		(*tab)[(*tabsz)++].name = name;
	}
}

void add_files (struct tab_t **tab, size_t *tabsz, int recurse, char *name, int allocate)
{
	struct stat st;
	struct dirent *de;
	DIR *d;

	if (stat(name, &st) == -1) {
		verbose (WARN, "stat(%s): %m", name);
		return;
	}

	if (!S_ISDIR(st.st_mode)) {
		add_file (tab, tabsz, name, allocate);
		return;
	}

	if (!recurse) {
		verbose (WARN, "%s: Recurse level exceeded.", name);
		return;
	}

	if (recurse != -1)
		recurse--;

	d = opendir(name);
	if (!d) {
		verbose (WARN, "opendir(%s): %m", name);
		return;
	}

	while ((de = readdir(d))) {
		char *fname;
		size_t fnamesz;

		if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
			continue;

		fnamesz = strlen(name) + strlen(de->d_name) + 2;
		assert ((fname = (char *) malloc(fnamesz)));
		snprintf (fname, fnamesz, "%s/%s", name, de->d_name);

		if (stat(fname, &st) == -1) {
			verbose (WARN, "stat(%s): %m", fname);
			goto end;
		}

		if (S_ISDIR(st.st_mode))
			add_files (tab, tabsz, recurse, fname, 1);
		else
			add_file (tab, tabsz, fname, 1);

end:
		free (fname);
	}

	closedir (d);
}

void add_files_cmdline (struct tab_t **tab, size_t *tabsz, int recurse, int ac, char * const av[])
{
	size_t i;
	for (i = 0; i < ac; i++)
		add_files (tab, tabsz, recurse, av[i], 0);
}

void add_files_stdin (struct tab_t **tab, size_t *tabsz, int recurse)
{
	for (;;) {
		char buf[512];

		fgets (buf, sizeof(buf), stdin);

		if (feof(stdin))
			break;

		if (strlen(buf) >= 1 && buf[strlen(buf) - 1] == 0x0A)
			buf[strlen(buf) - 1] = 0;

		if (strlen(buf) >= 1 && buf[strlen(buf) - 1] == 0x0D)
			buf[strlen(buf) - 1] = 0;

		add_files (tab, tabsz, recurse, buf, 1);
	}
}

void free_tab (struct tab_t *tab, size_t tabsz)
{
	size_t i;

	for (i = 0; i < tabsz; i++)
		if (tab[i].allocated)
			free (tab[i].name);
}

int main (int ac, char * const av[])
{
	struct tab_t *tab = NULL;
	size_t tabsz = 0;
	int rs = 0;

	parse_opts (ac, av);

	if (opt.fasthelp || opt.version) {
		version();
		if (opt.fasthelp)
			fasthelp();
		exit (EXIT_SUCCESS);
	}

	if (optind < ac)
		add_files_cmdline (&tab, &tabsz, opt.recurse, ac - optind, av + optind);

	if (opt.stdin)
		add_files_stdin (&tab, &tabsz, opt.recurse);

	srand (getpid());
	if (!opt.cont) {
		size_t i;
		for (i = 0; i < tabsz; i++)
			tab[i].rpos = rand() % sizeof(unsigned int);
		qsort (tab, tabsz, sizeof(*tab), tab_comp);
	}

	if (!tabsz)
		exit (EXIT_FAILURE);

	signal (SIGHUP, sighup);

	if (opt.cont) {
		for (;;) {
			if (do_cat(tab[rand() % tabsz].name, opt.bufsz))
				rs = 1;
		}
	} else {
		size_t i;
		for (i = 0; i < tabsz; i++)
			if (do_cat(tab[i].name, opt.bufsz))
				rs = 1;
	}

	signal (SIGHUP, SIG_DFL);

	free_tab (tab, tabsz);

	return rs ? EXIT_FAILURE : EXIT_SUCCESS;
}
