/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
 */

#include "lib.h"
#include "compat.h"
#include "mempool.h"
#include "hash.h"
#include "array.h"

#include "sieve-common.h"
#include "sieve-limits.h"
#include "sieve-extensions.h"
#include "sieve-commands.h"
#include "sieve-code.h"
#include "sieve-binary.h"
#include "sieve-comparators.h"
#include "sieve-validator.h"
#include "sieve-generator.h"
#include "sieve-interpreter.h"
#include "sieve-dump.h"

#include "sieve-match-types.h"

#include <string.h>

/*
 * Types
 */

struct sieve_match_values {
	pool_t pool;
	ARRAY(string_t *) values;
	unsigned count;
};

/*
 * Default match types
 */

const struct sieve_match_type_def *sieve_core_match_types[] = {
	&is_match_type, &contains_match_type, &matches_match_type
};

const unsigned int sieve_core_match_types_count =
	N_ELEMENTS(sieve_core_match_types);

/*
 * Match-type 'extension'
 */

static bool
mtch_validator_load(const struct sieve_extension *ext,
		    struct sieve_validator *valdtr);

const struct sieve_extension_def match_type_extension = {
	.name = "@match-types",
	.validator_load = mtch_validator_load
};

/*
 * Validator context:
 *   name-based match-type registry.
 */

static struct sieve_validator_object_registry *
_get_object_registry(struct sieve_validator *valdtr)
{
	struct sieve_instance *svinst;
	const struct sieve_extension *mcht_ext;

	svinst = sieve_validator_svinst(valdtr);
	mcht_ext = sieve_get_match_type_extension(svinst);
	return sieve_validator_object_registry_get(valdtr, mcht_ext);
}

void sieve_match_type_register(struct sieve_validator *valdtr,
			       const struct sieve_extension *ext,
			       const struct sieve_match_type_def *mcht_def)
{
	struct sieve_validator_object_registry *regs =
		_get_object_registry(valdtr);

	sieve_validator_object_registry_add(regs, ext, &mcht_def->obj_def);
}

static bool sieve_match_type_exists(struct sieve_validator *valdtr,
				    const char *identifier)
{
	struct sieve_validator_object_registry *regs =
		_get_object_registry(valdtr);

	return sieve_validator_object_registry_find(regs, identifier, NULL);
}

static const struct sieve_match_type *
sieve_match_type_create_instance(struct sieve_validator *valdtr,
				 struct sieve_command *cmd,
				 const char *identifier)
{
	struct sieve_validator_object_registry *regs =
		_get_object_registry(valdtr);
	struct sieve_object object;
	struct sieve_match_type *mcht;

	if (!sieve_validator_object_registry_find(regs, identifier, &object))
		return NULL;

	mcht = p_new(sieve_command_pool(cmd), struct sieve_match_type, 1);
	mcht->object = object;
	mcht->def = (const struct sieve_match_type_def *)object.def;

	return mcht;
}

bool mtch_validator_load(const struct sieve_extension *ext,
			 struct sieve_validator *valdtr)
{
	struct sieve_validator_object_registry *regs =
		sieve_validator_object_registry_init(valdtr, ext);
	unsigned int i;

	/* Register core match-types */
	for (i = 0; i < sieve_core_match_types_count; i++) {
		sieve_validator_object_registry_add(
			regs, NULL, &(sieve_core_match_types[i]->obj_def));
	}
	return TRUE;
}

/*
 * Interpreter context
 */

struct mtch_interpreter_context {
	struct sieve_match_values *match_values;
	bool match_values_enabled;
};

static void mtch_interpreter_free(const struct sieve_extension *ext ATTR_UNUSED,
				  struct sieve_interpreter *interp ATTR_UNUSED,
				  void *context)
{
	struct mtch_interpreter_context *mctx =
		(struct mtch_interpreter_context *)context;

	if (mctx->match_values != NULL)
		pool_unref(&mctx->match_values->pool);
}

struct sieve_interpreter_extension
mtch_interpreter_extension = {
	.ext_def = &match_type_extension,
	.free = mtch_interpreter_free
};

static inline struct mtch_interpreter_context *
get_interpreter_context(struct sieve_interpreter *interp, bool create)
{
	struct sieve_instance *svinst;
	const struct sieve_extension *mcht_ext;
	struct mtch_interpreter_context *ctx;

	svinst = sieve_interpreter_svinst(interp);
	mcht_ext = sieve_get_match_type_extension(svinst);

	ctx = (struct mtch_interpreter_context *)
		sieve_interpreter_extension_get_context(interp, mcht_ext);
	if (ctx == NULL && create) {
		pool_t pool = sieve_interpreter_pool(interp);
		ctx = p_new(pool, struct mtch_interpreter_context, 1);

		sieve_interpreter_extension_register(
			interp, mcht_ext, &mtch_interpreter_extension,
			ctx);
	}
	return ctx;
}

/*
 * Match values
 */

bool sieve_match_values_set_enabled(const struct sieve_runtime_env *renv,
				    bool enable)
{
	struct mtch_interpreter_context *ctx =
		get_interpreter_context(renv->interp, enable);

	if (ctx != NULL) {
		bool previous = ctx->match_values_enabled;

		ctx->match_values_enabled = enable;
		return previous;
	}
	return FALSE;
}

bool sieve_match_values_are_enabled(const struct sieve_runtime_env *renv)
{
	struct mtch_interpreter_context *ctx =
		get_interpreter_context(renv->interp, FALSE);

	return (ctx == NULL ? FALSE : ctx->match_values_enabled);
}

struct sieve_match_values *
sieve_match_values_start(const struct sieve_runtime_env *renv)
{
	struct mtch_interpreter_context *ctx =
		get_interpreter_context(renv->interp, FALSE);
	struct sieve_match_values *match_values;

	if (ctx == NULL || !ctx->match_values_enabled)
		return NULL;

	pool_t pool = pool_alloconly_create("sieve_match_values", 1024);

	match_values = p_new(pool, struct sieve_match_values, 1);
	match_values->pool = pool;
	match_values->count = 0;

	p_array_init(&match_values->values, pool, 4);

	return match_values;
}

static string_t *
sieve_match_values_add_entry(struct sieve_match_values *mvalues)
{
	string_t *entry;

	if (mvalues == NULL)
		return NULL;
	if (mvalues->count >= SIEVE_MAX_MATCH_VALUES)
		return NULL;

	if (mvalues->count >= array_count(&mvalues->values)) {
		entry = str_new(mvalues->pool, 64);
		array_append(&mvalues->values, &entry, 1);
	} else {
		string_t *const *ep =
			array_idx(&mvalues->values, mvalues->count);
		entry = *ep;
		str_truncate(entry, 0);
	}

	mvalues->count++;
	return entry;
}

void sieve_match_values_set(struct sieve_match_values *mvalues,
			    unsigned int index, string_t *value)
{
	if (mvalues != NULL && index < array_count(&mvalues->values)) {
		string_t *const *ep = array_idx(&mvalues->values, index);
		string_t *entry = *ep;

		if (entry != NULL && value != NULL) {
			str_truncate(entry, 0);
			str_append_str(entry, value);
		}
	}
}

void sieve_match_values_add(struct sieve_match_values *mvalues,
			    string_t *value)
{
	string_t *entry = sieve_match_values_add_entry(mvalues);

	if (entry != NULL && value != NULL)
		str_append_str(entry, value);
}

void sieve_match_values_add_cstr(struct sieve_match_values *mvalues,
				 const char *value)
{
	string_t *entry = sieve_match_values_add_entry(mvalues);

	if (entry != NULL && value != NULL)
		str_append(entry, value);
}

void sieve_match_values_add_char(struct sieve_match_values *mvalues, char c)
{
	string_t *entry = sieve_match_values_add_entry(mvalues);

	if (entry != NULL)
		str_append_c(entry, c);
}

void sieve_match_values_skip(struct sieve_match_values *mvalues, int num)
{
	int i;

	for (i = 0; i < num; i++)
		(void)sieve_match_values_add_entry(mvalues);
}

void sieve_match_values_commit(const struct sieve_runtime_env *renv,
			       struct sieve_match_values **mvalues)
{
	struct mtch_interpreter_context *ctx;

	if ((*mvalues) == NULL)
		return;

	ctx = get_interpreter_context(renv->interp, FALSE);
	if (ctx == NULL || !ctx->match_values_enabled)
		return;

	if (ctx->match_values != NULL) {
		pool_unref(&ctx->match_values->pool);
		ctx->match_values = NULL;
	}

	ctx->match_values = *mvalues;
	*mvalues = NULL;
}

void sieve_match_values_abort(struct sieve_match_values **mvalues)
{
	if ((*mvalues) == NULL)
		return;

	pool_unref(&(*mvalues)->pool);
	*mvalues = NULL;
}

void sieve_match_values_get(const struct sieve_runtime_env *renv,
			    unsigned int index, string_t **value_r)
{
	struct mtch_interpreter_context *ctx =
		get_interpreter_context(renv->interp, FALSE);
	struct sieve_match_values *mvalues;

	if (ctx == NULL || ctx->match_values == NULL) {
		*value_r = NULL;
		return;
	}

	mvalues = ctx->match_values;
	if (index < array_count(&mvalues->values) && index < mvalues->count) {
		string_t *const *entry = array_idx(&mvalues->values, index);

		*value_r = *entry;
		return;
	}

	*value_r = NULL;
}

/*
 * Match-type tagged argument
 */

/* Forward declarations */

static bool
tag_match_type_is_instance_of(struct sieve_validator *valdtr,
			      struct sieve_command *cmd,
			      const struct sieve_extension *ext,
			      const char *identifier, void **data);
static bool
tag_match_type_validate(struct sieve_validator *valdtr,
			struct sieve_ast_argument **arg,
			struct sieve_command *cmd);
static bool
tag_match_type_generate(const struct sieve_codegen_env *cgenv,
			struct sieve_ast_argument *arg,
			struct sieve_command *cmd);

/* Argument object */

const struct sieve_argument_def match_type_tag = {
	.identifier = "MATCH-TYPE",
	.is_instance_of = tag_match_type_is_instance_of,
	.validate = tag_match_type_validate,
	.generate = tag_match_type_generate
};

/* Argument implementation */

static bool
tag_match_type_is_instance_of(struct sieve_validator *valdtr,
			      struct sieve_command *cmd,
			      const struct sieve_extension *ext ATTR_UNUSED,
			      const char *identifier, void **data)
{
	const struct sieve_match_type *mcht;

	if (data == NULL)
		return sieve_match_type_exists(valdtr, identifier);

	if ((mcht = sieve_match_type_create_instance(
		valdtr, cmd, identifier)) == NULL)
		return FALSE;

	*data = (void *)mcht;
	return TRUE;
}

static bool
tag_match_type_validate(struct sieve_validator *valdtr,
			struct sieve_ast_argument **arg,
			struct sieve_command *cmd ATTR_UNUSED)
{
	const struct sieve_match_type *mcht =
		(const struct sieve_match_type *)(*arg)->argument->data;
	struct sieve_match_type_context *mtctx;

	mtctx = p_new(sieve_command_pool(cmd),
		      struct sieve_match_type_context, 1);
	mtctx->match_type = mcht;
	mtctx->argument = *arg;
	mtctx->comparator = NULL; /* Can be filled in later */

	(*arg)->argument->data = mtctx;

	/* Syntax:
	 *   ":is" / ":contains" / ":matches" (subject to extension)
	 */

	/* Skip tag */
	*arg = sieve_ast_argument_next(*arg);

	/* Check whether this match type requires additional validation.
	   Additional validation can override the match type recorded in the
	   context for later code generation.
	 */
	if (mcht->def != NULL && mcht->def->validate != NULL)
		return mcht->def->validate(valdtr, arg, mtctx);

	return TRUE;
}

static bool
tag_match_type_generate(const struct sieve_codegen_env *cgenv,
			struct sieve_ast_argument *arg,
			struct sieve_command *cmd ATTR_UNUSED)
{
	struct sieve_match_type_context *mtctx =
		(struct sieve_match_type_context *)arg->argument->data;

	(void)sieve_opr_match_type_emit(cgenv->sblock, mtctx->match_type);
	return TRUE;
}

void sieve_match_types_link_tags(struct sieve_validator *valdtr,
				 struct sieve_command_registration *cmd_reg,
				 int id_code)
{
	struct sieve_instance *svinst;
	const struct sieve_extension *mcht_ext;

	svinst = sieve_validator_svinst(valdtr);
	mcht_ext = sieve_get_comparator_extension(svinst);

	sieve_validator_register_tag(valdtr, cmd_reg, mcht_ext,
				     &match_type_tag, id_code);
}

/*
 * Validation
 */

bool sieve_match_type_validate(struct sieve_validator *valdtr,
			       struct sieve_command *cmd,
			       struct sieve_ast_argument *key_arg,
			       const struct sieve_match_type *mcht_default,
			       const struct sieve_comparator *cmp_default)
{
	struct sieve_ast_argument *arg = sieve_command_first_argument(cmd);
	struct sieve_ast_argument *mt_arg = NULL;
	struct sieve_match_type_context *mtctx;
	const struct sieve_match_type *mcht = NULL;
	const struct sieve_comparator *cmp = NULL;

	/* Find match type and comparator among the arguments */
	while (arg != NULL && arg != cmd->first_positional) {
		if (sieve_argument_is_comparator(arg)) {
			cmp = sieve_comparator_tag_get(arg);
			if (mt_arg != NULL)
				break;
		}

		if (sieve_argument_is_match_type(arg)) {
			mt_arg = arg;
			if (cmp != NULL)
				break;
		}
		arg = sieve_ast_argument_next(arg);
	}

	/* Verify the default match type if none is specified explicitly */
	if (mt_arg == NULL || mt_arg->argument == NULL ||
	    mt_arg->argument->data == NULL) {
		mcht = sieve_match_type_copy(sieve_command_pool(cmd),
					     mcht_default);
		mtctx = t_new(struct sieve_match_type_context, 1);
		mtctx->command = cmd;
		mtctx->match_type = mcht;
	} else {
		mtctx = (struct sieve_match_type_context *)
			mt_arg->argument->data;
		mcht = mtctx->match_type;
	}

	/* Verify using the default comparator if none is specified explicitly */
	if (cmp != NULL )
		mtctx->comparator_specified = TRUE;
	else
		cmp = sieve_comparator_copy(sieve_command_pool(cmd), cmp_default);
	mtctx->comparator = cmp;

	/* Check whether this match type requires additional validation.
	   Additional validation can override the match type recorded in the
	   context for later code generation.
	 */
	if (mcht != NULL && mcht->def != NULL &&
	    mcht->def->validate_context != NULL) {
		return mcht->def->validate_context(valdtr, mt_arg,
						   mtctx, key_arg);
	}

	return TRUE;
}

void sieve_match_type_arguments_remove(
	struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd)
{
	struct sieve_ast_argument *arg = sieve_command_first_argument(cmd);

	/* Remove any comparator and match type arguments */
	while (arg != NULL && arg != cmd->first_positional) {
		if (sieve_argument_is_comparator(arg)) {
			arg = sieve_ast_arguments_detach(arg, 1);
			continue;
		}

		if (sieve_argument_is_match_type(arg)) {
			arg = sieve_ast_arguments_detach(arg, 1);
			continue;
		}

		arg = sieve_ast_argument_next(arg);
	}
}

/*
 * Match-type operand
 */

const struct sieve_operand_class sieve_match_type_operand_class =
	{ "match type" };

static const struct sieve_extension_objects core_match_types =
	SIEVE_EXT_DEFINE_MATCH_TYPES(sieve_core_match_types);

const struct sieve_operand_def match_type_operand = {
	.name = "match-type",
	.code = SIEVE_OPERAND_MATCH_TYPE,
	.class = &sieve_match_type_operand_class,
	.interface = &core_match_types
};

/*
 * Common validation implementation
 */

bool sieve_match_substring_validate_context(
	struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
	struct sieve_match_type_context *ctx,
	struct sieve_ast_argument *key_arg ATTR_UNUSED)
{
	const struct sieve_comparator *cmp = ctx->comparator;

	if (cmp == NULL || cmp->def == NULL)
		return TRUE;

	if ((cmp->def->flags & SIEVE_COMPARATOR_FLAG_SUBSTRING_MATCH) == 0) {
		sieve_argument_validate_error(
			valdtr, arg,
			"the specified %s comparator does not support "
			"sub-string matching as required by the :%s match type",
			cmp->object.def->identifier,
			ctx->match_type->object.def->identifier);
		return FALSE;
	}
	return TRUE;
}
