
#include <lighttpd/base.h>
#include <lighttpd/plugin_core.h>

#define MIME_COUNT_CHILDREN(x)	(x->cmin == 0 ? 0 : ((guint)x->cmax - x->cmin + 1))
#define MIME_MARK_NODE(x)		((gpointer)((uintptr_t)x | 1))
#define MIME_UNMARK_NODE(x)		((gpointer)((uintptr_t)x & (~1)))
#define MIME_IS_NODE(x)			(1 == ((uintptr_t)x & 1))

liMimetypeNode *li_mimetype_node_new(void) {
	return g_slice_new0(liMimetypeNode);
}

void li_mimetype_node_free(liMimetypeNode *node) {
	guint i;
	gpointer ptr;

	if (node->mimetype)
		g_string_free(node->mimetype, TRUE);

	for (i = 0; i < MIME_COUNT_CHILDREN(node); i++) {
		ptr = node->children[i];

		if (NULL == ptr)
			continue;

		if (!MIME_IS_NODE(ptr)) {
			g_string_free(ptr, TRUE);
		} else {
			li_mimetype_node_free(MIME_UNMARK_NODE(ptr));
		}
	}

	if (node->children)
		g_free(node->children);

	g_slice_free(liMimetypeNode, node);
}

static void mimetype_insert(liMimetypeNode *node, GString *suffix, GString *mimetype, guint depth) {
	guchar c, cdiff;
	gpointer ptr;
	liMimetypeNode *next_node;
	LI_FORCE_ASSERT(!MIME_IS_NODE(mimetype));

	/* start of suffix reached */
	if (depth == suffix->len) {
		if (node->mimetype)
			g_string_free(node->mimetype, TRUE);

		node->mimetype = mimetype;
		return;
	}

	c = (guchar) suffix->str[suffix->len - depth - 1];
	LI_FORCE_ASSERT(c != '\0');

	if (NULL == node->children) {
		node->cmin = node->cmax = c;
		node->children = g_malloc(sizeof(gpointer));
		node->children[0] = NULL;
	} else if (c < node->cmin) {
		cdiff = node->cmin - c; /* how much space we need in front */
		node->children = g_realloc(node->children, sizeof(gpointer) * (MIME_COUNT_CHILDREN(node) + cdiff)); /* make room for more children */
		memmove(&node->children[(guint)cdiff], node->children, sizeof(gpointer) * MIME_COUNT_CHILDREN(node)); /* move existing children to the back */
		memset(node->children, 0, cdiff * sizeof(gpointer));
		node->cmin = c;
	} else if (c > node->cmax) {
		cdiff = c - node->cmax;
		node->children = g_realloc(node->children, sizeof(gpointer) * (MIME_COUNT_CHILDREN(node) + cdiff)); /* make room for more children */
		memset(&node->children[MIME_COUNT_CHILDREN(node)], 0, cdiff * sizeof(gpointer));
		node->cmax = c;
	}

	ptr = node->children[c - node->cmin];

	if (NULL != ptr && MIME_IS_NODE(ptr)) {
		/* slot contains another node */
		next_node = MIME_UNMARK_NODE(ptr);
	} else {
		/* slot not used yet or contains a mimetype, split into node */
		next_node = g_slice_new(liMimetypeNode);
		next_node->mimetype = ptr;
		next_node->cmax = next_node->cmin = 0;
		next_node->children = NULL;
		node->children[c - node->cmin] = MIME_MARK_NODE(next_node);
	}

	mimetype_insert(next_node, suffix, mimetype, depth+1);
}

void li_mimetype_insert(liMimetypeNode *node, GString *suffix, GString *mimetype) {
	mimetype_insert(node, suffix, mimetype, 0);
}

GString *li_mimetype_get(liVRequest *vr, GString *filename) {
	/* search in mime_types option for the longest suffix match */
	GString *mimetype;
	liMimetypeNode *node;
	guchar *c, *s;
	gpointer ptr;

	if (!vr || !filename || !filename->len)
		return NULL;

	node = CORE_OPTIONPTR(LI_CORE_OPTION_MIME_TYPES).ptr;

	mimetype = node->mimetype;

	for (s = (guchar*) filename->str, c = (guchar*) filename->str + filename->len; c-- > s; ) {
		if (*c < node->cmin || *c > node->cmax)
			return mimetype;

		ptr = node->children[*c - node->cmin];
		if (NULL == ptr) {
			return mimetype;
		}

		if (!MIME_IS_NODE(ptr)) {
			return ptr;
		}

		node = MIME_UNMARK_NODE(ptr);

		if (NULL != node->mimetype) {
			mimetype = node->mimetype;
		}
	}

	return mimetype;
}
