#ifndef SIEVE_AST_H
#define SIEVE_AST_H

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

#include "sieve-common.h"
#include "sieve-error.h"

/*
	Abstract Syntax Tree (AST) structure:

	sieve_ast (root)
	[*command]
	 |
	 +-- command:
	 |   ....
	 +-- command:
	 |	 [identifier *argument                      *test *command]
	 |                +-- argument:                 |     \--> as from root
	 |                |   ....                      |
 	 |                +-- argument:                 V (continued below)
	 |                |   [number | tag | *string]
	 |                .
	 .

	 *test
	 +-- test:
	 |   ....
	 +-- test:
	 |   [identifier *argument                     *test]
	 |               +-- argument:                 \-->  as from the top
	 .               |   ....                              of this tree
	                 +-- argument:
	                 |   [number | tag | *string]
	                 .

	 Tests and commands are defined using the same structure: sieve_ast_node.
	 However, arguments and string-lists are described using sieve_ast_argument.
*/

/* IMPORTANT NOTICE: Do not decorate the AST with objects other than those
 * allocated on the ast's pool or static const objects. Otherwise it is possible
 * that pointers in the tree become dangling which is highly undesirable.
 */

/*
 * Forward declarations
 */

struct sieve_ast_list;
struct sieve_ast_arg_list;

/*
 * Types
 */

enum sieve_ast_argument_type {
	SAAT_NONE,
	SAAT_NUMBER,
	SAAT_STRING,
	SAAT_STRING_LIST,
	SAAT_TAG,
};

enum sieve_ast_type {
	SAT_NONE,
	SAT_ROOT,
	SAT_COMMAND,
	SAT_TEST,
};

/*
 * AST Nodes
 */

/* Argument node */

struct sieve_ast_argument {
	enum sieve_ast_argument_type type;

	/* Back reference to the AST object */
	struct sieve_ast *ast;

	/* List related */
	struct sieve_ast_arg_list *list;
	struct sieve_ast_argument *next;
	struct sieve_ast_argument *prev;

	/* Parser-assigned data */

	union {
		string_t *str;
		struct sieve_ast_arg_list *strlist;
		const char *tag;
		unsigned int number;
	} _value;

	unsigned int source_line;

	/* Assigned during validation */

	/* Argument associated with this ast element  */
	struct sieve_argument *argument;

	/* Parameters to this (tag) argument */
	struct sieve_ast_argument *parameters;
};

struct sieve_ast_node {
	enum sieve_ast_type type;

	/* Back reference to the AST object */
	struct sieve_ast *ast;

	/* Back reference to this node's parent */
	struct sieve_ast_node *parent;

	/* Linked list references */
	struct sieve_ast_list *list;
	struct sieve_ast_node *next;
	struct sieve_ast_node *prev;

	/* Commands (NULL if not allocated) */
	bool block;
	struct sieve_ast_list *commands;

	/* Tests (NULL if not allocated)*/
	bool test_list;
	struct sieve_ast_list *tests;

	/* Arguments (NULL if not allocated) */
	struct sieve_ast_arg_list *arguments;

	/* Identifier of command or test */
	const char *identifier;

	/* The location in the file where this command was started */
	unsigned int source_line;

	/* Assigned during validation */

	/* Context */
	struct sieve_command *command;
};

/*
 * AST node lists
 */

struct sieve_ast_list {
	struct sieve_ast_node *head;
	struct sieve_ast_node *tail;
	unsigned int len;
};

struct sieve_ast_arg_list {
	struct sieve_ast_argument *head;
	struct sieve_ast_argument *tail;
	unsigned int len;
};

/*
 * AST object
 */

struct sieve_ast;

struct sieve_ast *sieve_ast_create(struct sieve_script *script);
void sieve_ast_ref(struct sieve_ast *ast);
void sieve_ast_unref(struct sieve_ast **ast);

struct sieve_ast_node *sieve_ast_root(struct sieve_ast *ast);
pool_t sieve_ast_pool(struct sieve_ast *ast);
struct sieve_script *sieve_ast_script(struct sieve_ast *ast);

/* Extension support */

struct sieve_ast_extension {
	const struct sieve_extension_def *ext;

	void (*free)(const struct sieve_extension *ext, struct sieve_ast *ast,
		void *context);
};

void sieve_ast_extension_link
	(struct sieve_ast *ast, const struct sieve_extension *ext,
		bool required);
const struct sieve_extension * const *sieve_ast_extensions_get
	(struct sieve_ast *ast, unsigned int *count_r);

void sieve_ast_extension_register
	(struct sieve_ast *ast, const struct sieve_extension *ext,
		const struct sieve_ast_extension *ast_ext, void *context);
void sieve_ast_extension_set_context
	(struct sieve_ast *ast, const struct sieve_extension *ext, void *context);
void *sieve_ast_extension_get_context
	(struct sieve_ast *ast, const struct sieve_extension *ext);

bool sieve_ast_extension_is_required
	(struct sieve_ast *ast, const struct sieve_extension *ext);

/*
 * AST node manipulation
 */

/* Command nodes */

struct sieve_ast_node *sieve_ast_test_create
	(struct sieve_ast_node *parent, const char *identifier,
		unsigned int source_line);
struct sieve_ast_node *sieve_ast_command_create
	(struct sieve_ast_node *parent, const char *identifier,
		unsigned int source_line);

struct sieve_ast_node *sieve_ast_node_detach
	(struct sieve_ast_node *first);

const char *sieve_ast_type_name(enum sieve_ast_type ast_type);

/* Argument nodes */

struct sieve_ast_argument *sieve_ast_argument_create
	(struct sieve_ast *ast, unsigned int source_line);

struct sieve_ast_arg_list *sieve_ast_arg_list_create(pool_t pool);
bool sieve_ast_arg_list_add
	(struct sieve_ast_arg_list *list, struct sieve_ast_argument *argument);
bool sieve_ast_arg_list_insert
	(struct sieve_ast_arg_list *list, struct sieve_ast_argument *before,
		struct sieve_ast_argument *argument);
void sieve_ast_arg_list_substitute
	(struct sieve_ast_arg_list *list, struct sieve_ast_argument *argument,
		struct sieve_ast_argument *replacement);

struct sieve_ast_argument *sieve_ast_argument_string_create_raw
	(struct sieve_ast *ast, string_t *str, unsigned int source_line);
struct sieve_ast_argument *sieve_ast_argument_string_create
	(struct sieve_ast_node *node, const string_t *str, unsigned int source_line);
struct sieve_ast_argument *sieve_ast_argument_cstring_create
	(struct sieve_ast_node *node, const char *str, unsigned int source_line);

struct sieve_ast_argument *sieve_ast_argument_tag_create
	(struct sieve_ast_node *node, const char *tag, unsigned int source_line);

struct sieve_ast_argument *sieve_ast_argument_number_create
	(struct sieve_ast_node *node, unsigned int number, unsigned int source_line);

void sieve_ast_argument_string_set
	(struct sieve_ast_argument *argument, string_t *newstr);
void sieve_ast_argument_string_setc
	(struct sieve_ast_argument *argument, const char *newstr);

void sieve_ast_argument_number_set
	(struct sieve_ast_argument *argument, unsigned int newnum);
void sieve_ast_argument_number_substitute
	(struct sieve_ast_argument *argument, unsigned int number);

struct sieve_ast_argument *sieve_ast_argument_tag_insert
(struct sieve_ast_argument *before, const char *tag, unsigned int source_line);

struct sieve_ast_argument *sieve_ast_argument_stringlist_create
	(struct sieve_ast_node *node, unsigned int source_line);
struct sieve_ast_argument *sieve_ast_argument_stringlist_substitute
	(struct sieve_ast_node *node, struct sieve_ast_argument *arg);

struct sieve_ast_argument *sieve_ast_arguments_detach
	(struct sieve_ast_argument *first, unsigned int count);
bool sieve_ast_argument_attach
	(struct sieve_ast_node *node, struct sieve_ast_argument *argument);

const char *sieve_ast_argument_type_name(enum sieve_ast_argument_type arg_type);
#define sieve_ast_argument_name(argument) \
	sieve_ast_argument_type_name((argument)->type)

bool sieve_ast_stringlist_add
	(struct sieve_ast_argument *list, const string_t *str,
		unsigned int source_line);
bool sieve_ast_stringlist_add_strc
	(struct sieve_ast_argument *list, const char *str,
		unsigned int source_line);

/*
 * Utility
 */

int sieve_ast_stringlist_map
	(struct sieve_ast_argument **listitem, void *context,
		int (*map_function)(void *context, struct sieve_ast_argument *arg));
struct sieve_ast_argument *sieve_ast_stringlist_join
	(struct sieve_ast_argument *list, struct sieve_ast_argument *items);

/*
 * AST access macros
 */

/* Generic list access macros */
#define __AST_LIST_FIRST(list) \
	((list) == NULL ? NULL : (list)->head)
#define __AST_LIST_LAST(list) \
	((list) == NULL ? NULL : (list)->tail)
#define __AST_LIST_COUNT(list) \
	((list) == NULL || (list)->head == NULL ? 0 : (list)->len)
#define __AST_LIST_NEXT(item) ((item)->next)
#define __AST_LIST_PREV(item) ((item)->prev)

#define __AST_NODE_LIST_FIRST(node, list) __AST_LIST_FIRST((node)->list)
#define __AST_NODE_LIST_LAST(node, list) __AST_LIST_LAST((node)->list)
#define __AST_NODE_LIST_COUNT(node, list) __AST_LIST_COUNT((node)->list)

/* AST macros */

/* AST node macros */
#define sieve_ast_node_pool(node) (sieve_ast_pool((node)->ast))
#define sieve_ast_node_parent(node) ((node)->parent)
#define sieve_ast_node_prev(node) __AST_LIST_PREV(node)
#define sieve_ast_node_next(node) __AST_LIST_NEXT(node)
#define sieve_ast_node_type(node) ((node) == NULL ? SAT_NONE : (node)->type)
#define sieve_ast_node_line(node) ((node) == NULL ? 0 : (node)->source_line)

/* AST command node macros */
#define sieve_ast_command_first(node) __AST_NODE_LIST_FIRST(node, commands)
#define sieve_ast_command_count(node) __AST_NODE_LIST_COUNT(node, commands)
#define sieve_ast_command_prev(command) __AST_LIST_PREV(command)
#define sieve_ast_command_next(command) __AST_LIST_NEXT(command)

/* Compare the identifier of the previous command */
#define sieve_ast_prev_cmd_is(cmd, id) \
	( (cmd)->prev == NULL ? FALSE : \
		strncasecmp((cmd)->prev->identifier, id, sizeof(id)-1) == 0 )

/* AST test macros */
#define sieve_ast_test_count(node) __AST_NODE_LIST_COUNT(node, tests)
#define sieve_ast_test_first(node) __AST_NODE_LIST_FIRST(node, tests)
#define sieve_ast_test_next(test) __AST_LIST_NEXT(test)

/* AST argument macros */
#define sieve_ast_argument_pool(node) (sieve_ast_pool((node)->ast))
#define sieve_ast_argument_first(node) __AST_NODE_LIST_FIRST(node, arguments)
#define sieve_ast_argument_last(node) __AST_NODE_LIST_LAST(node, arguments)
#define sieve_ast_argument_count(node) __AST_NODE_LIST_COUNT(node, arguments)
#define sieve_ast_argument_prev(argument) __AST_LIST_PREV(argument)
#define sieve_ast_argument_next(argument) __AST_LIST_NEXT(argument)
#define sieve_ast_argument_type(argument) (argument)->type
#define sieve_ast_argument_line(argument) (argument)->source_line

#define sieve_ast_argument_str(argument) ((argument)->_value.str)
#define sieve_ast_argument_strc(argument) (str_c((argument)->_value.str))
#define sieve_ast_argument_tag(argument) ((argument)->_value.tag)
#define sieve_ast_argument_number(argument) ((argument)->_value.number)

/* AST string list macros */
// @UNSAFE: should check whether we are actually accessing a string list
#define sieve_ast_strlist_first(list) \
	__AST_NODE_LIST_FIRST(list, _value.strlist)
#define sieve_ast_strlist_last(list) \
	__AST_NODE_LIST_LAST(list, _value.strlist)
#define sieve_ast_strlist_count(list) \
	__AST_NODE_LIST_COUNT(list, _value.strlist)
#define sieve_ast_strlist_next(str) __AST_LIST_NEXT(str)
#define sieve_ast_strlist_prev(str) __AST_LIST_PREV(str)
#define sieve_ast_strlist_str(str) sieve_ast_argument_str(str)
#define sieve_ast_strlist_strc(str) sieve_ast_argument_strc(str)

/*
 * Debug
 */

void sieve_ast_unparse(struct sieve_ast *ast);

#endif
