#ifndef VAR_EXPAND_NEW_H
#define VAR_EXPAND_NEW_H

/** Variable expansion programs **

  A variable expansion program is a set of filters delineated with %{},
  or a literal-only program which expands to the string it contains.
  See https://doc.dovecot.org/latest/core/settings/syntax.html#variable-expansion.

  The normal usage is to call t_var_expand() to get rendition of a program from
  data stack memory, or var_expand() if you want to provide the string yourself.
  The string is appended to, or truncated to where it was on starting on failure.

  If you need to expand the program multiple times, you should use
  var_expand_program_create() to create a reusable program chain and then call
  var_expand_program_execute() to expand it. Since parameters are given to execute
  you can get the same program executed with different parameters, so you don't
  have to rebuild the program if your variables are changed.

  The function var_expand_program_create() parses a string that can contain
  one or more programs, and chains them together. This is usually what is wanted.

  There are some cases though when you need to deal with programs one by one,
  and this is handled by 'var-expand-split.h', and there are a few functions here
  that do not seem to make much sense alone:

  The var_expand_program_execute_one() and var_expand_program_has_variable() are
  such ones. The execute_one() only makes sense to use with individual programs
  that can be extracted with var_expand_program_template() or
  var_expand_program_split(), which are in their own header as they have
  array.h dependency.

  The var_expand_program_has_variable() can be used for generic purposes too
  by leaving the first_program_only as FALSE.
*/

/* Used for getting either prefix:key values, or dynamic values for keys
   in value tables.

   Gets key and context, needs to return -1 on error (with error_r set)
   or 0 on success. value_r *must* be non-null on success.

   Prefix is removed before calling the function.
*/
typedef int value_provider_func_t(const char *key, const char **value_r,
				  void *context, const char **error_r);
/* Used for escaping values. On success sets output_r and returns 0.
   On failure sets error_r and returns -1. */
typedef int var_expand_escape_func_t(const char *input, const char **output_r,
				     void *context, const char **error_r);

struct var_expand_parser_state;
struct var_expand_program;

#define VAR_EXPAND_TABLE_END { .key = NULL }
#define VAR_EXPAND_CONTEXTS_END (void*)var_expand_contexts_end

struct var_expand_table {
	/* Key name, as in %{key} */
	const char *key;
	/* Value to expand into */
	const char *value;
	/* Or function that provides the value */
	value_provider_func_t *func;
};

struct var_expand_provider {
	/* key as in %{key:name} */
	const char *key;
	/* function to call to get value */
	value_provider_func_t *func;
};

extern const void *const var_expand_contexts_end;

struct var_expand_params {
	/* Variables to use, must end with VAR_EXPAND_TABLE_END,
	   asserts that tables_arr is non-NULL. */
	const struct var_expand_table *table;
	/* Providers to use, must end with VAR_EXPAND_TABLE_END,
	   asserts that providers_arr is non-NULL. */
	const struct var_expand_provider *providers;
	/* Multiple var expand tables, must be NULL terminated */
	const struct var_expand_table *const *tables_arr;
	/* Multiple var expand providers, must be NULL terminated */
	const struct var_expand_provider *const *providers_arr;
	/* Function that gets called to escape values */
	var_expand_escape_func_t *escape_func;
	/* Context for escape function */
	void *escape_context;
	/* Contexts for table functions and providers, can be
	   set to NULL if no multiple contexts are needed, then context
	   is defaulted to.

	   Asserts that contexts ends with VAR_EXPAND_CONTEXTS_END.
	*/
	void *const *contexts;
	/* Context for table functions and providers. */
	void *context;
	/* Event for %{event:} expansion, can be NULL. Global event
	   will be attempted if this is NULL. */
	struct event *event;
};

/* Creates a new expansion program for reusing */
int var_expand_program_create(const char *str, struct var_expand_program **program_r,
			      const char **error_r);
/* Lists all seen variables in a program */
const char *const *var_expand_program_variables(const struct var_expand_program *program);
/* Checks if the program has a variable, if first_program_only is set, checks the
  first program only, otherwise it checks the variables using var_expand_program_variables().

  The check is done to all filters and parameters on the first program, so it can detect
  if the variable is used anywhere in the program.
*/
bool var_expand_program_has_variable(const struct var_expand_program *program,
				     const char *variable, bool first_program_only);
/* Dumps the program into a dest for debugging */
void var_expand_program_dump(const struct var_expand_program *program, string_t *dest);
/* Executes the program with given params. Params can be left NULL, in which case
   empty parameters are used. */
int var_expand_program_execute(string_t *dest, const struct var_expand_program *program,
			       const struct var_expand_params *params,
			       const char **error_r) ATTR_NULL(3);
/* Execute the first program only */
int var_expand_program_execute_one(string_t *dest, const struct var_expand_program *program,
				   const struct var_expand_params *params,
				   const char **error_r);
/* Free up program */
void var_expand_program_free(struct var_expand_program **_program);

/* Creates a new program, executes it and frees it. Params can be left NULL, in which
   case empty parameters are used. */
int var_expand(string_t *dest, const char *str, const struct var_expand_params *params,
	       const char **error_r) ATTR_NULL(3);

/* Wrapper for var_expand(), places the result into result_r. */
int t_var_expand(const char *str, const struct var_expand_params *params,
		 const char **result_r, const char **error_r);

/* Merge two tables together, keys in table a will be overwritten with keys
 * from table b in collision. */
struct var_expand_table *
var_expand_merge_tables(pool_t pool, const struct var_expand_table *a,
			const struct var_expand_table *b);

/* Returns true if provider is a built-in provider */
bool var_expand_provider_is_builtin(const char *prefix);

/* Provides size of a table */
static inline size_t ATTR_PURE
var_expand_table_size(const struct var_expand_table *table)
{
	size_t n = 0;
	while (table != NULL && table[n].key != NULL)
		 n++;
	return n;
}

/* Get table entry by name. Returns NULL if not found. */
static inline struct var_expand_table *
var_expand_table_get(struct var_expand_table *table, const char *key)
{
	for (size_t i = 0; table[i].key != NULL; i++) {
		if (strcmp(table[i].key, key) == 0) {
			return &(table[i]);
		}
	}
	return NULL;
}

/* Set table variable to value. Asserts that key is found. */
static inline void var_expand_table_set_value(struct var_expand_table *table,
					      const char *key, const char *value,
					      const char *file, unsigned int line)
{
	struct var_expand_table *entry = var_expand_table_get(table, key);
	if (entry != NULL) {
		i_assert(entry->func == NULL);
		entry->value = value;
	} else
		i_panic("%s:%u No key '%s' in table", file, line, key);
}
#define var_expand_table_set_value(table, key, value) \
	var_expand_table_set_value((table), (key), (value), __FILE__, __LINE__);

/* Set table variable function. Asserts that key is found. */
static inline void var_expand_table_set_func(struct var_expand_table *table,
					     const char *key,
					     value_provider_func_t *func,
					     const char *file, unsigned int line)
{
	struct var_expand_table *entry = var_expand_table_get(table, key);
	if (entry != NULL) {
		i_assert(entry->value == NULL);
		entry->func = func;
	} else
		i_panic("%s:%u No key '%s' in table", file, line, key);
}
#define var_expand_table_set_func(table, key, func) \
	var_expand_table_set_func((table), (key), (func), __FILE__, __LINE__);

/* Set key_b variable to key_a. Copies func and value.
   Asserts that both are found. */
static inline void var_expand_table_copy(struct var_expand_table *table,
					 const char *key_b, const char *key_a)
{
	struct var_expand_table *entry_a = var_expand_table_get(table, key_a);
	struct var_expand_table *entry_b = var_expand_table_get(table, key_b);

	i_assert(entry_a != NULL && entry_b != NULL);
	entry_b->value = entry_a->value;
	entry_b->func = entry_a->func;
}

/* Export variable expand program to a portable string.

   Output format is a list of programs. Each program is catenated after
   the previous program.
   If there is only literal: \x01 <literal>
   <literal>: tab-escaped string \r
   Otherwise:
   <program>: \x02 <function> \x01 <list-of-parameters> \t <list-of-variables> \t
   <function>: string
   <list-of-parameters>: <parameter> \x01 <parameter> \x01 ..
      Note that last parameter has no \x01 at the end.
   <parameter>: <key> \x01 <type> <value>
   <key>: string
   <type>: s = string, i = intmax, v = variable
   <value>: <encoded-number> | tab-escaped string \r
   <encoded-number>:
     The number is expressed in 7-bit bytes and 8th bit indicates the
     presence of next byte. The number is in little-endian ordering.
   <list-of-variables>: <variable-name> \x01 <variable-name> \x01 ..
     Note that last variable has no \x01 at the end.
*/

const char *var_expand_program_export(const struct var_expand_program *program);
void var_expand_program_export_append(string_t *dest,
				      const struct var_expand_program *program);

/* Reconstruct var_expand program */
const char *var_expand_program_to_string_one(const struct var_expand_program *program);
const char *var_expand_program_to_string(const struct var_expand_program *program);
void var_expand_program_to_string_append_one(string_t *dest,
					 const struct var_expand_program *program);
void var_expand_program_to_string_append(string_t *dest,
					 const struct var_expand_program *program);

/* Imports a variable expansion program exported by var_expand_program_export(). */

int var_expand_program_import(const char *data,
			      struct var_expand_program **program_r,
			      const char **error_r);
int var_expand_program_import_sized(const char *data, size_t size,
				    struct var_expand_program **program_r,
				    const char **error_r);

void var_expand_crypt_load(void);

#endif
