#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sqlite3.h>
#include <ccgi.h>
#include <time.h>
#include <ctype.h>

/*
CREATE TABLE messages (
	id INTEGER PRIMARY KEY ASC,
	creation INTEGER NOT NULL DEFAULT (strftime('%s', datetime('now'))),
	name TEXT,
	url TEXT,
	post TEXT NOT NULL,
	cap TEXT,
	CHECK (url IS NULL OR (name IS NOT NULL AND url IS NOT NULL))
);
*/

void html_print(const unsigned char *str)
{
	if (str)
		for (; *str != '\0'; ++str)
			switch (*str) {
			case '<':
				fputs("&lt;", stdout);
				break;
			case '>':
				fputs("&gt;", stdout);
				break;
			case '&':
				fputs("&amp;", stdout);
				break;
			default:
				putchar(*str);
			}
}

void post_print(const unsigned char *str)
{
	if (str)
		for (; *str != '\0'; ++str)
			switch (*str) {
			case '<':
				fputs("&lt;", stdout);
				break;
			case '>':
				if (*(str+1) == '>') {
					const unsigned char *numstart = str += 2;
					fputs("<a href=\"#p", stdout);
					while (isdigit(*str))
						putchar(*str++);
					fputs("\">&gt;&gt;", stdout);
					while (numstart != str)
						putchar(*numstart++);
					fputs("</a>", stdout);
				} else
					fputs("&gt;", stdout);
				break;
			case '&':
				fputs("&amp;", stdout);
				break;
			default:
				putchar(*str);
			}
}

void html_print_attr(const unsigned char *str)
{
	if (str)
		for (; *str != '\0'; ++str)
			switch (*str) {
			case '<':
				fputs("&lt;", stdout);
				break;
			case '>':
				fputs("&gt;", stdout);
				break;
			case '&':
				fputs("&amp;", stdout);
				break;
			case '\'':
				fputs("&apos;", stdout);
				break;
			case '"':
				fputs("&quot;", stdout);
				break;
			default:
				putchar(*str);
			}
}

int main(void)
{
	sqlite3 *db;

	if (getenv("REQUEST_METHOD") == NULL) {
		fputs("This program must be run as CGI\n", stderr);
		return EXIT_FAILURE;
	}

	if (sqlite3_open("/var/local/lib/jtvjan/guestbook.sqlite3", &db) != SQLITE_OK) {
		fputs("Status: 500 Internal Server Error\nContent-Type: text/plain\n\nCouldn't connect to database.", stdout);
		fprintf(stderr, "sqlite3_connect: %s\n", sqlite3_errmsg(db));
		return EXIT_FAILURE;
	}

	if (strcmp("POST", getenv("REQUEST_METHOD")) == 0) {
		CGI_varlist *vars;
		const char *var;
		sqlite3_stmt *newmessage;
		size_t varlen;

		if ((vars = CGI_get_post(NULL, NULL)) == NULL) {
			fputs("Status: 400 Bad Request\nContent-Type: text/plain\n\nNo variables posted.", stdout);
			return EXIT_FAILURE;
		}

		if (sqlite3_prepare_v2(db, "INSERT INTO messages (name, url, post) VALUES (?1, CASE INSTR(?2, ':') WHEN 0 THEN CASE INSTR(?2, '@') WHEN 0 THEN 'http://' || ?2 ELSE 'mailto:' || ?2 END ELSE ?2 END, ?3)", 173, &newmessage, NULL) != SQLITE_OK) {
			fputs("Status: 500 Internal Server Error\nContent-Type: text/plain\n\nCouldn't prepare statement.", stdout);
			fprintf(stderr, "sqlite3_prepare(newmessage): %s\n", sqlite3_errmsg(db));
			return EXIT_FAILURE;
		}

		if ((var = CGI_lookup(vars, "codeword")) == NULL || strcmp(var, "uparupa") != 0) {
			fputs("Status: 400 Bad Request\nContent-Type: text/plain\n\nSorry, but you didn't fill in the backwards word correctly. It should be \"uparupa\".", stdout);
			return EXIT_FAILURE;
		}

		if ((var = CGI_lookup(vars, "name")) && (varlen = strlen(var)) > 0) {
			if (varlen > 80) {
				fputs("Status: 413 Request Entity Too Large\nContent-Type: text/plain\n\nName must be shorter than 80 characters", stdout);
				return EXIT_FAILURE;
			} else if (sqlite3_bind_text(newmessage, 1, var, varlen, NULL) != SQLITE_OK) {
				fputs("Status: 500 Internal Server Error\nContent-Type: text/plain\n\nCouldn't bind parameter.", stdout);
				return EXIT_FAILURE;
			}
		}

		if ((var = CGI_lookup(vars, "url")) && (varlen = strlen(var)) > 0) {
			if (varlen > 256) {
				fputs("Status: 413 Request Entity Too Large\nContent-Type: text/plain\n\nURL must be shorter than 256 characters", stdout);
				return EXIT_FAILURE;
			} else if (sqlite3_bind_text(newmessage, 2, var, varlen, NULL) != SQLITE_OK) {
				fputs("Status: 500 Internal Server Error\nContent-Type: text/plain\n\nCouldn't bind parameter.", stdout);
				return EXIT_FAILURE;
			}
		}

		if ((var = CGI_lookup(vars, "text")) && (varlen = strlen(var)) > 0) {
			if (varlen > 2000) {
				fputs("Status: 413 Request Entity Too Large\nContent-Type: text/plain\n\nPost must be shorter than 2000 characters", stdout);
				return EXIT_FAILURE;
			} else if (sqlite3_bind_text(newmessage, 3, var, varlen, NULL) != SQLITE_OK) {
				fputs("Status: 500 Internal Server Error\nContent-Type: text/plain\n\nCouldn't bind parameter.", stdout);
				return EXIT_FAILURE;
			}
		} else {
			fputs("Status: 400 Bad Request\nContent-Type: text/plain\n\nYou must write a message!", stdout);
			return EXIT_SUCCESS;
		}

		switch (sqlite3_step(newmessage)) {
		case SQLITE_DONE:
			break;
		case SQLITE_CONSTRAINT:
			fputs("Status: 400 Bad Request\nContent-Type: text/plain\n\nPlease supply a name along with your URL", stdout);
			return EXIT_SUCCESS;
		default:
			fputs("Status: 500 Internal Server Error\nContent-Type: text/plain\n\nCouldn't insert post", stdout);
			fprintf(stderr, "sqlite3_step(newmessage): %s\n", sqlite3_errmsg(db));
			return EXIT_FAILURE;
		}

		if (sqlite3_finalize(newmessage) != SQLITE_OK) {
			fputs("Status: 500 Internal Server Error\nContent-Type: text/plain\n\nCouldn't finalize query", stdout);
			return EXIT_FAILURE;
		}

		fputs("Content-Type: text/plain\nRefresh: 1\n\nYour post was accepted", stdout);

		system("echo 'https://jtvjan.nl/guestbook' | mail -s 'Guestbook got a post' jan@hasebos.net");

		CGI_free_varlist(vars);
	} else {
		sqlite3_stmt *findmessages;

		if (sqlite3_prepare_v2(db, "SELECT id, creation, name, url, post, cap FROM messages ORDER BY id DESC", 73, &findmessages, NULL) != SQLITE_OK) {
			fputs("Status: 500 Internal Server Error\nContent-Type: text/plain\n\nCouldn't prepare statement.", stdout);
			fprintf(stderr, "sqlite3_prepare(findmessages): %s\n", sqlite3_errmsg(db));
			return EXIT_FAILURE;
		}

		fputs(
			"Content-Type: text/html\n\n"
			"<!DOCTYPE html>"
			"<html lang=\"en\">"
			"<head>"
			"<meta charset=\"utf-8\"/>"
			"<meta name=\"viewport\" content=\"width=device-width\"/>"
			"<title>Guestbook</title>"
			"<style>label { display: block; } [type=submit] { display: block; } pre { white-space: pre-wrap; word-wrap: break-word; } .webmaster { color: red; }</style>"
			"</head>"
			"<body>"
			"<h1>Guestbook</h1>"
			"<p>Feature requests, bug reports and other complaints appreciated. This is for the whole site, so please mention which page you're talking about.</p>"
			"<form method=\"post\">"
			"<label>Your name (optional): <input name=\"name\" maxlength=\"80\"/></label>"
			"<label>URL (optional, your homepage or e-mail): <input name=\"url\" maxlength=\"256\"/></label>"
			"<label>The word <tt>apurapu</tt> backwards: <input name=\"codeword\" placeholder=\"Primitive spam prevention ;-)\"/></label>"
			"<textarea maxlength=\"2000\" name=\"text\" required=\"required\"></textarea>"
			"<input type=\"submit\" value=\"Send\"/>"
			"</form>",
			stdout
		);

		while (sqlite3_step(findmessages) == SQLITE_ROW) {
			/* pretty sure this only works by coïncidence */
			time_t timenum;
			char timestr[22];

			printf("<hr/><article id=\"p%d\"><b>%d</b> ", sqlite3_column_int(findmessages, 0), sqlite3_column_int(findmessages, 0));

			if (sqlite3_column_type(findmessages, 2) != SQLITE_NULL) {
				if (sqlite3_column_type(findmessages, 5) != SQLITE_NULL) {
					fputs("<span class=\"", stdout);
					html_print_attr(sqlite3_column_text(findmessages, 5));
					fputs("\">", stdout);
				}
				if (sqlite3_column_type(findmessages, 3) != SQLITE_NULL) {
					fputs("<a href=\"", stdout);
					html_print_attr(sqlite3_column_text(findmessages, 3));
					fputs("\" rel=\"nofollow ugc\">", stdout);
				}
				html_print(sqlite3_column_text(findmessages, 2));
				if (sqlite3_column_type(findmessages, 3) != SQLITE_NULL)
					fputs("</a>", stdout);
				if (sqlite3_column_type(findmessages, 5) != SQLITE_NULL)
					fputs("</span>", stdout);
				fputs(" @ ", stdout);
			}

			timenum = sqlite3_column_int64(findmessages, 1);
			strftime(timestr, 22, "%Y-%m-%d %H:%M:%S", gmtime(&timenum));
			printf("<time>%s</time>", timestr);

			fputs("<pre>", stdout);
			post_print(sqlite3_column_text(findmessages, 4));
			fputs("</pre></article>", stdout);
		}

		if (sqlite3_finalize(findmessages) != SQLITE_OK)
			fputs("<hr/><p>An error occured while reading messages.</p>", stdout);

		fputs(
			"</body>"
			"</html>",
			stdout
		);

	}

	sqlite3_close(db);

	return EXIT_SUCCESS;
}
