/*-
 * Copyright (c) 2017 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.
 */

#include "vmod_re2.h"

#include <limits.h>
#include <stdio.h>

#include "vbm.h"
#include "vre2/vre2set.h"
#include "set_hdr_iter.h"

#define INIT(ctx) (((ctx)->method & VCL_MET_INIT) != 0)

enum bitmap_e {
	STRING = 0,
	BACKEND,
	REGEX,
	INTEGER,
	SUBROUTINE,
	MAX_BITMAP,
};

#define NELEMS(a) (sizeof(a) / sizeof((a)[0]))

struct set_options {
	VCL_INT		max_mem;
	unsigned	utf8 : 1;
	unsigned	posix_syntax : 1;
	unsigned	longest_match : 1;
	unsigned	literal : 1;
	unsigned	never_nl : 1;
	unsigned	dot_nl : 1;
	unsigned	case_sensitive : 1;
	unsigned	perl_classes : 1;
	unsigned	word_boundary : 1;
	unsigned	one_line : 1;
};

struct vmod_re2_set {
	unsigned		magic;
// magic defined in set_hdr_iter.h
	vre2set			*set;
	struct vbitmap		*added[MAX_BITMAP];
	char			*vcl_name;
	char			**string;
	VCL_BACKEND		*backend;
	struct vmod_re2_regex	**regex;
	VCL_INT			*integer;
	VCL_SUB			*sub;
	struct set_options	opts;
	struct vrt_blob		self;	// for .use
#define COMPILE_FAILURE UINT_MAX
	unsigned		compiled;
	int			npatterns;
};

struct task_set_match {
	unsigned	magic;
#define TASK_SET_MATCH_MAGIC 0x7a24a90b
	int		*matches;
	size_t		nmatches;
};

struct task_set_init {
	unsigned			magic;
#define SET_INIT_TASK_MAGIC 0xe24e2945
	VSLIST_ENTRY(task_set_init)	list;
	struct vmod_re2_set		*set;
};

VSLIST_HEAD(set_init_head, task_set_init);

typedef
VCL_STRING regex_rewrite_f(const struct vrt_ctx *ctx, struct vmod_re2_regex *re,
			   VCL_STRING text, VCL_STRING rewrite,
			   VCL_STRING fallback);

static regex_rewrite_f * const regex_rewrite[] = {
	[SUB]		= vmod_regex_sub,
	[SUBALL]	= vmod_regex_suball,
	[EXTRACT]	= vmod_regex_extract,
};

static inline int
decimal_digits(int n)
{
	int digits = 1;

	assert(n >= 0);
	while (n > 9) {
		digits++;
		n /= 10;
	}
	return digits;
}

static int
compile(VRT_CTX, struct vmod_re2_set *set, const char *method)
{
	const char *err;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_RE2_SET_MAGIC);
	AZ(set->compiled);

	if (set->npatterns == 0) {
		VFAIL(ctx, "%s%s: no patterns were added", set->vcl_name,
		      method);
		return (-1);
	}
	if ((err = vre2set_compile(set->set)) != NULL) {
		set->compiled = COMPILE_FAILURE;
		VFAIL(ctx, "%s%s: possibly insufficient memory", set->vcl_name,
		      method);
		return (-1);
	}
	set->compiled = 1;
	return (0);
}

static void
set_complete_init(VRT_CTX, void *priv_task)
{
	struct set_init_head *head;
	struct task_set_init *task;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	AN(priv_task);
	head = priv_task;
	AZ(VSLIST_EMPTY(head));

	VSLIST_FOREACH(task, head, list) {
		CHECK_OBJ_NOTNULL(task, SET_INIT_TASK_MAGIC);
		CHECK_OBJ_NOTNULL(task->set, VMOD_RE2_SET_MAGIC);

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

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

/* Object set */

VCL_VOID
vmod_set__init(VRT_CTX, struct vmod_re2_set **setp, const char *vcl_name,
	       struct vmod_priv *priv_task, VCL_ENUM anchor_a, VCL_BOOL utf8,
	       VCL_BOOL posix_syntax, VCL_BOOL longest_match, VCL_INT max_mem,
	       VCL_BOOL literal, VCL_BOOL never_nl, VCL_BOOL dot_nl,
	       VCL_BOOL case_sensitive, VCL_BOOL perl_classes,
	       VCL_BOOL word_boundary, VCL_BOOL one_line)
{
	struct vmod_re2_set *set;
	struct set_init_head *head;
	struct task_set_init *task;
	anchor_e anchor;
	const char *err;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(ctx->ws, WS_MAGIC);
	AN(setp);
	AZ(*setp);
	AN(vcl_name);
	AN(priv_task);
	ALLOC_OBJ(set, VMOD_RE2_SET_MAGIC);
	AN(set);
	*setp = set;

	if (anchor_a == VENUM(none))
		anchor = NONE;
	else if (anchor_a == VENUM(start))
		anchor = START;
	else if (anchor_a == VENUM(both))
		anchor = BOTH;
	else
		WRONG("illegal anchor");

	if ((err = vre2set_init(&set->set, anchor, utf8, posix_syntax,
				longest_match, max_mem, literal, never_nl,
				dot_nl, case_sensitive, perl_classes,
				word_boundary, one_line))
	    != NULL) {
		VFAIL(ctx, "new %s = re2.set(): %s", vcl_name, err);
		return;
	}

	if (priv_task->priv == NULL) {
		if ((head = WS_Alloc(ctx->ws, sizeof(*head))) == NULL) {
			VERRNOMEM(ctx, "insufficient workspace for task head "
				  "initializing %s", vcl_name);
			return;
		}
		VSLIST_INIT(head);
		priv_task->priv = head;
		priv_task->len = sizeof(*task);
		priv_task->methods = set_init;
	}
	else {
		AN(priv_task->methods);
		head = priv_task->priv;
	}
	if ((task = WS_Alloc(ctx->ws, sizeof(*task))) == NULL) {
		VERRNOMEM(ctx, "insufficient workspace to initialize %s",
			  vcl_name);
		return;
	}
	task->magic = SET_INIT_TASK_MAGIC;
	task->set = set;
	VSLIST_INSERT_HEAD(head, task, list);

	for (unsigned i = 0; i < NELEMS(set->added); i++) {
		set->added[i] = vbit_new(0);
		AN(set->added[i]);
	}

	set->vcl_name = strdup(vcl_name);
	AN(set->vcl_name);

#define SET(OPT) set->opts.OPT = OPT;
	SET(utf8);
	SET(posix_syntax);
	SET(longest_match);
	SET(max_mem);
	SET(literal);
	SET(never_nl);
	SET(dot_nl);
	SET(case_sensitive);
	SET(perl_classes);
	SET(word_boundary);
	SET(one_line);
#undef SET

	AZ(set->string);
	AZ(set->backend);
	AZ(set->regex);
	AZ(set->compiled);
	AZ(set->npatterns);

	set->self.magic = VRT_BLOB_MAGIC;
	set->self.type = VMOD_RE2_SET_MAGIC;
	set->self.len = sizeof(*set);
	set->self.blob = set;
}

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

	if (setp == NULL || *setp == NULL)
		return;
	CHECK_OBJ(*setp, VMOD_RE2_SET_MAGIC);
	set = *setp;
	*setp = NULL;
	AZ(vre2set_fini(&set->set));
	for (int i = 0; i < set->npatterns; i++) {
		if (vbit_test(set->added[STRING], i)
		    && set->string[i] != NULL)
			free(set->string[i]);
		if (vbit_test(set->added[REGEX], i)
		    && set->regex[i] != NULL)
			vmod_regex__fini(&set->regex[i]);
		if (vbit_test(set->added[BACKEND], i)
		    && set->backend[i] != NULL)
			VRT_Assign_Backend(&set->backend[i], NULL);
	}
	for (unsigned i = 0; i < NELEMS(set->added); i++)
		vbit_destroy(set->added[i]);
	if (set->vcl_name != NULL)
		free(set->vcl_name);
	FREE_OBJ(set);
}

#define ERR_PREFIX "%s.add(\"%.40s\"): "

VCL_VOID
vmod_set_add(VRT_CTX, struct vmod_re2_set *set, struct VARGS(set_add) *args)
{
	const char *err;
	int n;
	VCL_STRING pattern = args->arg1;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_RE2_SET_MAGIC);
	if (pattern == NULL)
		pattern = "";
	if (!INIT(ctx)) {
		VFAIL(ctx, ERR_PREFIX ".add() may only be called in vcl_init",
		     set->vcl_name, pattern);
		return;
	}

	if (set->compiled == COMPILE_FAILURE) {
		VFAIL(ctx, ERR_PREFIX "%s compilation has failed",
		      set->vcl_name, pattern, set->vcl_name);
		return;
	}
	if (set->compiled) {
		VFAIL(ctx, ERR_PREFIX "%s has already been compiled",
		      set->vcl_name, pattern, set->vcl_name);
		return;
	}
	if ((err = vre2set_add(set->set, pattern, &n)) != NULL) {
		VFAIL(ctx, ERR_PREFIX "Cannot compile '%.40s': %s",
		      set->vcl_name, pattern, pattern, err);
		return;
	}

	if (args->valid_string && args->string != NULL) {
		if ((set->string = realloc(set->string,
					   (n + 1) * (sizeof(char *))))
		    == NULL) {
			VERRNOMEM(ctx, ERR_PREFIX "adding string %s",
				   set->vcl_name, pattern, args->string);
			return;
		}
		set->string[n] = strdup(args->string);
		AN(set->string[n]);
		vbit_set(set->added[STRING], n);
	}
	if (args->valid_backend && args->backend != NULL) {
		if ((set->backend = realloc(set->backend,
					    (n + 1) * (sizeof(VCL_BACKEND))))
		    == NULL) {
			VERRNOMEM(ctx, ERR_PREFIX "adding backend %s",
				   set->vcl_name, pattern,
				   VRT_BACKEND_string(args->backend));
			return;
		}
		set->backend[n] = NULL;
		VRT_Assign_Backend(&set->backend[n], args->backend);
		vbit_set(set->added[BACKEND], n);
	}
	if (args->valid_integer) {
		if ((set->integer = realloc(set->integer,
					    (n + 1) * (sizeof(VCL_INT))))
		    == NULL) {
			VERRNOMEM(ctx, ERR_PREFIX "adding integer %jd",
				   set->vcl_name, pattern, args->integer);
			return;
		}
		set->integer[n] = args->integer;
		vbit_set(set->added[INTEGER], n);
	}
	if (args->valid_save && args->save) {
		struct vmod_re2_regex *re = NULL;
		char *vcl_name;
		size_t namelen;
		int digits = decimal_digits(n);

		assert(digits >= 1);
		namelen = strlen(set->vcl_name) + digits + 2;
		vcl_name = malloc(namelen);
		snprintf(vcl_name, namelen, "%s_%d", set->vcl_name, n);
		vmod_regex__init(ctx, &re, vcl_name, pattern, set->opts.utf8,
				 set->opts.posix_syntax,
				 set->opts.longest_match, set->opts.max_mem,
				 set->opts.literal, set->opts.never_nl,
				 set->opts.dot_nl, args->never_capture,
				 set->opts.case_sensitive,
				 set->opts.perl_classes,
				 set->opts.word_boundary, set->opts.one_line);
		free(vcl_name);
		if (re->vcl_name == NULL) {
			vmod_regex__fini(&re);
			return;
		}
		if ((set->regex
		     = realloc(set->regex,
			       (n + 1) * (sizeof(struct vmod_re2_regex *))))
		    == NULL) {
			VERRNOMEM(ctx, ERR_PREFIX "saving regex", set->vcl_name,
				  pattern);
			return;
		}
		set->regex[n] = re;
		vbit_set(set->added[REGEX], n);
	}
	if (args->valid_sub) {
		if ((set->sub = realloc(set->sub,
					(n + 1) * (sizeof(VCL_SUB))))
		    == NULL) {
			VERRNOMEM(ctx, ERR_PREFIX "adding subroutine",
				   set->vcl_name, pattern);
			return;
		}
		set->sub[n] = args->sub;
		vbit_set(set->added[SUBROUTINE], n);
	}
	set->npatterns++;
}

#undef ERR_PREFIX

VCL_VOID
vmod_set_compile(VRT_CTX, struct vmod_re2_set *set)
{
	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_RE2_SET_MAGIC);
	if (!INIT(ctx)) {
		VFAIL(ctx, "%s.compile(): .compile() may only be called in "
		      "vcl_init", set->vcl_name);
		return;
	}
	if (set->compiled == COMPILE_FAILURE) {
		VFAIL(ctx, "%s.compile(): %s compilation has failed",
		      set->vcl_name, set->vcl_name);
		return;
	}
	if (set->compiled) {
		VFAIL(ctx, "%s.compile(): %s has already been compiled",
		      set->vcl_name, set->vcl_name);
		return;
	}
	compile(ctx, set, ".compile()");
}

#define ERR_PREFIX "%s.match(\"%.40s\"): "

VCL_BOOL
vmod_set_match(VRT_CTX, struct vmod_re2_set *set, VCL_STRING subject)
{
	int match = 0;
	struct vmod_priv *priv;
	struct task_set_match *task;
	char *buf;
	size_t buflen;
	const char *err;
	errorkind_e errkind = NO_ERROR;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_RE2_SET_MAGIC);
	if (subject == NULL)
		subject = "";

	if (set->compiled == COMPILE_FAILURE) {
		VFAIL(ctx, ERR_PREFIX "%s compilation has failed",
		      set->vcl_name, subject, set->vcl_name);
		return 0;
	}
	if (!set->compiled) {
		VFAIL(ctx, ERR_PREFIX "%s was not compiled", set->vcl_name,
		      subject, set->vcl_name);
		return 0;
	}

	priv = VRT_priv_task(ctx, set);
	if (priv == NULL) {
		VFAIL(ctx, ERR_PREFIX "No priv_task - workspace overflow?",
		      set->vcl_name, subject);
		return 0;
	}
	if (priv->priv == NULL) {
		if ((priv->priv = WS_Alloc(ctx->ws, sizeof(*task))) == NULL) {
			VERRNOMEM(ctx, ERR_PREFIX "allocating match data",
				  set->vcl_name, subject);
			return 0;
		}
		priv->len = sizeof(*task);
		AZ(priv->methods);
		task = priv->priv;
		task->magic = TASK_SET_MATCH_MAGIC;
	}
	else {
		WS_Assert_Allocated(ctx->ws, priv->priv, sizeof(*task));
		CAST_OBJ(task, priv->priv, TASK_SET_MATCH_MAGIC);
	}

	buflen = WS_ReserveAll(ctx->ws);
	buf = WS_Reservation(ctx->ws);
	if ((err = vre2set_match(set->set, subject, &match, buf, buflen,
				 &task->nmatches, &errkind)) != NULL) {
		VFAIL(ctx, ERR_PREFIX "%s", set->vcl_name, subject, err);
		WS_Release(ctx->ws, 0);
		return 0;
	}
	if (match) {
		task->matches = (int *)buf;
		WS_Release(ctx->ws, task->nmatches * sizeof(int));
	}
	else {
		WS_Release(ctx->ws, 0);

		switch(errkind) {
		case NO_ERROR:
		case NOT_IMPLEMENTED:
			break;
		case OUT_OF_MEMORY:
			VFAIL(ctx, ERR_PREFIX "RE2 lib indicates out-of-memory "
			      "during match, consider increasing max_mem",
			      set->vcl_name, subject);
			break;
		case NOT_COMPILED:
		case INCONSISTENT:
		default:
			WRONG("impossible or invalid error kind");
		}
	}

	return match;
}

#undef ERR_PREFIX

static struct task_set_match *
get_task_data(VRT_CTX, struct vmod_re2_set *set)
{
	struct vmod_priv *priv;
	struct task_set_match *task;

	priv = VRT_priv_task(ctx, set);
	if (priv == NULL || priv->priv == NULL)
		return NULL;
	WS_Assert_Allocated(ctx->ws, priv->priv, sizeof(*task));
	CAST_OBJ(task, priv->priv, TASK_SET_MATCH_MAGIC);
	return task;
}

VCL_BOOL
vmod_set_matched(VRT_CTX, struct vmod_re2_set *set, VCL_INT n)
{
	struct task_set_match *task;
	int hi, lo = 0;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_RE2_SET_MAGIC);
	if (n < 1 || n > set->npatterns) {
		VFAIL(ctx, "n=%jd out of range in %s.matched() (%d patterns)",
		      (intmax_t)n, set->vcl_name, set->npatterns);
		return 0;
	}

	if ((task = get_task_data(ctx, set)) == NULL) {
		VFAIL(ctx, "%s.matched(%jd) called without prior match",
		      set->vcl_name, (intmax_t)n);
		return 0;
	}

	if (task->nmatches == 0)
		return 0;
	WS_Assert_Allocated(ctx->ws, task->matches,
			    task->nmatches * sizeof(int));
	n--;
	hi = task->nmatches - 1;
	do {
		int m = lo + (hi - lo) / 2;
		if (task->matches[m] == n)
			return 1;
		if (task->matches[m] < n)
			lo = m + 1;
		else
			hi = m - 1;
	} while (lo <= hi);

	return 0;
}

VCL_INT
vmod_set_nmatches(VRT_CTX, struct vmod_re2_set *set)
{
	struct task_set_match *task;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_RE2_SET_MAGIC);
	if ((task = get_task_data(ctx, set)) == NULL) {
		VFAIL(ctx, "%s.nmatches() called without prior match",
		     set->vcl_name);
		return 0;
	}
	return task->nmatches;
}

static int
get_match_idx(VRT_CTX, struct vmod_re2_set *set, VCL_INT n, VCL_ENUM selects,
	      const char *method)
{
	struct task_set_match *task;
	int idx = 0;

	if (n > set->npatterns) {
		VFAIL(ctx, "%s.%s(%jd): set has %d patterns", set->vcl_name,
		      method, (intmax_t)n, set->npatterns);
		return -1;
	}
	if (n > 0)
		return n - 1;

	if ((task = get_task_data(ctx, set)) == NULL) {
		VFAIL(ctx, "%s.%s() called without prior match", set->vcl_name,
		      method);
		return -1;
	}

	if (task->nmatches == 0) {
		VFAIL(ctx, "%s.%s(%jd): previous match was unsuccessful",
		      set->vcl_name, method, (intmax_t)n);
		return -1;
	}
	if (task->nmatches > 1) {
		if (selects == VENUM(UNIQUE)) {
			VFAIL(ctx, "%s.%s(%jd): %ld successful matches",
			      set->vcl_name, method, (intmax_t)n,
			      task->nmatches);
			return -1;
		}
		if (selects == VENUM(LAST))
			idx = task->nmatches - 1;
		else
			assert(selects == VENUM(FIRST));
	}
	WS_Assert_Allocated(ctx->ws, task->matches,
			    task->nmatches * sizeof(int));
	return task->matches[idx];
}

VCL_INT
vmod_set_which(VRT_CTX, struct vmod_re2_set *set, VCL_ENUM selects)
{
	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_RE2_SET_MAGIC);
	return get_match_idx(ctx, set, 0, selects, "which") + 1;
}

static VCL_STRING
rewritef(VRT_CTX, struct vmod_re2_set * const restrict set,
	 VCL_STRING const restrict text, VCL_STRING const restrict rewrite,
	 VCL_STRING const restrict fallback, VCL_INT n,
	 VCL_ENUM const restrict selects, rewrite_e type)
{
	int idx;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_RE2_SET_MAGIC);
	if (set->regex == NULL) {
		VFAIL(ctx, "%s.%s(%jd): No regexen were saved for %s",
		      set->vcl_name, rewrite_name[type], (intmax_t)n,
		      set->vcl_name);
		return NULL;
	}

	idx = get_match_idx(ctx, set, n, selects, rewrite_name[type]);
	if (idx < 0)
		return NULL;
	if (!vbit_test(set->added[REGEX], idx)) {
		AN(selects);
		VFAIL(ctx, "%s.%s(%s, %s, %jd, %s): Pattern %d was not saved",
		      set->vcl_name, rewrite_name[type], text, rewrite,
		      (intmax_t)n, selects, idx + 1);
		return NULL;
	}
	return (regex_rewrite[type])(ctx, set->regex[idx], text, rewrite,
				     fallback);
}

VCL_STRING
vmod_set_sub(VRT_CTX, struct vmod_re2_set *set, VCL_STRING text,
	     VCL_STRING rewrite, VCL_STRING fallback, VCL_INT n,
	     VCL_ENUM selects)
{
	return rewritef(ctx, set, text, rewrite, fallback, n, selects, SUB);
}

VCL_STRING
vmod_set_suball(VRT_CTX, struct vmod_re2_set *set, VCL_STRING text,
		VCL_STRING rewrite, VCL_STRING fallback, VCL_INT n,
		VCL_ENUM selects)
{
	return rewritef(ctx, set, text, rewrite, fallback, n, selects, SUBALL);
}

VCL_STRING
vmod_set_extract(VRT_CTX, struct vmod_re2_set *set, VCL_STRING text,
		 VCL_STRING rewrite, VCL_STRING fallback, VCL_INT n,
		 VCL_ENUM selects)
{
	return rewritef(ctx, set, text, rewrite, fallback, n, selects, EXTRACT);
}

VCL_STRING
vmod_set_string(VRT_CTX, struct vmod_re2_set *set, VCL_INT n, VCL_ENUM selects)
{
	int idx;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_RE2_SET_MAGIC);
	if (set->string == NULL) {
		VFAIL(ctx, "%s.string(%jd): No strings were set for %s",
		      set->vcl_name, (intmax_t)n, set->vcl_name);
		return NULL;
	}

	idx = get_match_idx(ctx, set, n, selects, "string");
	if (idx < 0)
		return NULL;
	if (!vbit_test(set->added[STRING], idx)) {
		AN(selects);
		VFAIL(ctx, "%s.string(%jd, %s): String %d was not added",
		      set->vcl_name, (intmax_t)n, selects, idx + 1);
		return NULL;
	}
	return set->string[idx];
}

VCL_BACKEND
vmod_set_backend(VRT_CTX, struct vmod_re2_set *set, VCL_INT n, VCL_ENUM selects)
{
	int idx;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_RE2_SET_MAGIC);
	if (set->backend == NULL) {
		VFAIL(ctx, "%s.backend(%jd): No backends were set for %s",
		      set->vcl_name, (intmax_t)n, set->vcl_name);
		return NULL;
	}

	idx = get_match_idx(ctx, set, n, selects, "backend");
	if (idx < 0)
		return NULL;
	if (!vbit_test(set->added[BACKEND], idx)) {
		AN(selects);
		VFAIL(ctx, "%s.backend(%jd, %s): Backend %d was not added",
		      set->vcl_name, (intmax_t)n, selects, idx + 1);
		return NULL;
	}
	return set->backend[idx];
}

VCL_INT
vmod_set_integer(VRT_CTX, struct vmod_re2_set *set, VCL_INT n, VCL_ENUM selects)
{
	int idx;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_RE2_SET_MAGIC);
	if (set->integer == NULL) {
		VRT_fail(ctx,
			 "%s.integer(%jd): No integers were set for %s",
			 set->vcl_name, (intmax_t)n, set->vcl_name);
		return (0);
	}

	idx = get_match_idx(ctx, set, n, selects, "integer");
	if (idx < 0)
		return (0);
	if (!vbit_test(set->added[INTEGER], idx)) {
		AN(selects);
		VRT_fail(ctx, "%s.integer(%jd, %s): integer %d was not added",
			 set->vcl_name, n, selects, idx + 1);
		return (0);
	}
	return set->integer[idx];
}

VCL_SUB
vmod_set_subroutine(VRT_CTX, struct VPFX(re2_set) *set, VCL_INT n,
		    VCL_ENUM selects)
{
	int idx;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_RE2_SET_MAGIC);
	if (set->sub == NULL) {
		VRT_fail(ctx,
			 "%s.subroutine(%jd): No subroutines were set for %s",
			 set->vcl_name, (intmax_t)n, set->vcl_name);
		return (NULL);
	}

	idx = get_match_idx(ctx, set, n, selects, "subroutine");
	if (idx < 0)
		return (NULL);
	if (!vbit_test(set->added[SUBROUTINE], idx)) {
		AN(selects);
		VRT_fail(ctx,
			 "%s.subroutine(%jd, %s): subroutine %d was not added",
			 set->vcl_name, n, selects, idx + 1);
		return (NULL);
	}
	return set->sub[idx];
}

VCL_BOOL
vmod_set_check_call(VRT_CTX, struct VPFX(re2_set) *set, VCL_INT n,
		    VCL_ENUM selects)
{
	struct task_set_match *task;
	int idx = 0;
	VCL_STRING err;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_RE2_SET_MAGIC);
	if (set->sub == NULL) {
		VNOTICE(ctx,
			"%s.check_call(%jd): No subroutines were set for %s",
			set->vcl_name, (intmax_t)n, set->vcl_name);
		return (0);
	}

	/*
	 * XXX: considerable DRY with get_match_idx().
	 * get_match_idx() invokes VRT_fail(), but we need to log
	 * SLT_Notice here, and the two alternatives are hard to
	 * disentangle within get_match_idx(). An alternative would be to
	 * pass in a flag to get_match_idx() that chooses between the two,
	 * and then we have to add the flag everywhere else. Consider
	 * that if we add anything else that needs SLT_Notice.
	 */
	if (n > set->npatterns) {
		VNOTICE(ctx, "%s.check_call(%jd): set has %d patterns",
			set->vcl_name, (intmax_t)n, set->npatterns);
		return (0);
	}
	if (n <= 0) {
		if ((task = get_task_data(ctx, set)) == NULL) {
			VNOTICE(ctx,
				"%s.check_call() called without prior match",
				set->vcl_name);
			return (0);
		}
		if (task->nmatches == 0) {
			VNOTICE(ctx, "%s.check_call(%jd): previous match was "
				"unsuccessful", set->vcl_name, (intmax_t)n);
			return (0);
		}
		if (task->nmatches > 1) {
			if (selects == VENUM(UNIQUE)) {
				VNOTICE(ctx, "%s.check_call(%jd): %ld "
					"successful matches", set->vcl_name,
					(intmax_t)n, task->nmatches);
				return (0);
			}
			if (selects == VENUM(LAST))
				idx = task->nmatches - 1;
			else
				assert(selects == VENUM(FIRST));
		}
		WS_Assert_Allocated(ctx->ws, task->matches,
				    task->nmatches * sizeof(int));
		idx = task->matches[idx];
	}

	if (!vbit_test(set->added[SUBROUTINE], idx)) {
		AN(selects);
		VNOTICE(ctx,
			"%s.check_call(%jd, %s): subroutine %d was not added",
			set->vcl_name, n, selects, idx + 1);
		return (0);
	}
	if ((err = VRT_check_call(ctx, set->sub[idx])) != NULL) {
		VNOTICE(ctx, "%s.check_call(): %s", set->vcl_name, err);
		return (0);
	}

	return (1);
}

VCL_BOOL
vmod_set_saved(VRT_CTX, struct vmod_re2_set *set, VCL_ENUM whichs, VCL_INT n,
	       VCL_ENUM selects)
{
	int idx;

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

	idx = get_match_idx(ctx, set, n, selects, "saved");
	if (idx < 0)
		return 0;
	if (whichs == VENUM(REGEX))
		return vbit_test(set->added[REGEX], idx);
	if (whichs == VENUM(BE))
		return vbit_test(set->added[BACKEND], idx);
	if (whichs == VENUM(STR))
		return vbit_test(set->added[STRING], idx);
	if (whichs == VENUM(INT))
		return vbit_test(set->added[INTEGER], idx);
	if (whichs == VENUM(SUB))
		return vbit_test(set->added[SUBROUTINE], idx);
	WRONG("illegal which ENUM");
	return 0;
}

/* copied from Vinyl cache_http.c */
void
http_vslh_del(const struct http *hp, unsigned hdr)
{
	int i;

	if (hp->vsl != NULL) {
		/* We don't support unsetting stuff in the first line */
		assert (hdr >= HTTP_HDR_FIRST);
		assert(VXID_TAG(hp->vsl->wid));
		i = (HTTP_HDR_UNSET - HTTP_HDR_METHOD);
		i += hp->logtag;
		VSLbt(hp->vsl, (enum VSL_tag_e)i, hp->hd[hdr]);
	}
}

VCL_VOID
vmod_set_hdr_filter(VRT_CTX, struct VPFX(re2_set) *set, VCL_HTTP hp,
		    VCL_BOOL whitelist)
{
	int match = 0;
	const char *err;
	errorkind_e errkind = NO_ERROR;
	uint16_t u, v;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(set, VMOD_RE2_SET_MAGIC);
	CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);

	for (v = u = HTTP_HDR_FIRST; u < hp->nhd; u++) {
		const char *hdr;
		unsigned len;

		Tcheck(hp->hd[u]);
		hdr = hp->hd[u].b;
		len = Tlen(hp->hd[u]);
		if ((err = vre2set_matchonly(set->set, hdr, len, &match,
					     &errkind))
		    != NULL) {
			VFAIL(ctx, "%s.hdr_filter(%.*s): %s", set->vcl_name,
			      len, hdr, err);
			goto loop;
		}

		switch(errkind) {
		case NO_ERROR:
		case NOT_IMPLEMENTED:
			break;
		case OUT_OF_MEMORY:
			VFAIL(ctx, "%s.hdr_filter(%.*s): RE2 lib indicates "
			      "out-of-memory during match, consider increasing "
			      "max_mem", set->vcl_name, len, hdr);
			goto loop;
		case NOT_COMPILED:
		case INCONSISTENT:
		default:
			WRONG("impossible or invalid error kind");
		}

		/* cf. Vinyl http_Unset() */

		/* !a != !b <=> a XOR b */
		if (!whitelist != !match) {
			http_vslh_del(hp, u);
			continue;
		}
                if (v != u) {
                        memcpy(&hp->hd[v], &hp->hd[u], sizeof *hp->hd);
                        memcpy(&hp->hdf[v], &hp->hdf[u], sizeof *hp->hdf);
                }
	loop:
                v++;
	}
	hp->nhd = v;
}

VCL_BLOB
vmod_set_use(VRT_CTX, struct VPFX(re2_set) *set)
{
	(void)ctx;
	CHECK_OBJ_NOTNULL(set, VMOD_RE2_SET_MAGIC);
	return (&set->self);
}
