/*-
 * 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"

static unsigned
select_match(VRT_CTX, const struct match_data * const restrict match,
       const char * const restrict obj, VCL_ENUM const restrict selects,
       const char * const restrict method, int fail)
{
	if (selects == VENUM(EXACT)) {
		if (match->exact == UINT_MAX)
			VFAIL_OR_NOTICE(ctx, fail, "%s.%s(select=EXACT): "
			      "no element matched exactly", obj, method);
		return match->exact;
	}
	if (match->n == 1)
		return match->indices[0];

	switch (selects[0]) {
	case 'U':
		assert(selects == VENUM(UNIQUE));
		VFAIL_OR_NOTICE(ctx, fail,
				"%s.%s(select=UNIQUE): %d elements were matched",
				obj, method, match->n);
		return (UINT_MAX);
	case 'L':
		if (selects == VENUM(LAST))
			return match->max;
		if (selects == VENUM(LONGEST))
			return match->indices[match->n - 1];
		WRONG("illegal select enum");
	case 'F':
		assert(selects == VENUM(FIRST));
		return match->min;
	case 'S':
		assert(selects == VENUM(SHORTEST));
		return match->indices[0];
	default:
		WRONG("illegal select enum");
	}
}

VCL_INT
vmod_set_which(VRT_CTX, struct vmod_selector_set *set, VCL_ENUM selects,
	       VCL_STRING element)
{
	struct match_data *match;

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

	if (element != NULL)
		if (!vmod_set_match(ctx, TRUST_ME(set), element)) {
			VFAIL(ctx, "%s.which(element=\"%s\"): no such element",
			      set->vcl_name, element);
			return (UINT_MAX);
		}

	match = get_existing_match_data(ctx, set, "which", 1);
	if (element != NULL) {
		CHECK_OBJ_NOTNULL(match, MATCH_DATA_MAGIC);
		assert(match->n == 1);
		return ((VCL_INT)match->indices[0] + 1);
	}
	if (match == NULL || match->n == 0)
		return (0);
	return ((VCL_INT)select_match(ctx, match, set->vcl_name, selects, "which", 1) + 1);
}

static unsigned
get_idx(VRT_CTX, VCL_INT narg, const struct vmod_selector_set * const restrict set,
	const char * const restrict method, VCL_STRING const restrict element,
	VCL_ENUM const restrict selects, int fail)
{
	struct match_data *match;

	if (narg > UINT_MAX) {
		errno = ERANGE;
		VFAIL_OR_NOTICE(ctx, fail, "%s.%s(%ld): out of range",
					set->vcl_name, method, narg);
		return (UINT_MAX);
	}


	if (narg > 0) {
		unsigned n = (unsigned)narg;
		if (n > set->nmembers) {
			VFAIL_OR_NOTICE(ctx, fail,
					"%s.%s(%u): set has %d elements",
					set->vcl_name, method, n,
					set->nmembers);
			return (UINT_MAX);
		}
		return (n - 1);
	}
	if (element != NULL)
		if (!vmod_set_match(ctx, TRUST_ME(set), element)) {
			VFAIL_OR_NOTICE(ctx, fail,
					"%s.%s(element=\"%s\"): no such element",
					set->vcl_name, method, element);
			return (UINT_MAX);
		}

	match = get_existing_match_data(ctx, set, method, fail);
	if (match == NULL || match->n == 0)
		return (UINT_MAX);
	return (select_match(ctx, match, set->vcl_name, selects, method, fail));
}

VCL_STRING
vmod_set_element(VRT_CTX, struct vmod_selector_set *set, VCL_INT n,
		 VCL_ENUM selects)
{
	unsigned idx;

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

	idx = get_idx(ctx, n, set, "element", NULL, selects, 1);
	if (idx == UINT_MAX)
		return (NULL);
	return (set->members[idx]);
}

static inline int
check_added(VRT_CTX, const struct vmod_selector_set * const restrict set,
	    unsigned idx, enum bitmap_e bitmap,
	    const char * const restrict method,
	    const char * const restrict type, int fail)
{
	if (!is_added(set, idx, bitmap)) {
		VFAIL_OR_NOTICE(ctx, fail,
				"%s.%s(): %s not added for element %u",
				set->vcl_name, method, type, idx + 1);
		return (0);
	}
	return (1);
}

VCL_BACKEND
vmod_set_backend(VRT_CTX, struct vmod_selector_set *set, VCL_INT n,
		 VCL_STRING element, VCL_ENUM selects)
{
	unsigned idx;
	VCL_BACKEND b;

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

	idx = get_idx(ctx, n, set, "backend", element, selects, 1);
	if (idx == UINT_MAX)
		return (NULL);
	if (!check_added(ctx, set, idx, BACKEND, "backend", "backend", 1))
		return (NULL);

	b = set->table[idx]->backend;
	CHECK_OBJ_ORNULL(b, DIRECTOR_MAGIC);
	return (b);
}

VCL_STRING
vmod_set_string(VRT_CTX, struct vmod_selector_set * set, VCL_INT n,
		VCL_STRING element, VCL_ENUM selects)
{
	unsigned idx;
	VCL_STRING s;

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

	idx = get_idx(ctx, n, set, "string", element, selects, 1);
	if (idx == UINT_MAX)
		return (NULL);
	if (!check_added(ctx, set, idx, STRING, "string", "string", 1))
		return (NULL);

	s = set->table[idx]->string;
	AN(s);
	return (s);
}

VCL_INT
vmod_set_integer(VRT_CTX, struct vmod_selector_set * set, VCL_INT n,
		 VCL_STRING element, VCL_ENUM selects)
{
	unsigned idx;

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

	idx = get_idx(ctx, n, set, "integer", element, selects, 1);
	if (idx == UINT_MAX)
		return (0);
	if (!check_added(ctx, set, idx, INTEGER, "integer", "integer", 1))
		return (0);

	return (set->table[idx]->integer);
}

static VCL_REGEX
get_re(VRT_CTX, const struct vmod_selector_set * const restrict set,
       VCL_INT n, VCL_STRING element, VCL_ENUM const restrict selects,
       const char * const restrict method)
{
	unsigned idx;
	VCL_REGEX re;

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

	idx = get_idx(ctx, n, set, method, element, selects, 1);
	if (idx == UINT_MAX)
		return (NULL);
	if (!check_added(ctx, set, idx, REGEX, method, "regex", 1))
		return (NULL);

	re = set->table[idx]->re;
	AN(re);
	return (re);
}

VCL_BOOL
vmod_set_re_match(VRT_CTX, struct vmod_selector_set *set, VCL_STRING subject,
		  VCL_INT n, VCL_STRING element, VCL_ENUM selects)
{
	VCL_REGEX re;
	(void)set;

	re = get_re(ctx, set, n, element, selects, "re_match");
	if (re == NULL)
		return (0);
	return (VRT_re_match(ctx, subject, re));
}

VCL_STRING
vmod_set_sub(VRT_CTX, struct vmod_selector_set *set, VCL_STRING str,
	     VCL_STRING sub, VCL_BOOL all, VCL_INT n, VCL_STRING element,
	     VCL_ENUM selects)
{
	VCL_REGEX re;
	(void)set;

	re = get_re(ctx, set, n, element, selects, "sub");
	if (re == NULL)
		return (NULL);
	return (VRT_regsub(ctx, all ? 1 : 0, str, re, sub));
}

VCL_BOOL
vmod_set_bool(VRT_CTX, struct VPFX(selector_set) *set, VCL_INT n,
	      VCL_STRING element, VCL_ENUM selects)
{
	unsigned idx;

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

	idx = get_idx(ctx, n, set, "bool", element, selects, 1);
	if (idx == UINT_MAX)
		return (0);
	if (!check_added(ctx, set, idx, BOOLEAN, "bool", "boolean", 1))
		return (0);

	return (set->table[idx]->bool);
}

VCL_SUB
vmod_set_subroutine(VRT_CTX, struct VPFX(selector_set) *set, VCL_INT n,
		    VCL_STRING element, VCL_ENUM selects)
{
	unsigned idx;

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

	idx = get_idx(ctx, n, set, "subroutine", element, selects, 1);
	if (idx == UINT_MAX)
		return (NULL);
	if (!check_added(ctx, set, idx, SUB, "subroutine", "subroutine", 1))
		return (NULL);

	return (set->table[idx]->sub);
}

VCL_BOOL
vmod_set_check_call(VRT_CTX, struct VPFX(selector_set) *set, VCL_INT n,
		    VCL_STRING element, VCL_ENUM selects)
{
	unsigned idx;
	VCL_STRING err;

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

	idx = get_idx(ctx, n, set, "check_call", element, selects, 0);
	if (idx == UINT_MAX)
		return (0);
	if (!check_added(ctx, set, idx, SUB, "check_call", "subroutine", 0))
		return (0);
	if ((err = VRT_check_call(ctx, set->table[idx]->sub)) != NULL) {
		VNOTICE(ctx, "%s.check_call(): %s", set->vcl_name, err);
		return (0);
	}

	return (1);
}
