/*-
 * Copyright (c) 2021 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_selector.h"

#include <ctype.h>

#include "ph.h"

static struct match_data *
get_match_data(VRT_CTX, const struct vmod_selector_set *set, const char *method)
{
	struct vmod_priv *task;
	struct match_data *match;

	task = VRT_priv_task(ctx, set);
	AN(task);
	if (task->priv == NULL) {
		if ((task->priv = WS_Alloc(ctx->ws, (unsigned)sizeof(*match))) == NULL) {
			VERRNOMEM(ctx, "Allocating match data in %s.%s()",
				  set->vcl_name, method);
			return (NULL);
		}
		task->len = sizeof(*match);
		AZ(task->methods);
		match = (struct match_data *)task->priv;
		match->magic = MATCH_DATA_MAGIC;
	}
	else {
		AN(WS_Allocated(ctx->ws, task->priv, sizeof(*match)));
		CAST_OBJ(match, task->priv, MATCH_DATA_MAGIC);
	}
	return match;
}

VCL_BOOL
vmod_set_match(VRT_CTX, struct vmod_selector_set *set, VCL_STRING subject)
{
	unsigned idx;
	struct match_data *match;
	char **members;
	const char *subj;

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

	if (set->nmembers == 0) {
		VFAIL(ctx, "%s.match(): no entries were added", set->vcl_name);
		return (0);
	}
	if (set->hash == NULL) {
		VFAIL(ctx, "%s.match(): set was not compiled", set->vcl_name);
		return (0);
	}
	if (subject == NULL) {
		VNOTICE(ctx, "%s.match(): subject string is NULL",
			set->vcl_name);
		return (0);
	}

	members = set->members;
	subj = subject;
	if (!set->case_sensitive) {
		char *copy;

		if ((copy = WS_Copy(ctx->ws, subject, -1)) == NULL) {
			VERRNOMEM(ctx, "%s.match(): copying subject for "
				  "case-insensitive match", set->vcl_name);
			return (0);
		}
		for (char *c = copy; *c; c++)
				*c = (char)tolower(*c);
		subj = copy;
		members = set->lomembers;
	}
	AN(members);

	match = get_match_data(ctx, set, "match");
	if (match == NULL)
		return (0);
	if ((idx = PH_Lookup(set->hash, members, subj)) == UINT_MAX) {
		match->n = 0;
		return (0);
	}

	if ((match->indices = WS_Alloc(ctx->ws, (unsigned)sizeof(unsigned))) == NULL) {
		VERRNOMEM(ctx, "Reserving space for index in "
			  "%s.match(\"%.40s\")", set->vcl_name, subject);
		return (0);
	}
	*match->indices = idx;
	match->n = 1;
	match->exact = idx;
	return (1);
}

VCL_BOOL
vmod_set_hasprefix(VRT_CTX, struct vmod_selector_set *set, VCL_STRING subject)
{
	struct match_data *match;
	char **members;
	const char *subj;

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

	if (set->nmembers == 0) {
		VFAIL(ctx, "%s.hasprefix(): no entries were added",
		      set->vcl_name);
		return (0);
	}
	if (set->origo == NULL) {
		VFAIL(ctx, "%s.hasprefix(): set was not compiled",
		      set->vcl_name);
		return (0);
	}
	if (subject == NULL) {
		VNOTICE(ctx, "%s.hasprefix(): subject string is NULL",
			set->vcl_name);
		return (0);
	}

	members = set->members;
	subj = subject;
	if (!set->case_sensitive) {
		char *copy;

		if ((copy = WS_Copy(ctx->ws, subject, -1)) == NULL) {
			VERRNOMEM(ctx, "%s.hasprefix(): copying subject for "
				  "case-insensitive match", set->vcl_name);
			return (0);
		}
		for (char *c = copy; *c; c++)
			*c = (char)tolower(*c);
		subj = copy;
		members = set->lomembers;
	}
	AN(members);

	match = get_match_data(ctx, set, "hasprefix");
	if (match == NULL)
		return (0);
	if ((match->limit = WS_ReserveLumps(ctx->ws, sizeof(unsigned))) == 0) {
		VERRNOMEM(ctx, "Reserving index array in "
			  "%s.hasprefix(\"%.40s\")", set->vcl_name, subject);
		WS_Release(ctx->ws, 0);
		return (0);
	}
	match->indices = (unsigned *)WS_Reservation(ctx->ws);

	if (QP_Prefixes(set->origo, members, subj, match) != 0) {
		VERRNOMEM(ctx, "Adding indices in %s.hasprefix(\"%.40s\")",
			  set->vcl_name, subject);
		WS_Release(ctx->ws, 0);
		return (0);
	}

	WS_Release(ctx->ws, (unsigned)(match->n * sizeof(unsigned)));
	return (match->n > 0);
}

struct match_data *
get_existing_match_data(VRT_CTX,
			const struct vmod_selector_set * const restrict set,
			const char * const restrict method, int fail)
{
	struct vmod_priv *task;
	struct match_data *match;

	task = VRT_priv_task(ctx, set);
	AN(task);
	if (task->priv == NULL) {
		VFAIL_OR_NOTICE(ctx, fail, "%s.%s() called without prior match",
				set->vcl_name, method);
		return (NULL);
	}
	AN(WS_Allocated(ctx->ws, task->priv, sizeof(*match)));
	CAST_OBJ(match, task->priv, MATCH_DATA_MAGIC);
	return match;
}

VCL_INT
vmod_set_nmatches(VRT_CTX, struct vmod_selector_set *set)
{
	struct match_data *match;

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

	match = get_existing_match_data(ctx, set, "nmatches", 1);
	if (match == NULL)
		return (0);
	return (match->n);
}

VCL_BOOL
vmod_set_matched(VRT_CTX, struct VPFX(selector_set) *set, VCL_INT idx,
		 VCL_STRING element, VCL_ENUM selects)
{
	struct match_data *match;

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

	if (idx > set->nmembers) {
		VFAIL(ctx, "%s.matched(%ld) out of range (%d members)",
		      set->vcl_name, idx, set->nmembers);
		return (0);
	}

	match = get_existing_match_data(ctx, set, "matched", 1);
	if (match == NULL || match->n == 0)
		return (0);

	AN(match->indices);
	AN(WS_Allocated(ctx->ws, match->indices, match->n * sizeof(unsigned)));

	if (idx > 0) {
		/* XXX search algorithm? */
		idx--;
		for (unsigned i = 0; i < match->n; i++)
			if (match->indices[i] == idx)
				return (1);
		return (0);
	}

	if (element != NULL) {
		for (unsigned i = 0; i < match->n; i++)
			if (strcmp(set->members[match->indices[i]], element)
			    == 0)
				return (1);
		return (0);
	}

	if (selects == VENUM(UNIQUE))
		return (match->n == 1);
	if (selects == VENUM(EXACT))
		return (match->exact != UINT_MAX);
	return (1);
}
