/*
 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <kcgi.h>
#include <kcgihtml.h>
#include <sqlite3.h>

enum	page {
	PAGE_INDEX, /* /index or just / */
	PAGE__MAX
};

static const char *const pages[PAGE__MAX] = {
	"index", /* PAGE_INDEX */
};

/*
 * Generic routine to prime the HTTP document.
 */
static void
resp_open(struct kreq *req, enum khttp http)
{

	khttp_head(req, kresps[KRESP_STATUS], 
		"%s", khttps[http]);
	khttp_head(req, kresps[KRESP_CONTENT_TYPE], 
		"%s", kmimetypes[req->mime]);
	khttp_body(req);
}

/*
 * When the database is busy or locked, sleep for a random amount of
 * time before continuing.
 * We could put all sorts of heuristics in here to back off, but we do
 * something really simple.
 */
static void
db_sleep(size_t attempt)
{

	if (attempt < 10)
		usleep(arc4random_uniform(100000));
	else
		usleep(arc4random_uniform(500000));
}

/*
 * Keep trying to open the database forever.
 */
static sqlite3 *
db_open(void)
{
	size_t	 attempt;
	sqlite3	*db;
	int	 rc;

	attempt = 0;
again:
	rc = sqlite3_open(DATADIR "/example4.db", &db);
	if (SQLITE_BUSY == rc) {
		db_sleep(attempt++);
		goto again;
	} else if (SQLITE_LOCKED == rc) {
		fprintf(stderr, "sqlite3_open: %s\n", 
			sqlite3_errmsg(db));
		db_sleep(attempt++);
		goto again;
	} else if (SQLITE_PROTOCOL == rc) {
		fprintf(stderr, "sqlite3_open: %s\n", 
			sqlite3_errmsg(db));
		db_sleep(attempt++);
		goto again;
	} else if (SQLITE_OK == rc) {
		sqlite3_busy_timeout(db, 500);
		return(db);
	} 

	fprintf(stderr, "sqlite3_open: %s\n", sqlite3_errmsg(db));
	return(NULL);
}

/*
 * Close the database.
 */
static void
db_close(sqlite3 *db)
{

	if (SQLITE_OK == sqlite3_close(db))
		return;
	fprintf(stderr, "sqlite3_close: %s\n", sqlite3_errmsg(db));
}

/*
 * Step through the result set of a database query.
 */
static int
db_step(sqlite3 *db, sqlite3_stmt *stmt)
{
	int	 rc;
	size_t	 attempt = 0;

again:
	rc = sqlite3_step(stmt);
	if (SQLITE_BUSY == rc) {
		db_sleep(attempt++);
		goto again;
	} else if (SQLITE_LOCKED == rc) {
		fprintf(stderr, "sqlite3_step: %s\n", 
			sqlite3_errmsg(db));
		db_sleep(attempt++);
		goto again;
	} else if (SQLITE_PROTOCOL == rc) {
		fprintf(stderr, "sqlite3_step: %s\n", 
			sqlite3_errmsg(db));
		db_sleep(attempt++);
		goto again;
	}

	if (SQLITE_DONE == rc || SQLITE_ROW == rc)
		return(rc);

	fprintf(stderr, "sqlite3_step: %s\n", sqlite3_errmsg(db));
	return(rc);
}

/*
 * Execute a database query.
 */
static int
db_exec(sqlite3 *db, const char *sql)
{
	size_t	attempt = 0;
	int	rc;

again:
	rc = sqlite3_exec(db, sql, NULL, NULL, NULL);

	if (SQLITE_BUSY == rc) {
		db_sleep(attempt++);
		goto again;
	} else if (SQLITE_LOCKED == rc) {
		fprintf(stderr, "sqlite3_exec: %s\n", 
			sqlite3_errmsg(db));
		db_sleep(attempt++);
		goto again;
	} else if (SQLITE_PROTOCOL == rc) {
		fprintf(stderr, "sqlite3_exec: %s\n", 
			sqlite3_errmsg(db));
		db_sleep(attempt++);
		goto again;
	} else if (SQLITE_OK == rc)
		return(1);

	fprintf(stderr, "sqlite3_exec: %s (%s)\n", 
		sqlite3_errmsg(db), sql);
	return(0);
}

/*
 * Prepare a database statement.
 * If we fail, try to populate the database: it might not have been
 * initialised yet.
 */
static sqlite3_stmt *
db_stmt(sqlite3 *db, const char *sql, int recurse)
{
	sqlite3_stmt	*stmt;
	const char	*esql;
	size_t		 attempt = 0;
	int		 rc, bailed;

	bailed = 0;
again:
	rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);

	if (SQLITE_BUSY == rc) {
		db_sleep(attempt++);
		goto again;
	} else if (SQLITE_LOCKED == rc) {
		fprintf(stderr, "sqlite3_prepare_v2: %s\n", 
			sqlite3_errmsg(db));
		db_sleep(attempt++);
		goto again;
	} else if (SQLITE_PROTOCOL == rc) {
		fprintf(stderr, "sqlite3_prepare_v2: %s\n", 
			sqlite3_errmsg(db));
		db_sleep(attempt++);
		goto again;
	} else if (SQLITE_OK == rc)
		return(stmt);

	fprintf(stderr, "sqlite3_prepare_v2: %s (%s) (%d)\n", 
		sqlite3_errmsg(db), sql, sqlite3_errcode(db));
	sqlite3_finalize(stmt);

	/*
	 * Only attempt to do this if (1) we have a certain error, (2)
	 * we haven't already bailed out, and (3) we haven't recursed
	 * into the db_stmt() function.
	 */
	if (SQLITE_ERROR == rc && 0 == bailed && recurse) {
		esql = "CREATE TABLE record "
			"(count INTEGER NOT NULL DEFAULT(0))";
		if (db_exec(db, esql)) {
			esql = "INSERT INTO record DEFAULT VALUES";
			if (NULL != (stmt = db_stmt(db, esql, 0))) {
				db_step(db, stmt);
				sqlite3_finalize(stmt);
			}
		}
		bailed = 1;
		goto again;
	}

	return(NULL);
}

static void
sendindex(struct kreq *req)
{
	struct khtmlreq	 r;
	int		 rc;
	sqlite3		*db;
	sqlite3_stmt	*stmt;

	resp_open(req, KHTTP_200);
	khtml_open(&r, req);
	khtml_elem(&r, KELEM_DOCTYPE);
	khtml_elem(&r, KELEM_HTML);
	khtml_elem(&r, KELEM_HEAD);
	khtml_elem(&r, KELEM_TITLE);
	khtml_puts(&r, "Test");
	khtml_closeelem(&r, 2);
	khtml_elem(&r, KELEM_BODY);

	if (NULL != (db = db_open())) {
		stmt = db_stmt(db, "SELECT count FROM record", 1);
		if (NULL != stmt) {
			rc = db_step(db, stmt);
			if (SQLITE_ROW == rc)
				khtml_puts(&r, (char *)
					sqlite3_column_text(stmt, 0));
			else if (SQLITE_DONE == rc)
				khtml_puts(&r, "No value!?");
			else
				khtml_puts(&r, "Error getting value.");
			sqlite3_finalize(stmt);
		} else
			khtml_puts(&r, "Error getting statement.");

		stmt = db_stmt(db, "UPDATE record SET count=count+1", 0);
		(void)db_step(db, stmt);
		sqlite3_finalize(stmt);

		db_close(db);
	} else
		khtml_puts(&r, "Error opening database.");

	khtml_close(&r);
}

int
main(void)
{
	struct kreq	 r;

	/* 
	 * Set up our main HTTP context. 
	 */
	if (KCGI_OK != khttp_parse
	    (&r, NULL, 0, pages, PAGE__MAX, PAGE_INDEX))
		return(EXIT_FAILURE);

	if (KMETHOD_OPTIONS == r.method) {
		/* 
		 * Indicate that we accept GET and POST methods.
		 * This isn't really needed.
		 */
		khttp_head(&r, kresps[KRESP_STATUS], 
			"%s", khttps[KHTTP_200]);
		khttp_head(&r, kresps[KRESP_ALLOW], 
			"OPTIONS GET POST");
		khttp_body(&r);
	} else if (KMETHOD_GET != r.method) {
		/* 
		 * Don't accept non-GET and non-POST methods.
		 */
		resp_open(&r, KHTTP_405);
	} else if (PAGE__MAX == r.page || 
		   KMIME_TEXT_HTML != r.mime) {
		/*
		 * We've been asked for an unknown page or something
		 * with an unknown extension.
		 */
		resp_open(&r, KHTTP_404);
		khttp_puts(&r, "Page not found.");
	} else
		/* 
		 * Route to page handler.
		 */
		sendindex(&r);

	/*
	 * Clean up our context.
	 */
	khttp_free(&r);
	return(EXIT_SUCCESS);
}