#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "util.h"

static char *prefixpath = "/", *host = "127.0.0.1", *port = "70"; /* default */
static char *line;
static size_t linesize;
static time_t comparetime;
static struct feed *feeds;

/* Escape characters in Gopher, CR and LF are ignored */
static void
gophertext(FILE *fp, const char *s)
{
	for (; *s; s++) {
		switch (*s) {
		case '\r': /* ignore CR */
		case '\n': /* ignore LF */
			break;
		case '\t':
			fputs("        ", fp);
			break;
		default:
			putc(*s, fp);
			break;
		}
	}
}

static void
printfeed(FILE *fpitems, FILE *fpin, struct feed *f)
{
	struct uri u;
	char *fields[FieldLast];
	char *itemhost, *itemport, *itempath, *itemquery, *itemfragment;
	ssize_t linelen;
	unsigned int isnew;
	struct tm rtm, *tm;
	time_t parsedtime;
	int itemtype;

	if (f->name[0]) {
		fprintf(fpitems, "i%s\t\t%s\t%s\r\n", f->name, host, port);
		fprintf(fpitems, "i\t\t%s\t%s\r\n", host, port);
	}

	while ((linelen = getline(&line, &linesize, fpin)) > 0 &&
	       !ferror(fpitems)) {
		if (line[linelen - 1] == '\n')
			line[--linelen] = '\0';
		parseline(line, fields);

		itemhost = host;
		itemport = port;
		itemtype = 'i'; /* i type (extension): informational message */
		itempath = fields[FieldLink];
		itemquery = "";
		itemfragment = "";

		if (fields[FieldLink][0]) {
			itemtype = 'h'; /* h type (extension): HTML or external URL */
			/* if it is a Gopher URL then change it into a DirEntity */
			if (!strncmp(fields[FieldLink], "gopher://", 9) &&
			    uri_parse(fields[FieldLink], &u) != -1) {
				itemhost = u.host;
				itemport = u.port[0] ? u.port : "70";
				itemtype = '1'; /* directory */
				itempath = u.path;
				itemquery = u.query;
				itemfragment = u.fragment;
				/* use the Gopher type from the path if it is set */
				if (itempath[0] == '/') {
					itempath++;
					if (*itempath) {
						itemtype = *itempath;
						itempath++;
					}
				}
			}
		}

		parsedtime = 0;
		if (!strtotime(fields[FieldUnixTimestamp], &parsedtime) &&
		    (tm = localtime_r(&parsedtime, &rtm))) {
			isnew = (parsedtime >= comparetime) ? 1 : 0;
			f->totalnew += isnew;

			fprintf(fpitems, "%c%c %04d-%02d-%02d %02d:%02d ",
			        itemtype,
			        isnew ? 'N' : ' ',
			        tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
			        tm->tm_hour, tm->tm_min);
		} else {
			fprintf(fpitems, "%c                   ", itemtype);
		}
		f->total++;

		gophertext(fpitems, fields[FieldTitle]);
		fputs("\t", fpitems);
		/* Prefix non-Gopher URLs with "URL:" using the h type.
		   This Gopher extension is commonly used to link to external URLs */
		if (itemtype == 'h' && fields[FieldLink] == itempath) {
			fputs("URL:", fpitems);
			gophertext(fpitems, fields[FieldLink]);
		} else {
			gophertext(fpitems, itempath);
			if (itemquery[0]) {
				fputs("?", fpitems);
				gophertext(fpitems, itemquery);
			}
			if (itemfragment[0]) {
				fputs("#", fpitems);
				gophertext(fpitems, itemfragment);
			}
		}
		fprintf(fpitems, "\t%s\t%s\r\n", itemhost, itemport);
	}
	fputs(".\r\n", fpitems);
}

int
main(int argc, char *argv[])
{
	struct feed *f;
	FILE *fpitems, *fpindex, *fp;
	char buf[64], *name, *p;
	size_t maxcountlen = 0, maxnamelen = 0, len;
	int i;

	if (argc <= 1) {
		if (pledge("stdio", NULL) == -1)
			err(1, "pledge");
	} else {
		if (unveil("/", "r") == -1)
			err(1, "unveil: /");
		if (unveil(".", "rwc") == -1)
			err(1, "unveil: .");
		if (pledge("stdio rpath wpath cpath", NULL) == -1)
			err(1, "pledge");

		setlocale(LC_CTYPE, "");
	}

	if ((comparetime = getcomparetime()) == (time_t)-1)
		errx(1, "getcomparetime: could not get the current time or $SFEED_NEW_AGE is invalid");

	if ((p = getenv("SFEED_GOPHER_HOST")))
		host = p;
	if ((p = getenv("SFEED_GOPHER_PORT")))
		port = p;
	if ((p = getenv("SFEED_GOPHER_PATH")))
		prefixpath = p;

	if (!(feeds = calloc(argc <= 1 ? 1 : argc, sizeof(struct feed))))
		err(1, "calloc");

	if (argc <= 1) {
		feeds[0].name = "";
		printfeed(stdout, stdin, &feeds[0]);
		checkfileerror(stdin, "<stdin>", 'r');
		checkfileerror(stdout, "<stdout>", 'w');
	} else {
		for (i = 1; i < argc; i++) {
			name = ((name = strrchr(argv[i], '/'))) ? name + 1 : argv[i];
			f = &feeds[i - 1];
			f->name = name;

			if (!(fp = fopen(argv[i], "r")))
				err(1, "fopen: %s", argv[i]);
			if (!(fpitems = fopen(name, "wb")))
				err(1, "fopen");
			printfeed(fpitems, fp, f);
			checkfileerror(fp, argv[i], 'r');
			checkfileerror(fpitems, name, 'w');
			fclose(fp);
			fclose(fpitems);

			/* count max length: used for aligning the feed names */
			len = colw(name);
			if (len > maxnamelen)
				maxnamelen = len;

			/* count max length: used for aligning the feed counts to the right */
			len = snprintf(NULL, 0, " (%lu/%lu)", f->totalnew, f->total);
			if (len > maxcountlen)
				maxcountlen = len;
		}
	}

	/* write index file */
	if (argc > 1) {
		if (!(fpindex = fopen("index", "wb")))
			err(1, "fopen: index");

		for (i = 0; i < argc - 1; i++) {
			f = &feeds[i];

			/* append directory item to index */
			fputs("1", fpindex);
			fputs(f->totalnew ? "N " : "  ", fpindex);

			/* left align feed names and pad with spaces */
			len = colw(f->name);
			gophertext(fpindex, f->name);
			for (; len < maxnamelen; len++)
				fputs(" ", fpindex);

			/* right align the item counts by padding with spaces */
			snprintf(buf, sizeof(buf), " (%lu/%lu)", f->totalnew, f->total);
			len = strlen(buf);
			for (; len < maxcountlen; len++)
				fputs(" ", fpindex);
			fprintf(fpindex, "%s\t", buf);

			gophertext(fpindex, prefixpath);
			gophertext(fpindex, f->name);
			fprintf(fpindex, "\t%s\t%s\r\n", host, port);
		}
		fputs(".\r\n", fpindex);
		checkfileerror(fpindex, "index", 'w');
		fclose(fpindex);
	}

	return 0;
}
