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

#include "lib.h"
#include "hash.h"
#include "str.h"
#include "array.h"

#include "sieve-common.h"
#include "sieve-extensions.h"
#include "sieve-ast.h"
#include "sieve-binary.h"
#include "sieve-code.h"
#include "sieve-match-types.h"
#include "sieve-commands.h"
#include "sieve-validator.h"
#include "sieve-generator.h"
#include "sieve-dump.h"
#include "sieve-interpreter.h"

#include "ext-variables-common.h"
#include "ext-variables-limits.h"
#include "ext-variables-name.h"
#include "ext-variables-dump.h"
#include "ext-variables-operands.h"

/*
 * Variable operand
 */

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

const struct sieve_opr_string_interface variable_interface = {
	opr_variable_dump,
	opr_variable_read_value
};

const struct sieve_operand_def variable_operand = {
	.name = "variable",
	.ext_def = &variables_extension,
	.code = EXT_VARIABLES_OPERAND_VARIABLE,
	.class = &string_class,
	.interface = &variable_interface
};

void sieve_variables_opr_variable_emit
(struct sieve_binary_block *sblock, const struct sieve_extension *var_ext,
	struct sieve_variable *var)
{
	i_assert( sieve_extension_is(var_ext, variables_extension) );

	if ( var->ext == NULL ) {
		/* Default variable storage */
		(void) sieve_operand_emit(sblock, var_ext, &variable_operand);
		(void) sieve_binary_emit_byte(sblock, 0); /* Default */
		(void) sieve_binary_emit_unsigned(sblock, var->index);
		return;
	}

	(void) sieve_operand_emit(sblock, var_ext, &variable_operand);
	(void) sieve_binary_emit_extension(sblock, var->ext, 1); /* Extension */
	(void) sieve_binary_emit_unsigned(sblock, var->index);
}

static bool opr_variable_dump
(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
	sieve_size_t *address)
{
	const struct sieve_extension *this_ext = oprnd->ext;
	unsigned int index = 0;
	const struct sieve_extension *ext;
	unsigned int code = 1; /* Initially set to offset value */
	const char *identifier;

	if ( !sieve_binary_read_extension(denv->sblock, address, &code, &ext) )
		return FALSE;

	if ( !sieve_binary_read_unsigned(denv->sblock, address, &index) )
		return FALSE;

	identifier = ext_variables_dump_get_identifier(this_ext, denv, ext, index);
	identifier = identifier == NULL ? "??" : identifier;

	if ( oprnd->field_name != NULL )
		sieve_code_dumpf(denv, "%s: VAR[%s] ${%s}",
			oprnd->field_name, sieve_ext_variables_get_varid(ext, index), identifier);
	else
		sieve_code_dumpf(denv, "VAR[%s] ${%s}",
			sieve_ext_variables_get_varid(ext, index), identifier);

	return TRUE;
}

static int opr_variable_read_value
(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
	sieve_size_t *address, string_t **str_r)
{
	const struct sieve_extension *this_ext = oprnd->ext;
	const struct sieve_extension *ext;
	unsigned int code = 1; /* Initially set to offset value */
	struct sieve_variable_storage *storage;
	unsigned int index = 0;

	if ( !sieve_binary_read_extension(renv->sblock, address, &code, &ext) ) {
		sieve_runtime_trace_operand_error(renv, oprnd,
			"variable operand corrupt: invalid extension byte");
		return SIEVE_EXEC_BIN_CORRUPT;
	}

	storage = sieve_ext_variables_runtime_get_storage
		(this_ext, renv, ext);
	if ( storage == NULL ) {
		sieve_runtime_trace_operand_error(renv, oprnd,
			"variable operand corrupt: extension has no storage");
		return SIEVE_EXEC_BIN_CORRUPT;
	}

	if ( sieve_binary_read_unsigned(renv->sblock, address, &index) ) {
		/* Parameter str can be NULL if we are requested to only skip and not
		 * actually read the argument.
		 */
		if ( str_r != NULL ) {
			if ( !sieve_variable_get(storage, index, str_r) )
				return SIEVE_EXEC_FAILURE;

			if ( *str_r == NULL ) *str_r = t_str_new(0);
		}

		return SIEVE_EXEC_OK;
	}

	sieve_runtime_trace_operand_error(renv, oprnd,
		"variable operand corrupt: invalid variable index");
	return SIEVE_EXEC_BIN_CORRUPT;
}

int sieve_variable_operand_read_data
(const struct sieve_runtime_env *renv, struct sieve_operand *oprnd,
	sieve_size_t *address, const char *field_name,
	struct sieve_variable_storage **storage_r, unsigned int *var_index_r)
{
	const struct sieve_extension *ext;
	unsigned int code = 1; /* Initially set to offset value */
	unsigned int idx = 0;

	oprnd->field_name = field_name;

	if ( !sieve_operand_is_variable(oprnd) ) {
		sieve_runtime_trace_operand_error(renv, oprnd,
			"expected variable operand but found %s",	sieve_operand_name(oprnd));
		return SIEVE_EXEC_BIN_CORRUPT;
	}

	if ( !sieve_binary_read_extension(renv->sblock, address, &code, &ext) ) {
		sieve_runtime_trace_operand_error(renv, oprnd,
			"variable operand corrupt: invalid extension byte");
		return SIEVE_EXEC_BIN_CORRUPT;
	}

	*storage_r = sieve_ext_variables_runtime_get_storage
		(oprnd->ext, renv, ext);
	if ( *storage_r == NULL )	{
		sieve_runtime_trace_operand_error(renv, oprnd,
			"variable operand corrupt: extension has no storage");
		return SIEVE_EXEC_BIN_CORRUPT;
	}

	if ( !sieve_binary_read_unsigned(renv->sblock, address, &idx) ) {
		sieve_runtime_trace_operand_error(renv, oprnd,
			"variable operand corrupt: invalid variable index");
		return SIEVE_EXEC_BIN_CORRUPT;
	}

	*var_index_r = idx;
	return SIEVE_EXEC_OK;
}

int sieve_variable_operand_read
(const struct sieve_runtime_env *renv, sieve_size_t *address,
	const char *field_name, struct sieve_variable_storage **storage_r,
	unsigned int *var_index_r)
{
	struct sieve_operand operand;
	int ret;

	if ( (ret=sieve_operand_runtime_read(renv, address, field_name, &operand))
		<= 0)
		return ret;

	return sieve_variable_operand_read_data
		(renv, &operand, address, field_name, storage_r, var_index_r);
}

/*
 * Match value operand
 */

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

const struct sieve_opr_string_interface match_value_interface = {
	opr_match_value_dump,
	opr_match_value_read
};

const struct sieve_operand_def match_value_operand = {
	.name = "match-value",
	.ext_def = &variables_extension,
	.code = EXT_VARIABLES_OPERAND_MATCH_VALUE,
	.class = &string_class,
	.interface = &match_value_interface
};

void sieve_variables_opr_match_value_emit
(struct sieve_binary_block *sblock, const struct sieve_extension *var_ext,
	unsigned int index)
{
	i_assert( sieve_extension_is(var_ext, variables_extension) );
	(void) sieve_operand_emit(sblock, var_ext, &match_value_operand);
	(void) sieve_binary_emit_unsigned(sblock, index);
}

static bool opr_match_value_dump
(const struct sieve_dumptime_env *denv,	const struct sieve_operand *oprnd,
	sieve_size_t *address)
{
	unsigned int index = 0;

	if (sieve_binary_read_unsigned(denv->sblock, address, &index) ) {
		if ( oprnd->field_name != NULL )
			sieve_code_dumpf
				(denv, "%s: MATCHVAL %lu", oprnd->field_name, (unsigned long) index);
		else
			sieve_code_dumpf(denv, "MATCHVAL %lu", (unsigned long) index);

		return TRUE;
	}

	return FALSE;
}

static int opr_match_value_read
(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
	sieve_size_t *address, string_t **str_r)
{
	const struct sieve_extension *this_ext = oprnd->ext;
	const struct ext_variables_config *config =
		ext_variables_get_config(this_ext);
	unsigned int index = 0;

	if ( sieve_binary_read_unsigned(renv->sblock, address, &index) ) {
		/* Parameter str can be NULL if we are requested to only skip and not
		 * actually read the argument.
		 	*/
		if ( str_r != NULL ) {
			sieve_match_values_get(renv, index, str_r);

			if ( *str_r == NULL )
				*str_r = t_str_new(0);
			else if ( str_len(*str_r) > config->max_variable_size )
				str_truncate_utf8(*str_r, config->max_variable_size);
		}

		return SIEVE_EXEC_OK;
	}

	sieve_runtime_trace_operand_error(renv, oprnd,
		"match value operand corrupt: invalid index data");
	return SIEVE_EXEC_BIN_CORRUPT;
}
