#include <sys/stat.h>
#include <sys/types.h>

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <utime.h>

#include "util.h"

static char *line = NULL;
size_t linesize = 0;

static struct utimbuf contenttime;
static time_t comparetime;
static unsigned long totalnew = 0;

static struct feed **feeds = NULL;

/* Unescape / decode fields printed by string_print_encoded()
 * "\\" to "\", "\t", to TAB, "\n" to newline. Unrecognised escape sequences
 * are ignored: "\z" etc. */
static void
printcontent(const char *s, FILE *fp)
{
	for (; *s; s++) {
		switch (*s) {
		case '\\':
			switch (*(++s)) {
			case '\0': return; /* ignore */
			case '\\': fputc('\\', fp); break;
			case 't':  fputc('\t', fp); break;
			case 'n':  fputc('\n', fp); break;
			}
			break;
		default:
			fputc((int)*s, fp);
		}
	}
}

/* Unescape / decode fields printed by string_print_encoded()
 * "\\" to "\", "\t", to TAB, "\n" to newline. Unrecognised escape sequences
 * are ignored: "\z" etc. Encode HTML 2.0 / XML 1.0 entities.  */
static void
printcontentxml(const char *s, FILE *fp)
{
	for (; *s; s++) {
		switch (*s) {
		case '\\':
			switch (*(++s)) {
			case '\0': return; /* ignore */
			case '\\': fputc('\\', fp); break;
			case 't':  fputc('\t', fp); break;
			case 'n':  fputc('\n', fp); break;
			}
			break;
		/* XML entities */
		case '<':  fputs("&lt;",   fp); break;
		case '>':  fputs("&gt;",   fp); break;
		case '\'': fputs("&apos;", fp); break;
		case '&':  fputs("&amp;",  fp); break;
		case '"':  fputs("&quot;", fp); break;
		default:   fputc((int)*s, fp);
		}
	}
}

/* normalize path names, transform to lower-case and replace non-alpha and
 * non-digit with '-' */
static size_t
normalizepath(const char *path, char *buf, size_t bufsiz)
{
	size_t i, r = 0;

	for (i = 0; *path && i < bufsiz; path++) {
		if (isalpha((int)*path) || isdigit((int)*path)) {
			buf[i++] = tolower((int)*path);
			r = 0;
		} else {
			/* don't repeat '-', don't start with '-' */
			if (!r && i)
				buf[i++] = '-';
			r++;
		}
	}
	/* remove trailing '-' */
	for (; i > 0 && (buf[i - 1] == '-'); i--)
		;

	if (bufsiz > 0)
		buf[i] = '\0';

	return i;
}

static void
printfeed(FILE *fpitems, FILE *fpin, struct feed *f)
{
	char dirpath[PATH_MAX], filepath[PATH_MAX];
	char *fields[FieldLast], *feedname, name[128];
	size_t namelen;
	struct stat st;
	FILE *fpcontent = NULL;
	unsigned int isnew;
	time_t parsedtime;
	int r;

	if (f->name[0])
		feedname = f->name;
	else
		feedname = "unnamed";

	/* make directory for feedname */
	if (!(namelen = normalizepath(feedname, name, sizeof(name))))
		return;

	if (strlcpy(dirpath, name, sizeof(dirpath)) >= sizeof(dirpath))
		errx(1, "strlcpy: path truncation");
	/* directory doesn't exist: try to create it. */
	if (stat(dirpath, &st) == -1 && mkdir(dirpath, S_IRWXU) == -1)
		err(1, "mkdir: %s", dirpath);

	/* menu if not unnamed */
	if (f->name[0]) {
		fputs("<h2 id=\"", fpitems);
		xmlencode(f->name, fpitems);
		fputs("\"><a href=\"#", fpitems);
		xmlencode(f->name, fpitems);
		fputs("\">", fpitems);
		xmlencode(f->name, fpitems);
		fputs("</a></h2>\n", fpitems);
	}

	fputs("<table cellpadding=\"0\" cellspacing=\"0\">\n", fpitems);
	while (parseline(&line, &linesize, fields, fpin) > 0) {
		/* write content */
		if (!(namelen = normalizepath(fields[FieldTitle], name, sizeof(name))))
			continue;
		r = snprintf(filepath, sizeof(filepath), "%s/%s.html", dirpath, name);
		if (r == -1 || (size_t)r >= sizeof(filepath))
			errx(1, "snprintf: path truncation");

		/* file doesn't exist yet and has write access */
		if (access(filepath, F_OK) != 0) {
			if (!(fpcontent = fopen(filepath, "w+b")))
				err(1, "fopen: %s", filepath);
			fputs("<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"../../style.css\" />"
			      "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" /></head>\n"
			      "<body class=\"frame\"><div class=\"content\">"
			      "<h2><a href=\"", fpcontent);
			xmlencode(fields[FieldLink], fpcontent);
			fputs("\">", fpcontent);
			xmlencode(fields[FieldTitle], fpcontent);
			fputs("</a></h2>", fpcontent);
			/* NOTE: this prints the raw HTML of the feed, this is
			 * potentially dangerous, it is up to the user / browser
			 * to trust a feed it's HTML content. */
			if (!strcmp(fields[FieldContentType], "html")) {
				printcontent(fields[FieldContent], fpcontent);
			} else {
				/* plain-text, wrap with <pre> */
				fputs("<pre>", fpcontent);
				printcontentxml(fields[FieldContent], fpcontent);
				fputs("</pre>", fpcontent);
			}
			fputs("</div></body></html>", fpcontent);
			fclose(fpcontent);
		}

		/* set modified and access time of file to time of item. */
		parsedtime = 0;
		if (strtotime(fields[FieldUnixTimestamp], &parsedtime) != -1) {
			contenttime.actime = parsedtime;
			contenttime.modtime = parsedtime;
			utime(filepath, &contenttime);
		}

		isnew = (parsedtime >= comparetime) ? 1 : 0;
		totalnew += isnew;
		f->totalnew += isnew;
		f->total++;
		if (isnew)
			fputs("<tr class=\"n\">", fpitems);
		else
			fputs("<tr>", fpitems);
		fputs("<td nowrap valign=\"top\">", fpitems);
		fputs(fields[FieldTimeFormatted], fpitems);
		fputs("</td><td nowrap valign=\"top\">", fpitems);
		if (isnew)
			fputs("<b><u>", fpitems);
		fputs("<a href=\"", fpitems);
		fputs(filepath, fpitems);
		fputs("\" target=\"content\">", fpitems);
		xmlencode(fields[FieldTitle], fpitems);
		fputs("</a>", fpitems);
		if (isnew)
			fputs("</u></b>", fpitems);
		fputs("</td></tr>\n", fpitems);
	}
	fputs("</table>\n", fpitems);
}

int
main(int argc, char *argv[])
{
	FILE *fpindex, *fpitems, *fpmenu, *fp;
	int showsidebar = (argc > 1);
	int i;
	struct feed *f;

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

	if ((comparetime = time(NULL)) == -1)
		err(1, "time");
	/* 1 day is old news */
	comparetime -= 86400;

	/* write main index page */
	if (!(fpindex = fopen("index.html", "w+b")))
		err(1, "fopen: index.html");
	if (!(fpmenu = fopen("menu.html", "w+b")))
		err(1, "fopen: menu.html");
	if (!(fpitems = fopen("items.html", "w+b")))
		err(1, "fopen: items.html");
	fputs("<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"../style.css\" />"
	      "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" /></head>"
	      "<body class=\"frame\"><div id=\"items\">", fpitems);

	if (argc == 1) {
		if (!(feeds[0] = calloc(1, sizeof(struct feed))))
			err(1, "calloc");
		feeds[0]->name = "";
		printfeed(fpitems, stdin, feeds[0]);
	} else {
		for (i = 1; i < argc; i++) {
			if (!(feeds[i - 1] = calloc(1, sizeof(struct feed))))
				err(1, "calloc");
			feeds[i - 1]->name = xbasename(argv[i]);

			if (!(fp = fopen(argv[i], "r")))
				err(1, "fopen: %s", argv[i]);
			printfeed(fpitems, fp, feeds[i - 1]);
			if (ferror(fp))
				err(1, "ferror: %s", argv[i]);
			fclose(fp);
		}
	}
	fputs("\n</div></body>\n</html>", fpitems); /* div items */

	if (showsidebar) {
		fputs("<html><head>"
		      "<link rel=\"stylesheet\" type=\"text/css\" href=\"../style.css\" />\n"
		      "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
		      "</head><body class=\"frame\"><div id=\"sidebar\">", fpmenu);

		for (i = 1; i < argc; i++) {
			f = feeds[i - 1];
			if (f->totalnew)
				fputs("<a class=\"n\" href=\"items.html#", fpmenu);
			else
				fputs("<a href=\"items.html#", fpmenu);
			xmlencode(f->name, fpmenu);
			fputs("\" target=\"items\">", fpmenu);
			if (f->totalnew > 0)
				fputs("<b><u>", fpmenu);
			xmlencode(f->name, fpmenu);
			fprintf(fpmenu, " (%lu)", f->totalnew);
			if (f->totalnew > 0)
				fputs("</u></b>", fpmenu);
			fputs("</a><br/>\n", fpmenu);
		}
		fputs("</div></body></html>", fpmenu);
	}
	fputs("<!DOCTYPE html><html><head>\n\t<title>Newsfeed (", fpindex);
	fprintf(fpindex, "%lu", totalnew);
	fputs(")</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"../style.css\" />\n"
	      "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
	      "</head>\n", fpindex);
	if (showsidebar) {
		fputs("<frameset framespacing=\"0\" cols=\"200,*\" frameborder=\"1\">\n"
		      "	<frame name=\"menu\" src=\"menu.html\" target=\"menu\">\n", fpindex);
	} else {
		fputs("<frameset framespacing=\"0\" cols=\"*\" frameborder=\"1\">\n", fpindex);
	}
	fputs("\t<frameset id=\"frameset\" framespacing=\"0\" cols=\"50%,50%\" frameborder=\"1\">\n"
	      "\t\t<frame name=\"items\" src=\"items.html\" target=\"items\">\n"
	      "\t\t<frame name=\"content\" target=\"content\">\n"
	      "\t</frameset>\n"
	      "</frameset>\n"
	      "</html>", fpindex);

	fclose(fpitems);
	fclose(fpmenu);
	fclose(fpindex);

	return 0;
}