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

#include "lib.h"

#include "sieve.h"
#include "sieve-code.h"
#include "sieve-commands.h"
#include "sieve-binary.h"
#include "sieve-generator.h"
#include "sieve-interpreter.h"
#include "sieve-dump.h"

#include "testsuite-common.h"
#include "testsuite-substitutions.h"

/*
 * Forward declarations
 */

void testsuite_opr_substitution_emit
	(struct sieve_binary_block *sblock, const struct testsuite_substitution *tsub,
		const char *param);

/*
 * Testsuite substitutions
 */

/* FIXME: make this extendible */

enum {
	TESTSUITE_SUBSTITUTION_FILE,
};

static const struct testsuite_substitution_def testsuite_file_substitution;

static const struct testsuite_substitution_def *substitutions[] = {
	&testsuite_file_substitution,
};

static const unsigned int substitutions_count = N_ELEMENTS(substitutions);

static inline const struct testsuite_substitution_def *
testsuite_substitution_get
(unsigned int code)
{
	if ( code >= substitutions_count )
		return NULL;

	return substitutions[code];
}

static const struct testsuite_substitution *testsuite_substitution_create
(struct sieve_ast *ast, const char *identifier)
{
	unsigned int i;

	for ( i = 0; i < substitutions_count; i++ ) {
		if ( strcasecmp(substitutions[i]->obj_def.identifier, identifier) == 0 ) {
			const struct testsuite_substitution_def *tsub_def = substitutions[i];
			struct testsuite_substitution *tsub;

			tsub = p_new(sieve_ast_pool(ast), struct testsuite_substitution, 1);
			tsub->object.def = &tsub_def->obj_def;
			tsub->object.ext = testsuite_ext;
			tsub->def = tsub_def;

			return tsub;
		}
	}

	return NULL;
}

/*
 * Substitution argument
 */

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

struct _testsuite_substitution_context {
	const struct testsuite_substitution *tsub;
	const char *param;
};

const struct sieve_argument_def testsuite_substitution_argument = {
	.identifier = "@testsuite-substitution",
	.generate = arg_testsuite_substitution_generate
};

struct sieve_ast_argument *testsuite_substitution_argument_create
(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_ast *ast,
	unsigned int source_line, const char *substitution, const char *param)
{
	const struct testsuite_substitution *tsub;
	struct _testsuite_substitution_context *tsctx;
	struct sieve_ast_argument *arg;
	pool_t pool;

	tsub = testsuite_substitution_create(ast, substitution);
	if ( tsub == NULL )
		return NULL;

	arg = sieve_ast_argument_create(ast, source_line);
	arg->type = SAAT_STRING;

	pool = sieve_ast_pool(ast);
	tsctx = p_new(pool, struct _testsuite_substitution_context, 1);
	tsctx->tsub = tsub;
	tsctx->param = p_strdup(pool, param);

	arg->argument = sieve_argument_create
		(ast, &testsuite_substitution_argument, testsuite_ext, 0);
	arg->argument->data = (void *) tsctx;

	return arg;
}

static bool arg_testsuite_substitution_generate
(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
	struct sieve_command *context ATTR_UNUSED)
{
	struct _testsuite_substitution_context *tsctx =
		(struct _testsuite_substitution_context *) arg->argument->data;

	testsuite_opr_substitution_emit(cgenv->sblock, tsctx->tsub, tsctx->param);

	return TRUE;
}

/*
 * Substitution operand
 */

static bool opr_substitution_dump
	(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
		sieve_size_t *address);
static int opr_substitution_read_value
	(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
		sieve_size_t *address, string_t **str);

const struct sieve_opr_string_interface testsuite_substitution_interface = {
	opr_substitution_dump,
	opr_substitution_read_value
};

const struct sieve_operand_def testsuite_substitution_operand = {
	.name = "test-substitution",
	.ext_def = &testsuite_extension,
	.code = TESTSUITE_OPERAND_SUBSTITUTION,
	.class = &string_class,
	.interface = &testsuite_substitution_interface
};

void testsuite_opr_substitution_emit
(struct sieve_binary_block *sblock, const struct testsuite_substitution *tsub,
	const char *param)
{
	/* Default variable storage */
	(void) sieve_operand_emit
		(sblock, testsuite_ext, &testsuite_substitution_operand);
	(void) sieve_binary_emit_unsigned(sblock, tsub->object.def->code);
	(void) sieve_binary_emit_cstring(sblock, param);
}

static bool opr_substitution_dump
(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
	sieve_size_t *address)
{
	unsigned int code = 0;
	const struct testsuite_substitution_def *tsub;
	string_t *param;

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

	tsub = testsuite_substitution_get(code);
	if ( tsub == NULL )
		return FALSE;

	if ( !sieve_binary_read_string(denv->sblock, address, &param) )
		return FALSE;

	if ( oprnd->field_name != NULL )
		sieve_code_dumpf(denv, "%s: TEST_SUBS %%{%s:%s}",
			oprnd->field_name, tsub->obj_def.identifier, str_c(param));
	else
		sieve_code_dumpf(denv, "TEST_SUBS %%{%s:%s}",
			tsub->obj_def.identifier, str_c(param));
	return TRUE;
}

static int opr_substitution_read_value
(const struct sieve_runtime_env *renv,
	const struct sieve_operand *oprnd ATTR_UNUSED, sieve_size_t *address,
	string_t **str_r)
{
	const struct testsuite_substitution_def *tsub;
	unsigned int code = 0;
	string_t *param;

	if ( !sieve_binary_read_unsigned(renv->sblock, address, &code) )
		return SIEVE_EXEC_BIN_CORRUPT;

	tsub = testsuite_substitution_get(code);
	if ( tsub == NULL )
		return SIEVE_EXEC_FAILURE;

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

		return SIEVE_EXEC_OK;
	}

	if ( !sieve_binary_read_string(renv->sblock, address, &param) )
		return SIEVE_EXEC_BIN_CORRUPT;

	if ( !tsub->get_value(str_c(param), str_r) )
		return SIEVE_EXEC_FAILURE;

	return SIEVE_EXEC_OK;
}

/*
 * Testsuite substitution definitions
 */

static bool testsuite_file_substitution_get_value
	(const char *param, string_t **result);

static const struct testsuite_substitution_def
testsuite_file_substitution = {
	SIEVE_OBJECT("file",
		&testsuite_substitution_operand,
		TESTSUITE_SUBSTITUTION_FILE),
	.get_value = testsuite_file_substitution_get_value
};

static bool testsuite_file_substitution_get_value
(const char *param, string_t **result)
{
	*result = t_str_new(256);

	str_printfa(*result, "[FILE: %s]", param);
	return TRUE;
}

