/*
 * fy-doc.h - YAML document internal header file
 *
 * Copyright (c) 2019 Pantelis Antoniou <pantelis.antoniou@konsulko.com>
 *
 * SPDX-License-Identifier: MIT
 */
#ifndef FY_DOC_H
#define FY_DOC_H

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdarg.h>

#include <libfyaml.h>

#include "fy-ctype.h"
#include "fy-utf8.h"
#include "fy-list.h"
#include "fy-typelist.h"
#include "fy-types.h"
#include "fy-diag.h"
#include "fy-dump.h"
#include "fy-docstate.h"
#include "fy-accel.h"
#include "fy-walk.h"
#include "fy-path.h"

struct fy_eventp;

/* TODO vary according to platfom */
static inline int fy_depth_limit(void)
{
	return FYPCF_GUARANTEED_MINIMUM_DEPTH_LIMIT;
}

FY_TYPE_FWD_DECL_LIST(document);

struct fy_node;

struct fy_node_pair {
	struct list_head node;
	struct fy_node *key;
	struct fy_node *value;
	struct fy_document *fyd;
	struct fy_node *parent;
};
FY_TYPE_FWD_DECL_LIST(node_pair);
FY_TYPE_DECL_LIST(node_pair);

FY_TYPE_FWD_DECL_LIST(node);
struct fy_node {
	struct list_head node;
	struct fy_token *tag;
	enum fy_node_style style;
	struct fy_node *parent;
	struct fy_document *fyd;
	unsigned int marks;
	unsigned int type : 2;	/* enum fy_node_type: 2 bits are enough for 3 types */
	bool has_meta : 1;
	bool attached : 1;		/* when it's attached somewhere */
	bool synthetic : 1;		/* node has been modified programmaticaly */
	bool key_root : 1;		/* node is the root of key fy_node_get_parent() will return NULL */
	void *meta;
	struct fy_accel *xl;		/* mapping access accelerator */
	struct fy_path_expr_node_data *pxnd;
	union {
		struct fy_token *scalar;
		struct fy_node_list sequence;
		struct fy_node_pair_list mapping;
	};
	union {
		struct fy_token *sequence_start;
		struct fy_token *mapping_start;
	};
	union {
		struct fy_token *sequence_end;
		struct fy_token *mapping_end;
	};
};
FY_TYPE_DECL_LIST(node);

struct fy_node *fy_node_alloc(struct fy_document *fyd, enum fy_node_type type);
struct fy_node_pair *fy_node_pair_alloc(struct fy_document *fyd);
int fy_node_pair_free(struct fy_node_pair *fynp);

void fy_node_detach_and_free(struct fy_node *fyn);
void fy_node_pair_detach_and_free(struct fy_node_pair *fynp);

struct fy_anchor {
	struct list_head node;
	struct fy_node *fyn;
	struct fy_token *anchor;
	bool multiple : 1;
};
FY_TYPE_FWD_DECL_LIST(anchor);
FY_TYPE_DECL_LIST(anchor);

struct fy_document {
	struct list_head node;
	struct fy_anchor_list anchors;
	struct fy_accel *axl;		/* name -> anchor access accelerator */
	struct fy_accel *naxl;		/* node -> anchor access accelerator */
	struct fy_document_state *fyds;
	struct fy_diag *diag;
	struct fy_parse_cfg parse_cfg;
	struct fy_node *root;
	bool parse_error : 1;

	struct fy_document *parent;
	struct fy_document_list children;

	fy_node_meta_clear_fn meta_clear_fn;
	void *meta_user;

	struct fy_path_expr_document_data *pxdd;
};
/* only the list declaration/methods */
FY_TYPE_DECL_LIST(document);

struct fy_document *fy_parse_document_create(struct fy_parser *fyp, struct fy_eventp *fyep);
void fy_document_purge_anchors(struct fy_document *fyd);

struct fy_node_mapping_sort_ctx {
	fy_node_mapping_sort_fn key_cmp;
	void *arg;
	struct fy_node_pair **fynpp;
	int count;
};

void fy_node_mapping_perform_sort(struct fy_node *fyn_map,
		fy_node_mapping_sort_fn key_cmp, void *arg,
		struct fy_node_pair **fynpp, int count);

void fy_node_mapping_fill_array(struct fy_node *fyn_map,
		struct fy_node_pair **fynpp, int count);

struct fy_node_pair **fy_node_mapping_sort_array(struct fy_node *fyn_map,
		fy_node_mapping_sort_fn key_cmp,
		void *arg, int *countp);

void fy_node_mapping_release_array(struct fy_node *fyn_map, struct fy_node_pair **fynpp);

struct fy_node_sequence_sort_ctx {
	fy_node_sequence_sort_fn cmp;
	void *arg;
	struct fy_node **fynp;
	int count;
};

struct fy_node_walk_ctx {
	unsigned int max_depth;
	unsigned int next_slot;
	unsigned int mark;
	struct fy_node *marked[0];
};

bool fy_node_is_empty(struct fy_node *fyn);

bool fy_check_ref_loop(struct fy_document *fyd, struct fy_node *fyn,
		       enum fy_node_walk_flags flags,
		       struct fy_node_walk_ctx *ctx,
		       unsigned int depth);

#define FYNWF_VISIT_MARKER	(FYNWF_MAX_USER_MARKER + 1)
#define FYNWF_REF_MARKER	(FYNWF_MAX_USER_MARKER + 2)
#define FYNWF_INSET_MARKER	(FYNWF_MAX_USER_MARKER + 3)

#define FYNWF_SYSTEM_MARKS	(FY_BIT(FYNWF_VISIT_MARKER) | \
				 FY_BIT(FYNWF_REF_MARKER) | \
				 FY_BIT(FYNWF_INSET_MARKER) )

void fy_node_clear_system_marks(struct fy_node *fyn);

bool fy_node_uses_single_input_only(struct fy_node *fyn, struct fy_input *fyi);
struct fy_input *fy_node_get_first_input(struct fy_node *fyn);
bool fy_node_is_synthetic(struct fy_node *fyn);
void fy_node_mark_synthetic(struct fy_node *fyn);
struct fy_input *fy_node_get_input(struct fy_node *fyn);
int fy_document_register_anchor(struct fy_document *fyd,
				struct fy_node *fyn, struct fy_token *anchor);
bool fy_node_mapping_key_is_duplicate(struct fy_node *fyn, struct fy_node *fyn_key);

struct fy_token *fy_node_non_synthesized_token(struct fy_node *fyn);
struct fy_token *fy_node_token(struct fy_node *fyn);

FILE *fy_document_get_error_fp(struct fy_document *fyd);
enum fy_parse_cfg_flags fy_document_get_cfg_flags(const struct fy_document *fyd);
bool fy_document_is_accelerated(struct fy_document *fyd);
bool fy_document_can_be_accelerated(struct fy_document *fyd);

/* TODO move to main include */
struct fy_node *fy_node_collection_iterate(struct fy_node *fyn, void **prevp);

/* indirect node */
FY_TYPE_FWD_DECL_LIST(ptr_node);
struct fy_ptr_node {
	struct list_head node;
	struct fy_node *fyn;
};
FY_TYPE_DECL_LIST(ptr_node);

struct fy_ptr_node *fy_ptr_node_create(struct fy_node *fyn);
void fy_ptr_node_destroy(struct fy_ptr_node *fypn);
void fy_ptr_node_list_free_all(struct fy_ptr_node_list *fypnl);
bool fy_ptr_node_list_contains(struct fy_ptr_node_list *fypnl, struct fy_node *fyn);
int fy_node_linearize_recursive(struct fy_ptr_node_list *fypnl, struct fy_node *fyn);
int fy_node_linearize(struct fy_ptr_node_list *fypnl, struct fy_node *fyn);
void fy_node_iterator_check(struct fy_node *fyn);


enum fy_document_iterator_state {
	FYDIS_WAITING_STREAM_START,
	FYDIS_WAITING_DOCUMENT_START,
	FYDIS_WAITING_BODY_START_OR_DOCUMENT_END,
	FYDIS_BODY,
	FYDIS_WAITING_DOCUMENT_END,
	FYDIS_WAITING_STREAM_END_OR_DOCUMENT_START,
	FYDIS_ENDED,
	FYDIS_ERROR,
};

struct fy_document_iterator_body_state {
	struct fy_node *fyn;	/* the collection node */
	bool processed_key : 1;	/* for mapping only */
	union {
		struct fy_node *fyni;		/* for sequence */
		struct fy_node_pair *fynp;	/* for mapping */
	};
};

struct fy_document_iterator {
	struct fy_document_iterator_cfg cfg;
	enum fy_document_iterator_state state;
	struct fy_document *fyd;
	struct fy_node *iterate_root;
	bool suppress_recycling_force : 1;
	bool suppress_recycling : 1;

	struct fy_eventp_list recycled_eventp;
	struct fy_token_list recycled_token;

	struct fy_eventp_list *recycled_eventp_list;	/* NULL when suppressing */
	struct fy_token_list *recycled_token_list;	/* NULL when suppressing */

	unsigned int stack_top;
	unsigned int stack_alloc;
	struct fy_document_iterator_body_state *stack;
	struct fy_document_iterator_body_state in_place[FYPCF_GUARANTEED_MINIMUM_DEPTH_LIMIT];

#define FYDIGF_GENERATED_SS	FY_BIT(0)
#define FYDIGF_GENERATED_DS	FY_BIT(1)
#define FYDIGF_GENERATED_DE	FY_BIT(2)
#define FYDIGF_GENERATED_SE	FY_BIT(3)
#define FYDIGF_GENERATED_BODY	FY_BIT(4)
#define FYDIGF_GENERATED_NULL	FY_BIT(5)
#define FYDIGF_ENDS_AFTER_BODY	FY_BIT(6)
#define FYDIGF_ENDS_AFTER_DOC	FY_BIT(7)
#define FYDIGF_WANTS_STREAM	FY_BIT(8)
#define FYDIGF_WANTS_DOC	FY_BIT(9)
	unsigned int generator_state;
};

int fy_document_iterator_setup(struct fy_document_iterator *fydi, const struct fy_document_iterator_cfg *cfg);
void fy_document_iterator_cleanup(struct fy_document_iterator *fydi);
struct fy_document_iterator *fy_document_iterator_create(void);
void fy_document_iterator_destroy(struct fy_document_iterator *fydi);

struct fy_event *
fy_document_iterator_document_start_internal(struct fy_document_iterator *fydi, struct fy_document *fyd,
					     struct fy_node *iterate_root);

struct fy_document_iterator_body_result {
	struct fy_node *fyn;
	bool end;
};

bool
fy_document_iterator_body_next_internal(struct fy_document_iterator *fydi,
					struct fy_document_iterator_body_result *res);

/* diagnostics */

static inline bool
fyd_debug_log_level_is_enabled(struct fy_document *fyd, enum fy_error_module module)
{
	return fyd && fy_diag_log_level_is_enabled(fyd->diag, FYET_DEBUG, module);
}

int fy_document_vdiag(struct fy_document *fyd, unsigned int flags,
		      const char *file, int line, const char *func,
		      const char *fmt, va_list ap);

int fy_document_diag(struct fy_document *fyd, unsigned int flags,
		     const char *file, int line, const char *func,
		     const char *fmt, ...)
	FY_FORMAT(printf, 6, 7);

void fy_document_diag_vreport(struct fy_document *fyd,
			      const struct fy_diag_report_ctx *fydrc,
			      const char *fmt, va_list ap);
void fy_document_diag_report(struct fy_document *fyd,
			     const struct fy_diag_report_ctx *fydrc,
			     const char *fmt, ...)
	FY_FORMAT(printf, 3, 4);

#ifdef FY_DEVMODE

#define fyd_debug(_fyd, _fmt, ...) \
	do { \
		struct fy_document *__fyd = (_fyd); \
		if (fyd_debug_log_level_is_enabled(__fyd, FYEM_DOC)) \
			fy_document_diag(__fyd, FYET_DEBUG, \
					__FILE__, __LINE__, __func__, \
					(_fmt) , ## __VA_ARGS__); \
	} while(0)
#else

#define fyd_debug(_fyd, _fmt, ...) \
	do { } while(0)

#endif

#define fyd_info(_fyd, _fmt, ...) \
	fy_document_diag((_fyd), FYET_INFO, __FILE__, __LINE__, __func__, \
			(_fmt) , ## __VA_ARGS__)
#define fyd_notice(_fyd, _fmt, ...) \
	fy_document_diag((_fyd), FYET_NOTICE, __FILE__, __LINE__, __func__, \
			(_fmt) , ## __VA_ARGS__)
#define fyd_warning(_fyd, _fmt, ...) \
	fy_document_diag((_fyd), FYET_WARNING, __FILE__, __LINE__, __func__, \
			(_fmt) , ## __VA_ARGS__)
#define fyd_error(_fyd, _fmt, ...) \
	fy_document_diag((_fyd), FYET_ERROR, __FILE__, __LINE__, __func__, \
			(_fmt) , ## __VA_ARGS__)

#define fyd_doc_debug(_fyd, _fmt, ...) \
	fyd_debug((_fyd), (_fmt) , ## __VA_ARGS__)

#define fyd_error_check(_fyd, _cond, _label, _fmt, ...) \
	do { \
		if (!(_cond)) { \
			fyd_error((_fyd), _fmt, ## __VA_ARGS__); \
			goto _label ; \
		} \
	} while(0)

#define _FYD_TOKEN_DIAG(_fyd, _fyt, _type, _module, _fmt, ...) \
	do { \
		struct fy_diag_report_ctx _drc; \
		memset(&_drc, 0, sizeof(_drc)); \
		_drc.type = (_type); \
		_drc.module = (_module); \
		_drc.fyt = (_fyt); \
		fy_document_diag_report((_fyd), &_drc, (_fmt) , ## __VA_ARGS__); \
	} while(0)

#define FYD_TOKEN_DIAG(_fyd, _fyt, _type, _module, _fmt, ...) \
	_FYD_TOKEN_DIAG(_fyd, fy_token_ref(_fyt), _type, _module, _fmt, ## __VA_ARGS__)

#define FYD_NODE_DIAG(_fyd, _fyn, _type, _module, _fmt, ...) \
	_FYD_TOKEN_DIAG(_fyd, fy_node_token(_fyn), _type, _module, _fmt, ## __VA_ARGS__)

#define FYD_TOKEN_ERROR(_fyd, _fyt, _module, _fmt, ...) \
	FYD_TOKEN_DIAG(_fyd, _fyt, FYET_ERROR, _module, _fmt, ## __VA_ARGS__)

#define FYD_NODE_ERROR(_fyd, _fyn, _module, _fmt, ...) \
	FYD_NODE_DIAG(_fyd, _fyn, FYET_ERROR, _module, _fmt, ## __VA_ARGS__)

#define FYD_TOKEN_ERROR_CHECK(_fyd, _fyt, _module, _cond, _label, _fmt, ...) \
	do { \
		if (!(_cond)) { \
			FYD_TOKEN_ERROR(_fyd, _fyt, _module, _fmt, ## __VA_ARGS__); \
			goto _label; \
		} \
	} while(0)

#define FYD_NODE_ERROR_CHECK(_fyd, _fyn, _module, _cond, _label, _fmt, ...) \
	do { \
		if (!(_cond)) { \
			FYD_NODE_ERROR(_fyd, _fyn, _module, _fmt, ## __VA_ARGS__); \
			goto _label; \
		} \
	} while(0)

#define FYD_TOKEN_WARNING(_fyd, _fyt, _module, _fmt, ...) \
	FYD_TOKEN_DIAG(_fyd, _fyt, FYET_WARNING, _module, _fmt, ## __VA_ARGS__)

#define FYD_NODE_WARNING(_fyd, _fyn, _type, _module, _fmt, ...) \
	FYD_NODE_DIAG(_fyd, _fyn, FYET_WARNING, _module, _fmt, ## __VA_ARGS__)

#endif
