/*
 * 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 <string.h>
#include <unistd.h>

#include "kcgi.h"
#include "kcgihtml.h"

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

enum	key {
	KEY_INTEGER, 
	KEY_FILE,
	KEY_PAGECOUNT,
	KEY_PAGESIZE,
	KEY__MAX
};

typedef	void (*disp)(struct kreq *);

static void senddata(struct kreq *);
static void sendindex(struct kreq *);

static const disp disps[PAGE__MAX] = {
	sendindex, /* PAGE_INDEX */
	senddata, /* PAGE_SENDDATA */
};

static const struct kvalid keys[KEY__MAX] = {
	{ kvalid_int, "integer" }, /* KEY_INTEGER */
	{ NULL, "file" }, /* KEY_FILE */
	{ kvalid_uint, "count" }, /* KEY_PAGECOUNT */
	{ kvalid_uint, "size" }, /* KEY_PAGESIZE */
};

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

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);
}

static void
senddata(struct kreq *req)
{
	int64_t	  	 i, j, k, nm, sz;
	char	 	*buf;
	struct khtmlreq	 r;

	nm = 1024 * 1024;
	if (NULL != req->fieldmap[KEY_PAGECOUNT])
		nm = req->fieldmap[KEY_PAGECOUNT]->parsed.i;
	if (0 == nm)
		nm = 1;

	sz = 1;
	if (NULL != req->fieldmap[KEY_PAGESIZE])
		sz = req->fieldmap[KEY_PAGESIZE]->parsed.i;
	if (0 == sz || (uint64_t)sz > SIZE_MAX)
		sz = 1;
	
	buf = kmalloc(sz);

	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, "Have a banana.");
	khtml_closeelem(&r, 2);
	khtml_elem(&r, KELEM_BODY);
	for (i = k = 0; i < nm; i++) {
		for (j = 0; j < sz; j++) {
			if (72 == k++) {
				buf[j] = '\n';
				k = 0;
			} else
				buf[j] = 65 + arc4random_uniform(24);
		}
		khtml_write(buf, sz, &r);
	}

	khtml_close(&r);
	free(buf);
}

static void
sendindex(struct kreq *req)
{
	char		*page;
	struct khtmlreq	 r;
	const char	*cp;

	kasprintf(&page, "%s/%s", req->pname, pages[PAGE_INDEX]);

	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, "Welcome!");
	khtml_closeelem(&r, 2);
	khtml_elem(&r, KELEM_BODY);
	khtml_puts(&r, "Welcome!");
	khtml_attr(&r, KELEM_FORM,
		KATTR_METHOD, "post",
		KATTR_ENCTYPE, "multipart/form-data",
		KATTR_ACTION, page,
		KATTR__MAX);
	khtml_elem(&r, KELEM_FIELDSET);
	khtml_elem(&r, KELEM_LEGEND);
	khtml_puts(&r, "Post (multipart)");
	khtml_closeelem(&r, 1);
	khtml_elem(&r, KELEM_P);
	cp = NULL == req->fieldmap[KEY_INTEGER] ?
		"" : req->fieldmap[KEY_INTEGER]->val;
	khtml_attr(&r, KELEM_INPUT,
		KATTR_TYPE, "number",
		KATTR_NAME, keys[KEY_INTEGER].name,
		KATTR_VALUE, cp, KATTR__MAX);
	khtml_closeelem(&r, 1);
	khtml_elem(&r, KELEM_P);
	khtml_attr(&r, KELEM_INPUT,
		KATTR_TYPE, "file",
		KATTR_MULTIPLE, "",
		KATTR_NAME, keys[KEY_FILE].name,
		KATTR__MAX);
	if (NULL != req->fieldmap[KEY_FILE]) {
		if (NULL != req->fieldmap[KEY_FILE]->file) {
			khtml_puts(&r, "file: ");
			khtml_puts(&r, req->fieldmap[KEY_FILE]->file);
			khtml_puts(&r, " ");
		} 
		if (NULL != req->fieldmap[KEY_FILE]->ctype) {
			khtml_puts(&r, "ctype: ");
			khtml_puts(&r, req->fieldmap[KEY_FILE]->ctype);
		} 
	}
	khtml_closeelem(&r, 1);
	khtml_elem(&r, KELEM_P);
	khtml_attr(&r, KELEM_INPUT,
		KATTR_TYPE, "submit",
		KATTR__MAX);
	khtml_closeelem(&r, 0);
	khtml_close(&r);
	free(page);
}

int
main(void)
{
	struct kreq	 r;

	/* 
	 * Set up our main HTTP context. 
	 */
	if (KCGI_OK != khttp_parse
	    (&r, keys, KEY__MAX, 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 && 
		   KMETHOD_POST != 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.
		 */
		(*disps[r.page])(&r);

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