/* Copyright (c) Pigeonhole authors, see top-level COPYING file */

#include "lib.h"
#include "str.h"

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

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

/*
 * Code dumper extension
 */

static void
ext_variables_code_dumper_free(struct sieve_code_dumper *dumper, void *context);

static const struct sieve_code_dumper_extension
variables_dump_extension = {
	&variables_extension,
	ext_variables_code_dumper_free,
};

/*
 * Code dump context
 */

struct ext_variables_dump_context {
	struct sieve_variable_scope *local_scope;
	ARRAY(struct sieve_variable_scope *) ext_scopes;
};

static void
ext_variables_code_dumper_free(struct sieve_code_dumper *dumper ATTR_UNUSED,
			       void *context)
{
	struct ext_variables_dump_context *dctx =
		(struct ext_variables_dump_context *)context;

	if (dctx == NULL || dctx->local_scope == NULL)
		return;

	sieve_variable_scope_unref(&dctx->local_scope);
}

static struct ext_variables_dump_context *
ext_variables_dump_get_context(const struct sieve_extension *this_ext,
			       const struct sieve_dumptime_env *denv)
{
	struct sieve_code_dumper *dumper = denv->cdumper;
	struct ext_variables_dump_context *dctx;
	pool_t pool;

	i_assert(sieve_extension_is(this_ext, variables_extension));
	dctx = sieve_dump_extension_get_context(dumper, this_ext);

	if (dctx == NULL) {
		/* Create dumper context */
		pool = sieve_code_dumper_pool(dumper);
		dctx = p_new(pool, struct ext_variables_dump_context, 1);
		p_array_init(&dctx->ext_scopes, pool,
			     sieve_extensions_get_count(this_ext->svinst));

		sieve_dump_extension_register(dumper, this_ext,
					      &variables_dump_extension, dctx);
	}
	return dctx;
}

bool ext_variables_code_dump(const struct sieve_extension *ext,
			     const struct sieve_dumptime_env *denv,
			     sieve_size_t *address)
{
	struct ext_variables_dump_context *dctx;
	struct sieve_variable_scope *local_scope;

	local_scope = sieve_variable_scope_binary_dump(ext->svinst, ext, NULL,
						       denv, address);

	dctx = ext_variables_dump_get_context(ext, denv);
	dctx->local_scope = local_scope;

	return TRUE;
}

/*
 * Scope registry
 */

void sieve_ext_variables_dump_set_scope(const struct sieve_extension *var_ext,
					const struct sieve_dumptime_env *denv,
					const struct sieve_extension *ext,
					struct sieve_variable_scope *scope)
{
	struct ext_variables_dump_context *dctx =
		ext_variables_dump_get_context(var_ext, denv);

	if (ext->id < 0)
		return;

	array_idx_set(&dctx->ext_scopes, (unsigned int) ext->id, &scope);
}

/*
 * Variable identifier dump
 */

const char *
ext_variables_dump_get_identifier(const struct sieve_extension *var_ext,
				  const struct sieve_dumptime_env *denv,
				  const struct sieve_extension *ext,
				  unsigned int index)
{
	struct ext_variables_dump_context *dctx =
		ext_variables_dump_get_context(var_ext, denv);
	struct sieve_variable_scope *scope;
	struct sieve_variable *var;

	if (ext == NULL)
		scope = dctx->local_scope;
	else {
		struct sieve_variable_scope *const *ext_scope;

		if (ext->id < 0 ||
		    ext->id >= (int) array_count(&dctx->ext_scopes))
			return NULL;

		ext_scope = array_idx(&dctx->ext_scopes,
				      (unsigned int)ext->id);
		scope = *ext_scope;
	}

	if (scope == NULL)
		return NULL;

	var = sieve_variable_scope_get_indexed(scope, index);
	return var->identifier;
}
