/*-
 * Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
 * All rights reserved
 *
 * Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* for strdup() */
#define _POSIX_C_SOURCE 200809L

#include "vmod_selector.h"

#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "vcl.h"
#include "vrnd.h"

#include "ph.h"
#include "VSC_selector.h"

#ifndef NO_VXID
#define NO_VXID (0U)
#endif

struct vsc_entry {
	unsigned		magic;
#define VMOD_SELECTOR_VSC_MAGIC 0x4b99b64a
	VSLIST_ENTRY(vsc_entry)	list;
	struct vsc_seg		*vsc_seg;
};

VSLIST_HEAD(vsc_head, vsc_entry);

struct set_init_task {
	unsigned			magic;
#define SET_INIT_TASK_MAGIC 0xeae8c785
	VSLIST_ENTRY(set_init_task)	list;
	struct vmod_selector_set	*set;
	unsigned			create_stats;
};

VSLIST_HEAD(set_init_head, set_init_task);

struct set_init_priv {
	unsigned		magic;
#define SET_INIT_PRIV_MAGIC 0x525e8fef
	struct set_init_head	*task_head;
	struct vsc_head		*vsc_head;
};

/* Event function */

int
vmod_event(VRT_CTX, struct vmod_priv *priv, enum vcl_event_e e)
{
	struct vsc_head *vsc_head;
	struct vsc_entry *vsc_entry;
	uint32_t seed[4];

	ASSERT_CLI();
	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	AN(priv);

	if (priv->priv == NULL) {
		vsc_head = malloc(sizeof(*vsc_head));
		AN(vsc_head);
		priv->priv = vsc_head;
		VSLIST_INIT(vsc_head);
	}
	else
		vsc_head = priv->priv;

	switch(e) {
	case VCL_EVENT_LOAD:
		AZ(VRND_RandomCrypto(seed, sizeof(seed)));
		PH_Init(seed);
		break;
	case VCL_EVENT_DISCARD:
		while (!VSLIST_EMPTY(vsc_head)) {
			vsc_entry = VSLIST_FIRST(vsc_head);
			CHECK_OBJ_NOTNULL(vsc_entry, VMOD_SELECTOR_VSC_MAGIC);
			VSC_selector_Destroy(&vsc_entry->vsc_seg);
			VSLIST_REMOVE_HEAD(vsc_head, list);
			FREE_OBJ(vsc_entry);
		}
		free(vsc_head);
		break;
	case VCL_EVENT_WARM:
		VSLIST_FOREACH(vsc_entry, vsc_head, list) {
			CHECK_OBJ_NOTNULL(vsc_entry, VMOD_SELECTOR_VSC_MAGIC);
			VRT_VSC_Reveal(vsc_entry->vsc_seg);
		}
		break;
	case VCL_EVENT_COLD:
		VSLIST_FOREACH(vsc_entry, vsc_head, list) {
			CHECK_OBJ_NOTNULL(vsc_entry, VMOD_SELECTOR_VSC_MAGIC);
			VRT_VSC_Hide(vsc_entry->vsc_seg);
		}
		break;
	default:
		WRONG("Illegal event type");
	}
	return 0;
}

/* Object regex */

struct memberidx {
	char 		*member;
	unsigned	n;
};

static int
cmp(const void *a, const void *b)
{
	const struct memberidx *aa = a, *bb = b;
	return( strcmp(aa->member, bb->member) );
}

static int
compile(VRT_CTX, struct VPFX(selector_set) *set, const char *method)
{
	char **members;
	struct memberidx *idx;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_SELECTOR_SET_MAGIC);

	members = set->members;
	if (!set->case_sensitive)
		members = set->lomembers;
	if (members == NULL || set->nmembers == 0) {
		CHECK_OBJ_NOTNULL(ctx->msg, VSB_MAGIC);
		VSL(SLT_VCL_Error, NO_VXID, "VCL %s vmod selector %s%s: "
		    "no entries were added, nothing to compile, "
		    "%s.match() will always fail\n", VCL_Name(ctx->vcl),
		    set->vcl_name, method, set->vcl_name);
		return 0;
	}

	idx = malloc(set->nmembers * sizeof(*idx));
	if (idx == NULL) {
		VFAIL(ctx, "%s%s: out of memory", set->vcl_name, method);
		return -1;
	}
	for (unsigned i = 0; i < set->nmembers; i++) {
		idx[i].n = i;
		idx[i].member = members[i];
	}
	qsort(idx, set->nmembers, sizeof(*idx), cmp);
	for (unsigned i = 0; i < set->nmembers; i++) {
		errno = 0;
		if (QP_Insert(&set->origo, idx[i].n, members,
			      set->allow_overlaps) != 0) {
			if (errno == EINVAL)
				VFAIL(ctx, "%s%s: \"%s\" added more than once",
				      set->vcl_name, method, members[i]);
			else if (errno == EPERM)
				VFAIL(ctx, "%s%s: allow_overlaps is false but "
				      "strings with common prefixes were added",
				      set->vcl_name, method);
			else
				VFAIL(ctx, "%s%s member \"%s\" failed: %s",
				      set->vcl_name, method, members[i],
				      strerror(errno));
			free(idx);
			return -1;
		}
	}
	free(idx);

	errno = 0;
	if ((set->hash = PH_Generate(members, set->nmembers)) == NULL) {
		if (errno == ERANGE)
			VFAIL(ctx, "%s%s: too many strings in the set",
			      set->vcl_name, method);
		else
			VFAIL(ctx, "%s%s failed: %s", set->vcl_name, method,
			      strerror(errno));
	}
	return 0;
}

static void
create_stats(VRT_CTX, struct vmod_selector_set *set, struct vsc_head *vsc_head)
{
	struct qp_stats qp_stats = { .magic = QP_STATS_MAGIC };
	struct ph_stats ph_stats = { .magic = PH_STATS_MAGIC };
	struct VSC_selector *vsc;
	struct vsc_seg *vsc_seg;
	struct vsc_entry *vsc_entry;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_SELECTOR_SET_MAGIC);
	AN(vsc_head);

	if (set->nmembers == 0) {
		memset(&qp_stats, 0, sizeof(qp_stats));
		memset(&ph_stats, 0, sizeof(ph_stats));
	}
	else if (set->origo == NULL) {
		VFAIL(ctx, "%s.create_stats(): set was not compiled",
		      set->vcl_name);
		return;
	}
	else {
		char **members = set->members;
		if (!set->case_sensitive)
			members = set->lomembers;
		AN(members);
		QP_Stats(set->origo, members, &qp_stats);
		assert(qp_stats.terms == set->nmembers);
		assert(qp_stats.leaves <= qp_stats.terms);
		assert(qp_stats.terms <= qp_stats.nodes);
		assert(qp_stats.dmin <= qp_stats.dmax);
		assert(qp_stats.dmin <= qp_stats.davg);
		assert(qp_stats.davg <= qp_stats.dmax);
		assert(qp_stats.fmin <= qp_stats.fmax);
		assert(qp_stats.fmin <= qp_stats.favg);
		assert(qp_stats.favg <= qp_stats.fmax);
		assert(qp_stats.nodesz > 0);

		PH_Stats(set->hash, members, &ph_stats);
		assert(ph_stats.buckets >= set->nmembers);
		assert(ph_stats.collisions <= ph_stats.buckets);
		assert(ph_stats.minlen <= ph_stats.maxlen);
		assert(ph_stats.klen == ((ph_stats.maxlen + 7) / 8) * 2);
		assert(ph_stats.h2buckets_min <= ph_stats.h2buckets_max);
		assert(ph_stats.h2buckets_min <= ph_stats.h2buckets_avg);
		assert(ph_stats.h2buckets_avg <= ph_stats.h2buckets_max);
		assert(ph_stats.h2strings_min <= ph_stats.h2strings_max);
		assert(ph_stats.h2strings_min <= ph_stats.h2strings_avg);
		assert(ph_stats.h2strings_avg <= ph_stats.h2strings_max);
		assert(ph_stats.h2klen_min <= ph_stats.h2klen_max);
		assert(ph_stats.h2klen_min <= ph_stats.h2klen_avg);
		assert(ph_stats.h2klen_avg <= ph_stats.h2klen_max);
	}

	vsc = VSC_selector_New(NULL, &vsc_seg, "%s.%s", VCL_Name(ctx->vcl),
			       set->vcl_name);
	vsc->elements = set->nmembers;
	for (unsigned i = 0; i < set->nmembers; i++)
		vsc->setsz += strlen(set->members[i]) + 1;
	vsc->minlen = ph_stats.minlen;
	vsc->maxlen = ph_stats.maxlen;

	vsc->trie_nodes = qp_stats.nodes;
	vsc->trie_nodesz = qp_stats.nodesz;
	vsc->trie_leaves = qp_stats.leaves;
	vsc->trie_depth_min = qp_stats.dmin;
	vsc->trie_depth_max = qp_stats.dmax;
	vsc->trie_depth_avg = (uint64_t)(qp_stats.davg + 0.5);
	vsc->trie_fanout_min = qp_stats.fmin;
	vsc->trie_fanout_max = qp_stats.fmax;
	vsc->trie_fanout_avg = (uint64_t)(qp_stats.favg + 0.5);

	vsc->hash_buckets = ph_stats.buckets;
	vsc->hash_collisions = ph_stats.collisions;
	vsc->hash_keylen = ph_stats.klen;
	vsc->hash_h2_buckets_min = ph_stats.h2buckets_min;
	vsc->hash_h2_buckets_max = ph_stats.h2buckets_max;
	vsc->hash_h2_buckets_avg = (uint64_t)(ph_stats.h2buckets_avg + 0.5);
	vsc->hash_h2_strings_min = ph_stats.h2strings_min;
	vsc->hash_h2_strings_max = ph_stats.h2strings_max;
	vsc->hash_h2_strings_avg = (uint64_t)(ph_stats.h2strings_avg + 0.5);
	vsc->hash_h2_klen_min = ph_stats.h2klen_min;
	vsc->hash_h2_klen_max = ph_stats.h2klen_max;
	vsc->hash_h2_klen_avg = (uint64_t)(ph_stats.h2klen_avg + 0.5);

	ALLOC_OBJ(vsc_entry, VMOD_SELECTOR_VSC_MAGIC);
	AN(vsc_entry);
	vsc_entry->vsc_seg = vsc_seg;
	VSLIST_INSERT_HEAD(vsc_head, vsc_entry, list);
}

static void
set_complete_init(VRT_CTX, void *priv_task)
{
	struct set_init_priv *priv;
	struct set_init_task *task;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	priv = priv_task;
	CHECK_OBJ_NOTNULL(priv, SET_INIT_PRIV_MAGIC);
	AN(priv->vsc_head);
	AN(priv->task_head);
	AZ(VSLIST_EMPTY(priv->task_head));

	VSLIST_FOREACH(task, priv->task_head, list) {
		CHECK_OBJ_NOTNULL(task, SET_INIT_TASK_MAGIC);
		CHECK_OBJ_NOTNULL(task->set, VMOD_SELECTOR_SET_MAGIC);

		if (task->set->hash == NULL)
			if (compile(ctx, task->set, " set initialization") != 0)
				return;

		if (task->create_stats)
			create_stats(ctx, task->set, priv->vsc_head);
	}
}

static const struct vmod_priv_methods set_init[1] = {{
		.magic = VMOD_PRIV_METHODS_MAGIC,
		.type = "vmod_selector_set_init",
		.fini = set_complete_init,
	}};

VCL_VOID
vmod_set__init(VRT_CTX, struct vmod_selector_set **setp, const char *vcl_name,
	       struct vmod_priv *priv_vcl, struct vmod_priv *priv_task,
	       VCL_BOOL case_sensitive, VCL_BOOL allow_overlaps)
{
	struct vmod_selector_set *set;
	struct set_init_priv *init_priv;
	struct set_init_task *task;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(ctx->ws, WS_MAGIC);
	AN(setp);
	AZ(*setp);
	AN(vcl_name);
	AN(priv_vcl);
	AN(priv_vcl->priv);
	AN(priv_task);
	
	ALLOC_OBJ(set, VMOD_SELECTOR_SET_MAGIC);
	AN(set);
	*setp = set;
	set->vcl_name = strdup(vcl_name);
	AN(set->vcl_name);
	set->case_sensitive = (case_sensitive != 0);
	set->allow_overlaps = (allow_overlaps != 0);

	ALLOC_OBJ(set->bitmaps, VMOD_SELECTOR_BITMAPS_MAGIC);
	AN(set->bitmaps);
	for (int i = 0; i < __MAX_BITMAP; i++) {
		set->bitmaps->bitmaps[i] = vbit_new(0);
		AN(set->bitmaps->bitmaps[i]);
	}

	if (priv_task->priv == NULL) {
		if ((init_priv = WS_Alloc(ctx->ws, sizeof(*init_priv)))
		    == NULL) {
			VERRNOMEM(ctx, "insufficient workspace for task "
				  "private data initializing %s", vcl_name);
			return;
		}
		INIT_OBJ(init_priv, SET_INIT_PRIV_MAGIC);
		if ((init_priv->task_head
		     = WS_Alloc(ctx->ws, sizeof(*init_priv->task_head)))
		    == NULL) {
			VERRNOMEM(ctx, "insufficient workspace for task "
				  "head initializing %s", vcl_name);
			return;
		}
		VSLIST_INIT(init_priv->task_head);
		init_priv->vsc_head = priv_vcl->priv;
		priv_task->priv = init_priv;
		priv_task->len = sizeof(*init_priv);
		priv_task->methods = set_init;
	}
	else {
		init_priv = priv_task->priv;
		CHECK_OBJ_NOTNULL(init_priv, SET_INIT_PRIV_MAGIC);
		AN(init_priv->task_head);
		AN(init_priv->vsc_head);
	}

	if ((task = WS_Alloc(ctx->ws, sizeof(*task))) == NULL) {
		VERRNOMEM(ctx, "insufficient workspace to initialize %s",
			  vcl_name);
		return;
	}
	INIT_OBJ(task, SET_INIT_TASK_MAGIC);
	task->set = set;
	AZ(task->create_stats);
	VSLIST_INSERT_HEAD(init_priv->task_head, task, list);

	AZ(set->table);
	AZ(set->members);
	AZ(set->origo);
	AZ(set->hash);
}

static inline void
set_added(struct vmod_selector_set *set, unsigned idx, enum bitmap_e bitmap)
{
	CHECK_OBJ_NOTNULL(set, VMOD_SELECTOR_SET_MAGIC);
	CHECK_OBJ_NOTNULL(set->bitmaps, VMOD_SELECTOR_BITMAPS_MAGIC);
	AN(set->bitmaps->bitmaps[bitmap]);

	vbit_set(set->bitmaps->bitmaps[bitmap], idx);
}

VCL_VOID
vmod_set__fini(struct vmod_selector_set **setp)
{
	struct vmod_selector_set *set;

	if (setp == NULL || *setp == NULL)
		return;
	CHECK_OBJ(*setp, VMOD_SELECTOR_SET_MAGIC);
	set = *setp;
	*setp = NULL;
	QP_Free(set->origo);
	PH_Free(set->hash);
	for (unsigned i = 0; i < set->nmembers; i++) {
		free(set->members[i]);
		for (int j = 0; j < __MAX_BITMAP; j++)
			if (is_added(set, i, j)) {
				struct entry *entry = set->table[i];
				CHECK_OBJ_NOTNULL(entry,
						  VMOD_SELECTOR_ENTRY_MAGIC);
				VRT_Assign_Backend(&entry->backend, NULL);
				free(entry->string);
				FREE_OBJ(entry);
				break;
			}
	}
	for (int i = 0; i < __MAX_BITMAP; i++)
		vbit_destroy(set->bitmaps->bitmaps[i]);
	FREE_OBJ(set->bitmaps);

	free(set->members);
	free(set->table);
	free(set->vcl_name);
	FREE_OBJ(set);
}

VCL_VOID
vmod_set_add(VRT_CTX, struct vmod_selector_set *set,
	     struct VARGS(set_add) *args)
{
	struct entry *entry;
	unsigned n;
	VCL_REGEX re = NULL;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_SELECTOR_SET_MAGIC);
	AN(args);
	CHECK_OBJ_ORNULL(args->backend, DIRECTOR_MAGIC);

	if ((ctx->method & VCL_MET_INIT) == 0) {
		VFAIL(ctx, "%s.add() may only be called in vcl_init",
		      set->vcl_name);
		return;
	}
	if (set->hash != NULL) {
		VFAIL(ctx, "%s.add(): set was already compiled", set->vcl_name);
		return;
	}
	if (args->arg1 == NULL) {
		VFAIL(ctx, "%s.add(): string to be added is NULL",
		      set->vcl_name);
		return;
	}

	set->nmembers++;
	n = set->nmembers;
	set->members = realloc(set->members, n * sizeof(VCL_STRING));
	AN(set->members);
	set->members[n - 1] = strdup(args->arg1);
	AN(set->members[n - 1]);

	if (!set->case_sensitive) {
		set->lomembers = realloc(set->lomembers,
					 n * sizeof(VCL_STRING));
		AN(set->lomembers);
		set->lomembers[n - 1] = strdup(args->arg1);
		AN(set->lomembers[n - 1]);
		for (char *m = set->lomembers[n-1]; *m; m++)
			*m = tolower(*m);
	}

	if (args->valid_regex)
		re = args->regex;

	if (!args->valid_string && re == NULL && !args->valid_backend
	    && !args->valid_integer && !args->valid_bool && !args->valid_sub)
		return;

	set->table = realloc(set->table, n * sizeof(struct entry *));
	AN(set->table);
	ALLOC_OBJ(entry, VMOD_SELECTOR_ENTRY_MAGIC);
	AN(entry);
	if (args->valid_string) {
		AN(args->string);
		entry->string = strdup(args->string);
		set_added(set, n - 1, STRING);
	}
	if (re != NULL) {
		entry->re = re;
		set_added(set, n - 1, REGEX);
	}
	if (args->valid_backend) {
		VRT_Assign_Backend(&entry->backend, args->backend);
		set_added(set, n - 1, BACKEND);
	}
	if (args->valid_integer) {
		entry->integer = args->integer;
		set_added(set, n - 1, INTEGER);
	}
	if (args->valid_bool) {
		entry->bool = args->bool;
		set_added(set, n - 1, BOOLEAN);
	}
	if (args->valid_sub) {
		entry->sub = args->sub;
		set_added(set, n - 1, SUB);
	}
	set->table[n - 1] = entry;
}

VCL_VOID
vmod_set_compile(VRT_CTX, struct VPFX(selector_set) *set)
{
	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_SELECTOR_SET_MAGIC);

	if ((ctx->method & VCL_MET_INIT) == 0) {
		VFAIL(ctx, "%s.compile() may only be called in vcl_init",
		      set->vcl_name);
		return;
	}
	if (set->hash != NULL) {
		VFAIL(ctx, "%s.compile(): set was already compiled",
		      set->vcl_name);
		return;
	}
	compile(ctx, set, ".compile()");
}

VCL_VOID
vmod_set_create_stats(VRT_CTX, struct vmod_selector_set *set,
		      struct vmod_priv *priv)
{
	struct set_init_priv *init_priv;
	struct set_init_task *task = NULL;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_SELECTOR_SET_MAGIC);

	if ((ctx->method & VCL_MET_INIT) == 0) {
		VFAIL(ctx, "%s.create_stats() may only be called in vcl_init",
		      set->vcl_name);
		return;
	}
	AN(priv);
	AN(priv->priv);
	init_priv = priv->priv;
	CHECK_OBJ_NOTNULL(init_priv, SET_INIT_PRIV_MAGIC);
	AZ(VSLIST_EMPTY(init_priv->task_head));

	VSLIST_FOREACH(task, init_priv->task_head, list) {
		CHECK_OBJ_NOTNULL(task, SET_INIT_TASK_MAGIC);
		CHECK_OBJ_NOTNULL(task->set, VMOD_SELECTOR_SET_MAGIC);
		if (task->set == set)
			break;
	}
	CHECK_OBJ_NOTNULL(task, SET_INIT_TASK_MAGIC);
	assert(task->set == set);
	task->create_stats = 1;
}

VCL_STRING
vmod_version(VRT_CTX)
{
	(void) ctx;
	return VERSION;
}
