#ifndef SIEVE_EXT_VARIABLES_H
#define SIEVE_EXT_VARIABLES_H

#include "lib.h"

#include "sieve-common.h"
#include "sieve-extensions.h"
#include "sieve-objects.h"
#include "sieve-code.h"

/* Public interface for other extensions to use
 */

/*
 * Limits
 */

unsigned int
sieve_variables_get_max_scope_count(const struct sieve_extension *var_ext);
size_t sieve_variables_get_max_value_size(
	const struct sieve_extension *var_ext);

/*
 * Variable extension
 */

/* FIXME: this is not suitable for future plugin support */

extern const struct sieve_extension_def variables_extension;

static inline int
sieve_ext_variables_get_extension(struct sieve_instance *svinst,
				  const struct sieve_extension **ext_r)
{
	return sieve_extension_register(svinst, &variables_extension, FALSE,
					ext_r);
}

/*
 * Variable name
 */

struct sieve_variable_name {
	string_t *identifier;
	int num_variable;
};

ARRAY_DEFINE_TYPE(sieve_variable_name, struct sieve_variable_name);

bool sieve_variable_identifier_is_valid(const char *identifier);

/*
 * Variable scope
 */

struct sieve_variable {
	const char *identifier;
	unsigned int index;

	const struct sieve_extension *ext;
	void *context;
};

struct sieve_variable_scope;

struct sieve_variable_scope *
sieve_variable_scope_create(struct sieve_instance *svinst,
			    const struct sieve_extension *var_ext,
			    const struct sieve_extension *ext);
void sieve_variable_scope_ref(struct sieve_variable_scope *scope);
void sieve_variable_scope_unref(struct sieve_variable_scope **scope);
pool_t sieve_variable_scope_pool(struct sieve_variable_scope *scope);

struct sieve_variable *
sieve_variable_scope_declare(struct sieve_variable_scope *scope,
			     const char *identifier);
struct sieve_variable *
sieve_variable_scope_import(struct sieve_variable_scope *scope,
			    struct sieve_variable *var);
struct sieve_variable *
sieve_variable_scope_get_variable(struct sieve_variable_scope *scope,
				  const char *identifier);
struct sieve_variable *
sieve_variable_scope_get_indexed(struct sieve_variable_scope *scope,
				 unsigned int index);

/* Binary */

struct sieve_variable_scope_binary *
sieve_variable_scope_binary_create(struct sieve_variable_scope *scope);

void sieve_variable_scope_binary_ref(
	struct sieve_variable_scope_binary *scpbin);
void sieve_variable_scope_binary_unref(
	struct sieve_variable_scope_binary **scpbin);

struct sieve_variable_scope *
sieve_variable_scope_binary_dump(struct sieve_instance *svinst,
				 const struct sieve_extension *var_ext,
				 const struct sieve_extension *ext,
				 const struct sieve_dumptime_env *denv,
				 sieve_size_t *address);
struct sieve_variable_scope_binary *
sieve_variable_scope_binary_read(struct sieve_instance *svinst,
				 const struct sieve_extension *var_ext,
				 const struct sieve_extension *ext,
				 struct sieve_binary_block *sblock,
				 sieve_size_t *address);

struct sieve_variable_scope *
sieve_variable_scope_binary_get(struct sieve_variable_scope_binary *scpbin);
unsigned int
sieve_variable_scope_binary_get_count(
	struct sieve_variable_scope_binary *scpbin);

/*
 * Variable namespaces
 */

struct sieve_variables_namespace;

struct sieve_variables_namespace_def {
	struct sieve_object_def obj_def;

	bool (*validate)(struct sieve_validator *valdtr,
			 const struct sieve_variables_namespace *nspc,
			 struct sieve_ast_argument *arg,
			 struct sieve_command *cmd,
			 ARRAY_TYPE(sieve_variable_name) *var_name,
			 void **var_data, bool assignment);
	bool (*generate)(const struct sieve_codegen_env *cgenv,
			 const struct sieve_variables_namespace *nspc,
			 struct sieve_ast_argument *arg,
			 struct sieve_command *cmd, void *var_data);

	bool (*dump_variable)(const struct sieve_dumptime_env *denv,
			      const struct sieve_variables_namespace *nspc,
			      const struct sieve_operand *oprnd,
			      sieve_size_t *address);
	int (*read_variable)(const struct sieve_runtime_env *renv,
			     const struct sieve_variables_namespace *nspc,
			     const struct sieve_operand *oprnd,
			     sieve_size_t *address, string_t **str);
};

#define SIEVE_VARIABLES_DEFINE_NAMESPACE(OP) SIEVE_EXT_DEFINE_OBJECT(OP)
#define SIEVE_VARIABLES_DEFINE_NAMESPACES(OPS) SIEVE_EXT_DEFINE_OBJECTS(OPS)

struct sieve_variables_namespace {
	struct sieve_object object;

	const struct sieve_variables_namespace_def *def;
};

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);

extern const struct sieve_operand_class sieve_variables_namespace_operand_class;

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);

/* Iteration over all declared variables */

struct sieve_variable_scope_iter;

struct sieve_variable_scope_iter *
sieve_variable_scope_iterate_init(struct sieve_variable_scope *scope);
bool sieve_variable_scope_iterate(struct sieve_variable_scope_iter *iter,
				  struct sieve_variable **var_r);
void sieve_variable_scope_iterate_deinit(
	struct sieve_variable_scope_iter **iter);

/* Statistics */

unsigned int
sieve_variable_scope_declarations(struct sieve_variable_scope *scope);
unsigned int sieve_variable_scope_size(struct sieve_variable_scope *scope);

/* Get all native variables */

struct sieve_variable *const *
sieve_variable_scope_get_variables(struct sieve_variable_scope *scope,
				   unsigned int *size_r);

/*
 * Variable storage
 */

struct sieve_variable_storage;

struct sieve_variable_storage *
sieve_variable_storage_create(const struct sieve_extension *var_ext,
			      pool_t pool,
			      struct sieve_variable_scope_binary *scpbin);
bool sieve_variable_get(struct sieve_variable_storage *storage,
			unsigned int index, string_t **value);
bool sieve_variable_get_modifiable(struct sieve_variable_storage *storage,
				   unsigned int index, string_t **value);
bool sieve_variable_assign(struct sieve_variable_storage *storage,
			   unsigned int index, const string_t *value);
bool sieve_variable_assign_cstr(struct sieve_variable_storage *storage,
				unsigned int index, const char *value);
bool sieve_variable_get_identifier(struct sieve_variable_storage *storage,
				   unsigned int index, const char **identifier);
const char *sieve_variable_get_varid(struct sieve_variable_storage *storage,
				     unsigned int index);

/*
 * Variables access
 */

bool sieve_ext_variables_is_active(const struct sieve_extension *var_ext,
				   struct sieve_validator *valdtr);

struct sieve_variable_scope *
sieve_ext_variables_get_local_scope(const struct sieve_extension *var_ext,
				    struct sieve_validator *valdtr);

/* Runtime */

static inline const char *
sieve_ext_variables_get_varid(const struct sieve_extension *ext,
			      unsigned int index)
{
	if (ext == NULL)
		return t_strdup_printf("%ld", (long)index);

	return t_strdup_printf("%s:%ld", sieve_extension_name(ext),
			       (long)index);
}

struct sieve_variable_storage *
sieve_ext_variables_runtime_get_storage(const struct sieve_extension *var_ext,
					const struct sieve_runtime_env *renv,
					const struct sieve_extension *ext);
void sieve_ext_variables_runtime_set_storage(
	const struct sieve_extension *var_ext,
	const struct sieve_runtime_env *renv, const struct sieve_extension *ext,
	struct sieve_variable_storage *storage);

const char *
sieve_ext_variables_runtime_get_identifier(
	const struct sieve_extension *var_ext,
	const struct sieve_runtime_env *renv, const struct sieve_extension *ext,
	unsigned int index);

/*
 * Variable arguments
 */

bool sieve_variable_argument_activate(const struct sieve_extension *var_ext,
				      const struct sieve_extension *this_ext,
				      struct sieve_validator *valdtr,
				      struct sieve_command *cmd,
				      struct sieve_ast_argument *arg,
				      bool assignment);

/*
 * Variable operands
 */

extern const struct sieve_operand_def variable_operand;

void sieve_variables_opr_variable_emit(struct sieve_binary_block *sblock,
				       const struct sieve_extension *var_ext,
				       struct sieve_variable *var);
void sieve_variables_opr_match_value_emit(struct sieve_binary_block *sblock,
					  const struct sieve_extension *var_ext,
					  unsigned int index);

int sieve_variable_operand_read_data(const struct sieve_runtime_env *renv,
				     struct sieve_operand *operand,
				     sieve_size_t *address,
				     const char *field_name,
				     struct sieve_variable_storage **storage_r,
				     unsigned int *var_index_r);
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);

static inline bool
sieve_operand_is_variable(const struct sieve_operand *operand)
{
	return (operand != NULL && operand->def != NULL &&
		operand->def == &variable_operand);
}

/*
 * Modifiers
 */

/* Definition */

struct sieve_variables_modifier;

struct sieve_variables_modifier_def {
	struct sieve_object_def obj_def;

	unsigned int precedence;

	bool (*modify)(const struct sieve_variables_modifier *modf,
		       string_t *in, string_t **result);
};

struct sieve_variables_modifier {
	struct sieve_object object;
	const struct sieve_extension *var_ext;

	const struct sieve_variables_modifier_def *def;
};

#define SIEVE_VARIABLES_DEFINE_MODIFIER(OP) SIEVE_EXT_DEFINE_OBJECT(OP)
#define SIEVE_VARIABLES_DEFINE_MODIFIERS(OPS) SIEVE_EXT_DEFINE_OBJECTS(OPS)

#define sieve_variables_modifier_name(smodf) \
	((smodf)->object.def->identifier)

ARRAY_DEFINE_TYPE(sieve_variables_modifier,
	struct sieve_variables_modifier);

/* Registry */

void sieve_variables_modifier_register(
	const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
	const struct sieve_extension *ext,
	const struct sieve_variables_modifier_def *smodf);

/* Tagged argument */

void sieve_variables_modifiers_link_tag(
	struct sieve_validator *valdtr, const struct sieve_extension *var_ext,
	struct sieve_command_registration *cmd_reg);

bool sieve_variables_modifiers_validate(
	struct sieve_validator *valdtr, struct sieve_command *cmd,
	ARRAY_TYPE(sieve_variables_modifier) *modifiers);

bool sieve_variables_modifiers_generate(
	const struct sieve_codegen_env *cgenv,
	ARRAY_TYPE(sieve_variables_modifier) *modifiers);

/* Coding */

extern const struct sieve_operand_class
	sieve_variables_modifier_operand_class;

bool sieve_variables_modifiers_code_dump(const struct sieve_dumptime_env *denv,
					 sieve_size_t *address);
int sieve_variables_modifiers_code_read(
	const struct sieve_runtime_env *renv,
	const struct sieve_extension *var_ext, sieve_size_t *address,
	ARRAY_TYPE(sieve_variables_modifier) *modifiers);

/* Application */

int sieve_variables_modifiers_apply(
	const struct sieve_runtime_env *renv,
	const struct sieve_extension *var_ext,
	ARRAY_TYPE(sieve_variables_modifier) *modifiers, string_t **value);

/*
 * Code dumping
 */

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);

#endif
