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

#include "sieve-common.h"
#include "sieve-commands.h"
#include "sieve-code.h"
#include "sieve-binary.h"
#include "sieve-dump.h"

#include "ext-variables-common.h"
#include "ext-variables-namespaces.h"

#include <ctype.h>

/*
 * Namespace registry
 */

void sieve_variables_namespace_register(
	const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
	const struct sieve_extension *ext,
	const struct sieve_variables_namespace_def *nspc_def)
{
	struct ext_variables_validator_context *ctx =
		ext_variables_validator_context_get(var_ext, valdtr);

	sieve_validator_object_registry_add(ctx->namespaces, ext,
					    &nspc_def->obj_def);
}

bool ext_variables_namespace_exists(const struct sieve_extension *var_ext,
				    struct sieve_validator *valdtr,
				    const char *identifier)
{
	struct ext_variables_validator_context *ctx =
		ext_variables_validator_context_get(var_ext, valdtr);

	return sieve_validator_object_registry_find(ctx->namespaces, identifier,
						    NULL);
}

const struct sieve_variables_namespace *
ext_variables_namespace_create_instance(const struct sieve_extension *var_ext,
					struct sieve_validator *valdtr,
					struct sieve_command *cmd,
					const char *identifier)
{
	struct ext_variables_validator_context *ctx =
		ext_variables_validator_context_get(var_ext, valdtr);
	struct sieve_object object;
	struct sieve_variables_namespace *nspc;
	pool_t pool;

	if (!sieve_validator_object_registry_find(ctx->namespaces, identifier,
						  &object))
		return NULL;

	pool = sieve_command_pool(cmd);
	nspc = p_new(pool, struct sieve_variables_namespace, 1);
	nspc->object = object;
	nspc->def = (const struct sieve_variables_namespace_def *) object.def;

	return nspc;
}

/*
 * Namespace variable argument
 */

struct arg_namespace_variable {
	const struct sieve_variables_namespace *namespace;

	void *data;
};

static bool
arg_namespace_generate(const struct sieve_codegen_env *cgenv,
		       struct sieve_ast_argument *arg,
		       struct sieve_command *context ATTR_UNUSED);

const struct sieve_argument_def namespace_argument = {
	.identifier = "@namespace",
	.generate = arg_namespace_generate,
};

bool ext_variables_namespace_argument_activate(
	const struct sieve_extension *this_ext, struct sieve_validator *valdtr,
	struct sieve_ast_argument *arg, struct sieve_command *cmd,
	ARRAY_TYPE(sieve_variable_name) *var_name, bool assignment)
{
	pool_t pool = sieve_command_pool(cmd);
	struct sieve_ast *ast = arg->ast;
	const struct sieve_variables_namespace *nspc;
	struct arg_namespace_variable *var;
	const struct sieve_variable_name *name_element = array_idx(var_name, 0);
	void *var_data = NULL;

	nspc = ext_variables_namespace_create_instance(
		this_ext, valdtr, cmd, str_c(name_element->identifier));
	if (nspc == NULL) {
		sieve_argument_validate_error(
			valdtr, arg,
			"referring to variable in unknown namespace '%s'",
			str_c(name_element->identifier));
		return FALSE;
	}

	if (nspc->def != NULL && nspc->def->validate != NULL &&
	    !nspc->def->validate(valdtr, nspc, arg, cmd, var_name, &var_data,
				 assignment))
		return FALSE;

	var = p_new(pool, struct arg_namespace_variable, 1);
	var->namespace = nspc;
	var->data = var_data;

	arg->argument = sieve_argument_create(ast, &namespace_argument,
					      this_ext, 0);
	arg->argument->data = var;

	return TRUE;
}

struct sieve_ast_argument *
ext_variables_namespace_argument_create(
	const struct sieve_extension *this_ext, struct sieve_validator *valdtr,
	struct sieve_ast_argument *parent_arg, struct sieve_command *cmd,
	ARRAY_TYPE(sieve_variable_name) *var_name)
{
	struct sieve_ast *ast = parent_arg->ast;
	struct sieve_ast_argument *new_arg;

	new_arg = sieve_ast_argument_create(
		ast, sieve_ast_argument_line(parent_arg));
	new_arg->type = SAAT_STRING;

	if (!ext_variables_namespace_argument_activate(
		this_ext, valdtr, new_arg, cmd, var_name, FALSE))
		return NULL;

	return new_arg;
}

static bool
arg_namespace_generate(const struct sieve_codegen_env *cgenv,
		       struct sieve_ast_argument *arg,
		       struct sieve_command *cmd)
{
	struct sieve_argument *argument = arg->argument;
	struct arg_namespace_variable *var =
		(struct arg_namespace_variable *)argument->data;
	const struct sieve_variables_namespace *nspc = var->namespace;

	if (nspc->def != NULL && nspc->def->generate != NULL)
		return nspc->def->generate(cgenv, nspc, arg, cmd, var->data);

	return TRUE;
}

/*
 * Namespace variable operands
 */

const struct sieve_operand_class sieve_variables_namespace_operand_class =
	{ "variable-namespace" };

static bool
opr_namespace_variable_dump(const struct sieve_dumptime_env *denv,
			    const struct sieve_operand *oprnd,
			    sieve_size_t *address);
static int
opr_namespace_variable_read(const struct sieve_runtime_env *renv,
			    const struct sieve_operand *oprnd,
			    sieve_size_t *address, string_t **str_r);

static const struct sieve_opr_string_interface namespace_variable_interface = {
	opr_namespace_variable_dump,
	opr_namespace_variable_read,
};

const struct sieve_operand_def namespace_variable_operand = {
	.name = "namespace",
	.ext_def = &variables_extension,
	.code = EXT_VARIABLES_OPERAND_NAMESPACE_VARIABLE,
	.class = &string_class,
	.interface = &namespace_variable_interface,
};

void sieve_variables_opr_namespace_variable_emit(
	struct sieve_binary_block *sblock,
	const struct sieve_extension *var_ext,
	const struct sieve_extension *ext,
	const struct sieve_variables_namespace_def *nspc_def)
{
	i_assert(sieve_extension_is(var_ext, variables_extension));
	sieve_operand_emit(sblock, var_ext, &namespace_variable_operand);
	sieve_opr_object_emit(sblock, ext, &nspc_def->obj_def);
}

static bool
opr_namespace_variable_dump(const struct sieve_dumptime_env *denv,
			    const struct sieve_operand *oprnd,
			    sieve_size_t *address)
{
	struct sieve_variables_namespace nspc;
	struct sieve_operand nsoprnd;

	if (!sieve_operand_read(denv->sblock, address, NULL, &nsoprnd))
		return FALSE;
	if (!sieve_opr_object_read_data(
		denv->sblock, &nsoprnd,
		&sieve_variables_namespace_operand_class, address,
		&nspc.object))
		return FALSE;

	nspc.def = (const struct sieve_variables_namespace_def *)
		nspc.object.def;

	if (nspc.def == NULL || nspc.def->dump_variable == NULL)
		return FALSE;

	return nspc.def->dump_variable(denv, &nspc, oprnd, address);
}

static int
opr_namespace_variable_read(const struct sieve_runtime_env *renv,
			    const struct sieve_operand *oprnd,
			    sieve_size_t *address, string_t **str_r)
{
	struct sieve_variables_namespace nspc;

	if (!sieve_opr_object_read(
		renv, &sieve_variables_namespace_operand_class, address,
		&nspc.object)) {
		sieve_runtime_trace_operand_error(renv, oprnd,
			"variable namespace operand corrupt: failed to read");
		return SIEVE_EXEC_BIN_CORRUPT;
	}

	nspc.def = (const struct sieve_variables_namespace_def *)
		nspc.object.def;

	if (nspc.def == NULL || nspc.def->read_variable == NULL)
		return SIEVE_EXEC_FAILURE;

	return nspc.def->read_variable(renv, &nspc, oprnd, address, str_r);
}
