/*
 * fy-emit.c - Internal YAML emitter methods
 *
 * Copyright (c) 2019 Pantelis Antoniou <pantelis.antoniou@konsulko.com>
 *
 * SPDX-License-Identifier: MIT
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <limits.h>
#include <ctype.h>
#include <errno.h>

#include <libfyaml.h>

#include "fy-parse.h"
#include "fy-emit.h"

static void fy_emitter_fill_default_colors(struct fy_emitter *fye);
static FILE *fy_emitter_get_output_fp(struct fy_emitter *fye);
static int fy_emitter_get_output_fd(struct fy_emitter *fye);
static int fy_emitter_null_output(struct fy_emitter *fye, enum fy_emitter_write_type type, const char *str, int len, void *userdata);

void fy_emit_save_ctx_cleanup(struct fy_emitter *emit, struct fy_emit_save_ctx *sc);

static inline struct fy_token_list *token_recycle_list(struct fy_emitter *emit, struct fy_parser *fyp)
{
	if (fyp && fyp->recycled_token_list)
		return fyp->recycled_token_list;
	if (emit && emit->recycled_token_list)
		return emit->recycled_token_list;
	return NULL;
}

static inline void fy_emit_token_unref(struct fy_emitter *emit, struct fy_parser *fyp, struct fy_token *fyt)
{
	fy_token_unref_rl(token_recycle_list(emit, fyp), fyt);
}

/* fwd decl */
void fy_emit_write(struct fy_emitter *emit, enum fy_emitter_write_type type, const char *str, size_t len);
void fy_emit_printf(struct fy_emitter *emit, enum fy_emitter_write_type type, const char *fmt, ...)
	FY_FORMAT(printf, 3, 4);

static inline bool fy_emit_is_json_mode(const struct fy_emitter *emit)
{
	enum fy_emitter_cfg_flags flags;

	if (emit->force_json)
		return true;

	flags = emit->xcfg.cfg.flags & FYECF_MODE(FYECF_MODE_MASK);
	return flags == FYECF_MODE_JSON || flags == FYECF_MODE_JSON_TP ||
	       flags == FYECF_MODE_JSON_ONELINE || flags == FYECF_MODE_JSON_COMPACT;
}

static inline bool fy_emit_is_flow_mode(const struct fy_emitter *emit)
{
	enum fy_emitter_cfg_flags flags = emit->xcfg.cfg.flags & FYECF_MODE(FYECF_MODE_MASK);

	return flags == FYECF_MODE_FLOW || flags == FYECF_MODE_FLOW_ONELINE ||
	       flags == FYECF_MODE_FLOW_COMPACT || fy_emit_is_json_mode(emit);
}

static inline bool fy_emit_is_block_mode(const struct fy_emitter *emit)
{
	enum fy_emitter_cfg_flags flags = emit->xcfg.cfg.flags & FYECF_MODE(FYECF_MODE_MASK);

	return flags == FYECF_MODE_BLOCK || flags == FYECF_MODE_DEJSON || flags == FYECF_MODE_PRETTY;
}

static inline bool fy_emit_is_oneline(const struct fy_emitter *emit)
{
	enum fy_emitter_cfg_flags flags = emit->xcfg.cfg.flags & FYECF_MODE(FYECF_MODE_MASK);

	return flags == FYECF_MODE_FLOW_ONELINE || flags == FYECF_MODE_JSON_ONELINE;
}

static inline bool fy_emit_is_compact(const struct fy_emitter *emit)
{
	enum fy_emitter_cfg_flags flags = emit->xcfg.cfg.flags & FYECF_MODE(FYECF_MODE_MASK);

	return flags == FYECF_MODE_FLOW_COMPACT || flags == FYECF_MODE_JSON_COMPACT;
}

static inline bool fy_emit_is_oneline_or_compact(const struct fy_emitter *emit)
{
	return fy_emit_is_oneline(emit) || fy_emit_is_compact(emit);
}

static inline bool fy_emit_sc_oneline(const struct fy_emitter *emit,
				      const struct fy_emit_save_ctx *sc)
{
	return fy_emit_is_oneline_or_compact(emit) || sc->oneline_flow;
}

static inline bool fy_emit_preserve_flow_layout(const struct fy_emitter *emit)
{
	return !!(emit->xcfg.xflags & FYEXCF_PRESERVE_FLOW_LAYOUT);
}

static inline bool fy_emit_is_dejson_mode(const struct fy_emitter *emit)
{
	enum fy_emitter_cfg_flags flags = emit->xcfg.cfg.flags & FYECF_MODE(FYECF_MODE_MASK);

	return flags == FYECF_MODE_DEJSON;
}

static inline bool fy_emit_is_pretty_mode(const struct fy_emitter *emit)
{
	enum fy_emitter_cfg_flags flags = emit->xcfg.cfg.flags & FYECF_MODE(FYECF_MODE_MASK);

	return flags == FYECF_MODE_PRETTY;
}

static inline bool fy_emit_is_original_mode(const struct fy_emitter *emit)
{
	enum fy_emitter_cfg_flags flags = emit->xcfg.cfg.flags & FYECF_MODE(FYECF_MODE_MASK);

	return flags == FYECF_MODE_ORIGINAL;
}

static inline bool fy_emit_is_manual(const struct fy_emitter *emit)
{
	enum fy_emitter_cfg_flags flags = emit->xcfg.cfg.flags & FYECF_MODE(FYECF_MODE_MASK);

	return flags == FYECF_MODE_MANUAL;
}

static inline int fy_emit_indent(struct fy_emitter *emit)
{
	int indent;

	indent = (emit->xcfg.cfg.flags & FYECF_INDENT(FYECF_INDENT_MASK)) >> FYECF_INDENT_SHIFT;
	return indent ? indent : 2;
}

static inline int fy_emit_width(struct fy_emitter *emit)
{
	int width;

	if (fy_emit_is_oneline(emit))
		return INT_MAX;

	width = (emit->xcfg.cfg.flags & FYECF_WIDTH(FYECF_WIDTH_MASK)) >> FYECF_WIDTH_SHIFT;
	if (width == 0)
		return 80;
	if (width == FYECF_WIDTH_MASK)
		return INT_MAX;
	return width;
}

static inline bool fy_emit_output_comments(struct fy_emitter *emit)
{
	return !!(emit->xcfg.cfg.flags & FYECF_OUTPUT_COMMENTS) &&
		!fy_emit_is_oneline_or_compact(emit) &&
		!fy_emit_is_json_mode(emit);
}

static int fy_emit_node_check_json(struct fy_emitter *emit, struct fy_node *fyn)
{
	struct fy_document *fyd;
	struct fy_node *fyni;
	struct fy_node_pair *fynp, *fynpi;
	int ret;

	if (!fyn)
		return 0;

	fyd = fyn->fyd;

	switch (fyn->type) {
	case FYNT_SCALAR:
		FYD_TOKEN_ERROR_CHECK(fyd, fyn->scalar, FYEM_INTERNAL,
				!fy_node_is_alias(fyn), err_out,
				"aliases not allowed in JSON emit mode");
		break;

	case FYNT_SEQUENCE:
		for (fyni = fy_node_list_head(&fyn->sequence); fyni;
				fyni = fy_node_next(&fyn->sequence, fyni)) {
			ret = fy_emit_node_check_json(emit, fyni);
			if (ret)
				return ret;
		}
		break;

	case FYNT_MAPPING:
		for (fynp = fy_node_pair_list_head(&fyn->mapping); fynp; fynp = fynpi) {

			fynpi = fy_node_pair_next(&fyn->mapping, fynp);

			ret = fy_emit_node_check_json(emit, fynp->key);
			if (ret)
				return ret;
			ret = fy_emit_node_check_json(emit, fynp->value);
			if (ret)
				return ret;
		}
		break;
	}
	return 0;
err_out:
	return -1;
}

static int fy_emit_node_check(struct fy_emitter *emit, struct fy_node *fyn)
{
	int ret;

	if (!fyn)
		return 0;

	if (fy_emit_is_json_mode(emit) && !emit->source_json) {
		ret = fy_emit_node_check_json(emit, fyn);
		if (ret)
			return ret;
	}

	return 0;
}

void fy_emit_node_internal(struct fy_emitter *emit, struct fy_node *fyn, int flags, int indent, bool is_key);
void fy_emit_scalar(struct fy_emitter *emit, struct fy_node *fyn, int flags, int indent, bool is_key);
void fy_emit_sequence(struct fy_emitter *emit, struct fy_node *fyn, int flags, int indent);
void fy_emit_mapping(struct fy_emitter *emit, struct fy_node *fyn, int flags, int indent);

void fy_emit_write(struct fy_emitter *emit, enum fy_emitter_write_type type, const char *str, size_t len);
void fy_emit_puts(struct fy_emitter *emit, enum fy_emitter_write_type type, const char *str);
void fy_emit_putc(struct fy_emitter *emit, enum fy_emitter_write_type type, int c);

/* simple write, just ascii, advance column */
void fy_emit_write_simple(struct fy_emitter *emit, enum fy_emitter_write_type type, const char *str, size_t len)
{
	int outlen;

	if (!len)
		return;

	outlen = emit->xcfg.cfg.output(emit, type, str, (int)len, emit->xcfg.cfg.userdata);
	if (outlen != (int)len)
		emit->output_error = true;

	emit->column += (int)len;
}

/* simple puts as above */
void fy_emit_puts_simple(struct fy_emitter *emit, enum fy_emitter_write_type type, const char *str)
{
	fy_emit_write_simple(emit, type, str, strlen(str));
}

void fy_emit_putc_simple(struct fy_emitter *emit, enum fy_emitter_write_type type, int c)
{
	char cc = (char)c;

	fy_emit_write_simple(emit, type, &cc, 1);
	if (cc == '\n') {
		emit->column = 0;
		emit->line++;
	}
}

void fy_emit_write(struct fy_emitter *emit, enum fy_emitter_write_type type, const char *str, size_t len)
{
	int c, w;
	const char *m, *e;
	int outlen;

	if (!len || len > INT_MAX)
		return;

	outlen = emit->xcfg.cfg.output(emit, type, str, (int)len, emit->xcfg.cfg.userdata);
	if (outlen != (int)len)
		emit->output_error = true;

	e = str + len;
	while ((c = fy_utf8_get(str, (e - str), &w)) >= 0) {

		/* special handling for MSDOS */
		if (c == '\r' && (e - str) > 1 && str[1] == '\n') {
			str += 2;
			emit->column = 0;
			emit->line++;
			continue;
		}

		/* regular line break */
		if (fy_is_lb_r_n(c)) {
			emit->column = 0;
			emit->line++;
			str += w;
			continue;
		}

		/* completely ignore ANSI color escape sequences */
		if (c == '\x1b' && (e - str) > 2 && str[1] == '[' &&
		    (m = memchr(str, 'm', e - str)) != NULL) {
			str = m + 1;
			continue;
		}

		emit->column++;
		str += w;
	}
}

void fy_emit_puts(struct fy_emitter *emit, enum fy_emitter_write_type type, const char *str)
{
	fy_emit_write(emit, type, str, strlen(str));
}

void fy_emit_putc(struct fy_emitter *emit, enum fy_emitter_write_type type, int c)
{
	char buf[FY_UTF8_FORMAT_BUFMIN];

	fy_utf8_format(c, buf, fyue_none);
	fy_emit_puts(emit, type, buf);
}

void fy_emit_vprintf(struct fy_emitter *emit, enum fy_emitter_write_type type, const char *fmt, va_list ap)
{
	char *str;
	int size;
	va_list ap2;

	va_copy(ap2, ap);

	size = vsnprintf(NULL, 0, fmt, ap);
	if (size < 0)
		return;

	str = alloca(size + 1);
	size = vsnprintf(str, size + 1, fmt, ap2);
	if (size < 0)
		return;

	fy_emit_write(emit, type, str, size);
}

void fy_emit_printf(struct fy_emitter *emit, enum fy_emitter_write_type type, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	fy_emit_vprintf(emit, type, fmt, ap);
	va_end(ap);
}

void fy_emit_write_ws(struct fy_emitter *emit)
{
	fy_emit_putc_simple(emit, fyewt_whitespace, ' ');
	emit->flags |= FYEF_WHITESPACE;
}

void fy_emit_write_indent(struct fy_emitter *emit, int indent)
{
	int len;
	char *ws;

	indent = indent > 0 ? indent : 0;

	if (!fy_emit_indentation(emit) || emit->column > indent ||
	    (emit->column == indent && !fy_emit_whitespace(emit)))
		fy_emit_putc_simple(emit, fyewt_linebreak, '\n');

	if (emit->column < indent) {
		len = indent - emit->column;
		ws = alloca(len + 1);
		memset(ws, ' ', len);
		ws[len] = '\0';
		fy_emit_write_simple(emit, fyewt_indent, ws, len);
	}

	emit->flags |= FYEF_WHITESPACE | FYEF_INDENTATION;
}

enum document_indicator {
	di_question_mark,
	di_colon,
	di_dash,
	di_left_bracket,
	di_right_bracket,
	di_left_brace,
	di_right_brace,
	di_comma,
	di_bar,
	di_greater,
	di_single_quote_start,
	di_single_quote_end,
	di_double_quote_start,
	di_double_quote_end,
	di_ambersand,
	di_star,
};

void fy_emit_write_indicator(struct fy_emitter *emit,
		enum document_indicator indicator,
		int flags, int indent,
		enum fy_emitter_write_type wtype)
{
	/* extended indicators mode? */
	if (wtype == fyewt_indicator &&
	    (emit->xcfg.xflags & FYEXCF_EXTENDED_INDICATORS)) {
		wtype = FYEWT_EXTENDED_INDICATORS_FIRST + (unsigned int)indicator;
		assert(wtype >= FYEWT_EXTENDED_INDICATORS_FIRST &&
				wtype <= FYEWT_EXTENDED_INDICATORS_LAST);
	}

	switch (indicator) {

	case di_question_mark:
		if (!fy_emit_whitespace(emit))
			fy_emit_write_ws(emit);
		fy_emit_putc_simple(emit, wtype, '?');
		emit->flags &= ~(FYEF_WHITESPACE | FYEF_OPEN_ENDED);
		break;

	case di_colon:
		fy_emit_putc_simple(emit, wtype, ':');
		emit->flags &= ~(FYEF_WHITESPACE | FYEF_OPEN_ENDED);
		break;

	case di_dash:
		if (!fy_emit_whitespace(emit))
			fy_emit_write_ws(emit);
		fy_emit_putc_simple(emit, wtype, '-');
		emit->flags &= ~(FYEF_WHITESPACE | FYEF_OPEN_ENDED);
		break;

	case di_left_bracket:
	case di_left_brace:
		emit->flow_level++;
		if (!fy_emit_whitespace(emit))
			fy_emit_write_ws(emit);
		fy_emit_putc_simple(emit, wtype, indicator == di_left_bracket ? '[' : '{');
		emit->flags |= FYEF_WHITESPACE;
		emit->flags &= ~(FYEF_INDENTATION | FYEF_OPEN_ENDED);
		break;

	case di_right_bracket:
	case di_right_brace:
		emit->flow_level--;
		fy_emit_putc_simple(emit, wtype, indicator == di_right_bracket ? ']' : '}');
		emit->flags &= ~(FYEF_WHITESPACE | FYEF_INDENTATION | FYEF_OPEN_ENDED);
		break;

	case di_comma:
		fy_emit_putc_simple(emit, wtype, ',');
		emit->flags &= ~(FYEF_WHITESPACE | FYEF_INDENTATION | FYEF_OPEN_ENDED);
		break;

	case di_bar:
	case di_greater:
		if (!fy_emit_whitespace(emit))
			fy_emit_write_ws(emit);
		fy_emit_putc_simple(emit, wtype, indicator == di_bar ? '|' : '>');
		emit->flags &= ~(FYEF_INDENTATION | FYEF_WHITESPACE | FYEF_OPEN_ENDED);
		break;

	case di_single_quote_start:
	case di_double_quote_start:
		if (!fy_emit_whitespace(emit))
			fy_emit_write_ws(emit);
		fy_emit_putc_simple(emit, wtype, indicator == di_single_quote_start ? '\'' : '"');
		emit->flags &= ~(FYEF_WHITESPACE | FYEF_INDENTATION | FYEF_OPEN_ENDED);
		break;

	case di_single_quote_end:
	case di_double_quote_end:
		fy_emit_putc_simple(emit, wtype, indicator == di_single_quote_end ? '\'' : '"');
		emit->flags &= ~(FYEF_WHITESPACE | FYEF_INDENTATION | FYEF_OPEN_ENDED);
		break;

	case di_ambersand:
		if (!fy_emit_whitespace(emit))
			fy_emit_write_ws(emit);
		fy_emit_putc_simple(emit, wtype, '&');
		emit->flags &= ~(FYEF_WHITESPACE | FYEF_INDENTATION);
		break;

	case di_star:
		if (!fy_emit_whitespace(emit))
			fy_emit_write_ws(emit);
		fy_emit_putc_simple(emit, wtype, '*');
		emit->flags &= ~(FYEF_WHITESPACE | FYEF_INDENTATION);
		break;
	}
}

int fy_emit_increase_indent(struct fy_emitter *emit, int flags, int indent)
{
	if (indent < 0)
		return (flags & DDNF_FLOW) ? fy_emit_indent(emit) : 0;

	if (!(flags & DDNF_INDENTLESS))
		return indent + fy_emit_indent(emit);

	return indent;
}

void fy_emit_write_comment(struct fy_emitter *emit, int flags, int indent, const char *str, size_t len, struct fy_atom *handle)
{
	const char *s, *e, *sr;
	int c, w;
	bool breaks;

	if (!str || !len)
		return;

	if (len == (size_t)-1)
		len = strlen(str);

	if (!fy_emit_whitespace(emit))
		fy_emit_write_ws(emit);
	indent = emit->column;

	s = str;
	e = str + len;

	sr = s;	/* start of normal output run */
	breaks = false;
	while (s < e && (c = fy_utf8_get(s, e - s, &w)) > 0) {

		if (fy_is_lb_m(c, fy_atom_lb_mode(handle))) {

			/* output run */
			fy_emit_write(emit, fyewt_comment, sr, s - sr);
			sr = s + w;
			fy_emit_write_indent(emit, indent);
			emit->flags |= FYEF_INDENTATION;
			breaks = true;
		} else {

			if (breaks) {
				fy_emit_write(emit, fyewt_comment, sr, s - sr);
				sr = s;
				fy_emit_write_indent(emit, indent);
			}
			emit->flags &= ~FYEF_INDENTATION;
			breaks = false;
		}

		s += w;
	}

	/* dump what's remaining */
	fy_emit_write(emit, fyewt_comment, sr, s - sr);

	emit->flags |= (FYEF_WHITESPACE | FYEF_INDENTATION);
}

struct fy_atom *fy_emit_token_comment_handle(struct fy_emitter *emit, struct fy_token *fyt, enum fy_comment_placement placement)
{
	struct fy_atom *handle;

	handle = fy_token_comment_handle(fyt, placement, false);
	return handle && fy_atom_is_set(handle) ? handle : NULL;
}

void fy_emit_document_start_indicator(struct fy_emitter *emit)
{
	/* do not emit twice */
	if (emit->flags & FYEF_HAD_DOCUMENT_START)
		return;

	/* do not try to emit if it's json mode */
	if (fy_emit_is_json_mode(emit))
		goto no_doc_emit;

	/* output linebreak anyway */
	if (emit->column)
		fy_emit_putc_simple(emit, fyewt_linebreak, '\n');

	/* ok, emit document start indicator */
	fy_emit_puts_simple(emit, fyewt_document_indicator, "---");
	emit->flags &= ~FYEF_WHITESPACE;
	emit->flags |= FYEF_HAD_DOCUMENT_START;
	return;

no_doc_emit:
	emit->flags &= ~FYEF_HAD_DOCUMENT_START;
}

struct fy_token *fy_node_value_token(struct fy_node *fyn)
{
	struct fy_token *fyt;

	if (!fyn)
		return NULL;

	switch (fyn->type) {
	case FYNT_SCALAR:
		fyt = fyn->scalar;
		break;
	case FYNT_SEQUENCE:
		fyt = fyn->sequence_start;
		break;
	case FYNT_MAPPING:
		fyt = fyn->mapping_start;
		break;
	default:
		fyt = NULL;
		break;
	}

	return fyt;
}

bool fy_emit_token_has_comment(struct fy_emitter *emit, struct fy_token *fyt, enum fy_comment_placement placement)
{
	if (!fy_emit_output_comments(emit))
		return false;
	return fy_emit_token_comment_handle(emit, fyt, placement) ? true : false;
}

bool fy_emit_node_has_comment(struct fy_emitter *emit, struct fy_node *fyn, enum fy_comment_placement placement)
{
	return fy_emit_token_has_comment(emit, fy_node_value_token(fyn), placement);
}

void fy_emit_token_comment(struct fy_emitter *emit, struct fy_token *fyt, int flags, int indent,
			  enum fy_comment_placement placement)
{
	struct fy_atom *handle;
	char *text;
	const char *t;
	size_t len;

	handle = fy_emit_token_comment_handle(emit, fyt, placement);
	if (!handle)
		return;

	len = fy_atom_format_text_length(handle);
	if (len < 0)
		return;

	text = alloca(len + 1);

	if (placement == fycp_top || placement == fycp_bottom) {
		int comment_indent = indent > 0 ? indent : 0;
		if (handle && indent >= 0) {
			comment_indent = indent + handle->indent_delta;
			if (comment_indent < 0)
				comment_indent = 0;
		}
		fy_emit_write_indent(emit, comment_indent);
		emit->flags |= FYEF_WHITESPACE;
	}

	t = fy_atom_format_text(handle, text, len + 1);

	fy_emit_write_comment(emit, flags, indent, t, len, handle);

	emit->flags &= ~FYEF_INDENTATION;

	if (placement == fycp_top || placement == fycp_bottom) {
		fy_emit_write_indent(emit, indent);
		emit->flags |= FYEF_WHITESPACE;
	}
}

void fy_emit_common_node_preamble(struct fy_emitter *emit,
		struct fy_token *fyt_value,
		struct fy_token *fyt_anchor,
		struct fy_token *fyt_tag,
		int flags, int indent)
{
	const char *anchor = NULL;
	const char *tag = NULL;
	const char *td_prefix __FY_DEBUG_UNUSED__;
	const char *td_handle;
	size_t td_prefix_size, td_handle_size;
	size_t tag_len = 0, anchor_len = 0;
	bool json_mode = false;
	struct fy_emit_save_ctx *sc = &emit->s_sc;

	(void)sc;

	/* content for root always starts on a new line */
	if (!fy_emit_is_oneline(emit) && (flags & DDNF_ROOT) && emit->column != 0 &&
            !(emit->flags & FYEF_HAD_DOCUMENT_START)) {
		fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
		emit->flags |= FYEF_WHITESPACE | FYEF_INDENTATION;
	}

	json_mode = fy_emit_is_json_mode(emit);

	if (!json_mode) {
		if (!(emit->xcfg.cfg.flags & FYECF_STRIP_LABELS)) {
			if (fyt_anchor)
				anchor = fy_token_get_text(fyt_anchor, &anchor_len);
		}

		if (!(emit->xcfg.cfg.flags & FYECF_STRIP_TAGS)) {
			if (fyt_tag)
				tag = fy_token_get_text(fyt_tag, &tag_len);
		}

		if (anchor) {
			fy_emit_write_indicator(emit, di_ambersand, flags, indent, fyewt_anchor);
			fy_emit_write(emit, fyewt_anchor, anchor, anchor_len);
		}

		if (tag) {
			if (!fy_emit_whitespace(emit))
				fy_emit_write_ws(emit);

			td_handle = fy_tag_token_get_directive_handle(fyt_tag, &td_handle_size);
			assert(td_handle);
			td_prefix = fy_tag_token_get_directive_prefix(fyt_tag, &td_prefix_size);
			assert(td_prefix);

			if (!td_handle_size)
				fy_emit_printf(emit, fyewt_tag, "!<%.*s>", (int)tag_len, tag);
			else
				fy_emit_printf(emit, fyewt_tag, "%.*s%.*s",
						(int)td_handle_size, td_handle,
						(int)(tag_len - td_prefix_size), tag + td_prefix_size);

			emit->flags &= ~(FYEF_WHITESPACE | FYEF_INDENTATION);
		}
	}
}

void fy_emit_node_internal(struct fy_emitter *emit, struct fy_node *fyn, int flags, int indent, bool is_key)
{
	enum fy_node_type type;
	struct fy_anchor *fya;
	struct fy_token *fyt_anchor = NULL;
	struct fy_token *fyt_value = NULL;

	if (!(emit->xcfg.cfg.flags & FYECF_STRIP_LABELS)) {
		fya = fy_document_lookup_anchor_by_node(emit->fyd, fyn);
		if (fya)
			fyt_anchor = fya->anchor;
	}

	type = fyn ? fyn->type : FYNT_SCALAR;

	if (type == FYNT_SCALAR)
		fyt_value = fyn ? fyn->scalar : NULL;
	else if (type == FYNT_SEQUENCE)
		fyt_value = fyn->sequence_start;
	else if (type == FYNT_MAPPING)
		fyt_value = fyn->mapping_start;

	fy_emit_common_node_preamble(emit, fyt_value, fyt_anchor, fyn->tag, flags, indent);

	if (type != FYNT_SCALAR && (flags & DDNF_ROOT) && emit->column != 0) {
		fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
		emit->flags |= FYEF_WHITESPACE | FYEF_INDENTATION;
	}

	switch (type) {
	case FYNT_SCALAR:
		/* if we're pretty and root at column 0 (meaning it's a single scalar document) output --- */
		if ((flags & DDNF_ROOT) && fy_emit_is_pretty_mode(emit) && !emit->column &&
				!fy_emit_is_flow_mode(emit) && !(flags & DDNF_FLOW))
			fy_emit_document_start_indicator(emit);
		fy_emit_scalar(emit, fyn, flags, indent, is_key);
		break;
	case FYNT_SEQUENCE:
		FYD_TOKEN_ERROR_CHECK(fyn->fyd, fyn->sequence_start, FYEM_INTERNAL,
				!is_key || !fy_emit_is_json_mode(emit), err_out,
				"JSON does not allow sequences as keys");
		fy_emit_sequence(emit, fyn, flags, indent);
		break;
	case FYNT_MAPPING:
		FYD_TOKEN_ERROR_CHECK(fyn->fyd, fyn->mapping_start, FYEM_INTERNAL,
				!is_key || !fy_emit_is_json_mode(emit), err_out,
				"JSON does not allow mappings as keys");
		fy_emit_mapping(emit, fyn, flags, indent);
		break;
	}
err_out:
	/* nothing */
	return;
}

void fy_emit_token_write_plain(struct fy_emitter *emit, struct fy_token *fyt, int flags, int indent)
{
	bool allow_breaks, should_indent, spaces, breaks;
	int c;
	enum fy_emitter_write_type wtype;
	const char *str = NULL;
	size_t len = 0;
	struct fy_atom *atom;
	struct fy_atom_iter iter;

	/* null and not json mode */
	if (!fyt && !fy_emit_is_json_mode(emit))
		goto out;

	wtype = (flags & DDNF_SIMPLE_SCALAR_KEY) ? fyewt_plain_scalar_key : fyewt_plain_scalar;

	atom = fy_token_atom(fyt);

	/* null and json mode */
	if (fy_emit_is_json_mode(emit) && fyt->scalar.is_null) {
		fy_emit_puts_simple(emit, wtype, "null");
		goto out;
	}

	allow_breaks = !(flags & DDNF_SIMPLE) && !fy_emit_is_json_mode(emit) && !fy_emit_is_oneline(emit);

	/* very very simple case first (90% of cases) */
	str = fy_token_get_direct_simple_output(fyt, &len);
	if (str && (!allow_breaks || (fy_emit_accum_column(&emit->ea) + (int)len <= fy_emit_width(emit)))) {
		fy_emit_write_simple(emit, wtype, str, len);
		goto out;
	}

	/* simple case first (90% of cases) */
	str = fy_token_get_direct_output(fyt, &len);
	if (str && fy_token_atom_style(fyt) == FYAS_PLAIN) {
		fy_emit_write(emit, wtype, str, len);
		goto out;
	}

	if (!atom)
		goto out;

	spaces = false;
	breaks = false;

	fy_atom_iter_start(atom, &iter);
	fy_emit_accum_start(&emit->ea, emit->column, fy_token_atom_lb_mode(fyt));
	while ((c = fy_atom_iter_utf8_get(&iter)) > 0) {

		if (fy_is_ws(c)) {

			should_indent = allow_breaks && !spaces &&
					fy_emit_accum_column(&emit->ea) > fy_emit_width(emit);

			if (should_indent && !fy_is_ws(fy_atom_iter_utf8_peek(&iter))) {
				fy_emit_output_accum(emit, wtype, &emit->ea);
				emit->flags &= ~FYEF_INDENTATION;
				fy_emit_write_indent(emit, indent);
				fy_emit_output_col_sync(emit, &emit->ea);
			} else
				fy_emit_accum_utf8_put(&emit->ea, c);

			spaces = true;

		} else if (fy_is_lb_m(c, fy_token_atom_lb_mode(fyt))) {

			/* blergh */
			if (!allow_breaks)
				break;

			/* output run */
			if (!breaks) {
				fy_emit_output_accum(emit, wtype, &emit->ea);
				fy_emit_write_indent(emit, indent);
				fy_emit_output_col_sync(emit, &emit->ea);
			}

			emit->flags &= ~FYEF_INDENTATION;
			fy_emit_write_indent(emit, indent);
			fy_emit_output_col_sync(emit, &emit->ea);

			breaks = true;

		} else {

			if (breaks) {
				fy_emit_write_indent(emit, indent);
				fy_emit_output_col_sync(emit, &emit->ea);
			}

			fy_emit_accum_utf8_put(&emit->ea, c);

			emit->flags &= ~FYEF_INDENTATION;

			spaces = false;
			breaks = false;
		}
	}
	fy_emit_output_accum(emit, wtype, &emit->ea);
	fy_emit_accum_finish(&emit->ea);
	fy_atom_iter_finish(&iter);

out:
	emit->flags &= ~(FYEF_WHITESPACE | FYEF_INDENTATION);
}

void fy_emit_token_write_alias(struct fy_emitter *emit, struct fy_token *fyt, int flags, int indent)
{
	const char *str = NULL;
	size_t len = 0;
	struct fy_atom_iter iter;
	int c;

	if (!fyt)
		return;

	fy_emit_write_indicator(emit, di_star, flags, indent, fyewt_alias);

	/* try direct output first (99% of cases) */
	str = fy_token_get_direct_output(fyt, &len);
	if (str) {
		fy_emit_write(emit, fyewt_alias, str, len);
		return;
	}

	/* corner case, use iterator */
	fy_atom_iter_start(fy_token_atom(fyt), &iter);
	fy_emit_accum_start(&emit->ea, emit->column, fy_token_atom_lb_mode(fyt));
	while ((c = fy_atom_iter_utf8_get(&iter)) > 0)
		fy_emit_accum_utf8_put(&emit->ea, c);
	fy_emit_output_accum(emit, fyewt_alias, &emit->ea);
	fy_emit_accum_finish(&emit->ea);
	fy_atom_iter_finish(&iter);
}

void fy_emit_token_write_quoted(struct fy_emitter *emit, struct fy_token *fyt, int flags, int indent, char qc)
{
	const struct fy_token_analysis *ta;
	bool allow_breaks, spaces, breaks;
	int c, i, w, digit;
	enum fy_emitter_write_type wtype;
	const char *str = NULL;
	size_t len = 0;
	bool should_indent, done_esc;
	struct fy_atom *atom;
	struct fy_atom_iter iter;
	enum fy_atom_style target_style;
	uint32_t hi_surrogate, lo_surrogate;
	uint8_t non_utf8[4];
	size_t non_utf8_len, k;
	int emit_width;

	wtype = qc == '\'' ?
		((flags & DDNF_SIMPLE_SCALAR_KEY) ?
		 	fyewt_single_quoted_scalar_key : fyewt_single_quoted_scalar) :
		((flags & DDNF_SIMPLE_SCALAR_KEY) ?
		 	fyewt_double_quoted_scalar_key : fyewt_double_quoted_scalar);

	fy_emit_write_indicator(emit,
			qc == '\'' ? di_single_quote_start : di_double_quote_start,
			flags, indent, wtype);

	/* XXX check whether this is ever the case */
	if (!fyt)
		goto out;

	/* note that if the original target style and the target differ
	 * we can note use direct output
	 */
	target_style = qc == '"' ? FYAS_DOUBLE_QUOTED : FYAS_SINGLE_QUOTED;

	allow_breaks = !(flags & DDNF_SIMPLE) && !fy_emit_is_json_mode(emit) && !fy_emit_is_oneline(emit);
	emit_width = fy_emit_width(emit);

	ta = fy_token_text_analyze(fyt);

	/* simple case of direct output (large amount of cases) */
	str = fy_token_get_direct_output(fyt, &len);
	if (str && fy_token_atom_style(fyt) == target_style &&
	   (ta->flags & FYTTAF_DIRECT_OUTPUT) && emit->column + ta->maxcol < emit_width) {
		fy_emit_write(emit, wtype, str, len);
		goto out;
	}

	/* no atom? i.e. empty */
	atom = fy_token_atom(fyt);
	if (!atom)
		goto out;

	spaces = false;
	breaks = false;

	fy_atom_iter_start(atom, &iter);
	fy_emit_accum_start(&emit->ea, emit->column, fy_token_atom_lb_mode(fyt));
	for (;;) {
		non_utf8_len = sizeof(non_utf8);
		c = fy_atom_iter_utf8_quoted_get(&iter, &non_utf8_len, non_utf8);
		if (c < 0)
			break;

		if (c == 0 && non_utf8_len > 0) {
			for (k = 0; k < non_utf8_len; k++) {
				c = (int)non_utf8[k] & 0xff;
				fy_emit_accum_utf8_put(&emit->ea, '\\');
				fy_emit_accum_utf8_put(&emit->ea, 'x');
				digit = ((unsigned int)c >> 4) & 15;
				fy_emit_accum_utf8_put(&emit->ea,
						digit <= 9 ? ('0' + digit) : ('A' + digit - 10));
				digit = (unsigned int)c & 15;
				fy_emit_accum_utf8_put(&emit->ea,
						digit <= 9 ? ('0' + digit) : ('A' + digit - 10));
			}
			continue;
		}

		if (fy_is_space(c) || (qc == '\'' && fy_is_ws(c))) {
			should_indent = allow_breaks && !spaces &&
					fy_emit_accum_column(&emit->ea) >= emit_width;

			if (should_indent &&
				((qc == '\'' && fy_is_ws(fy_atom_iter_utf8_peek(&iter))) ||
				  qc == '"')) {
				fy_emit_output_accum(emit, wtype, &emit->ea);

				if (qc == '"' && fy_is_ws(fy_atom_iter_utf8_peek(&iter)))
					fy_emit_putc_simple(emit, wtype, '\\');

				emit->flags &= ~FYEF_INDENTATION;
				fy_emit_write_indent(emit, indent);
				fy_emit_output_col_sync(emit, &emit->ea);
			} else
				fy_emit_accum_utf8_put(&emit->ea, c);

			spaces = true;
			breaks = false;

		} else if (qc == '\'' && fy_is_lb_m(c, fy_token_atom_lb_mode(fyt))) {

			/* blergh */
			if (!allow_breaks)
				break;

			/* output run */
			if (!breaks) {
				fy_emit_output_accum(emit, wtype, &emit->ea);
				fy_emit_write_indent(emit, indent);
				fy_emit_output_col_sync(emit, &emit->ea);
			}

			emit->flags &= ~FYEF_INDENTATION;
			fy_emit_write_indent(emit, indent);
			fy_emit_output_col_sync(emit, &emit->ea);

			breaks = true;
		} else {
			/* output run */
			if (breaks) {
				fy_emit_output_accum(emit, wtype, &emit->ea);
				fy_emit_write_indent(emit, indent);
				fy_emit_output_col_sync(emit, &emit->ea);

			} else if (qc == '"' && allow_breaks && fy_emit_accum_column(&emit->ea) >= emit_width) {

				fy_emit_output_accum(emit, wtype, &emit->ea);

				fy_emit_putc_simple(emit, wtype, '\\');

				emit->flags &= ~FYEF_INDENTATION;
				fy_emit_write_indent(emit, indent);
				fy_emit_output_col_sync(emit, &emit->ea);
			}

			/* escape */
			if (qc == '\'' && c == '\'') {
				fy_emit_accum_utf8_put(&emit->ea, '\'');
				fy_emit_accum_utf8_put(&emit->ea, '\'');
			} else if (qc == '"' &&
				   ((!fy_is_printq(c) || c == '"' || c == '\\') ||
				    (fy_emit_is_json_mode(emit) && !fy_is_json_unescaped(c)))) {

				fy_emit_accum_utf8_put(&emit->ea, '\\');

				/* common YAML and JSON escapes */
				done_esc = false;
				switch (c) {
				case '\b':
					fy_emit_accum_utf8_put(&emit->ea, 'b');
					done_esc = true;
					break;
				case '\f':
					fy_emit_accum_utf8_put(&emit->ea, 'f');
					done_esc = true;
					break;
				case '\n':
					fy_emit_accum_utf8_put(&emit->ea, 'n');
					done_esc = true;
					break;
				case '\r':
					fy_emit_accum_utf8_put(&emit->ea, 'r');
					done_esc = true;
					break;
				case '\t':
					fy_emit_accum_utf8_put(&emit->ea, 't');
					done_esc = true;
					break;
				case '"':
					fy_emit_accum_utf8_put(&emit->ea, '"');
					done_esc = true;
					break;
				case '\\':
					fy_emit_accum_utf8_put(&emit->ea, '\\');
					done_esc = true;
					break;
				}

				if (done_esc)
					goto done;

				if (!fy_emit_is_json_mode(emit)) {
					switch (c) {
					case '\0':
						fy_emit_accum_utf8_put(&emit->ea, '0');
						break;
					case '\a':
						fy_emit_accum_utf8_put(&emit->ea, 'a');
						break;
					case '\v':
						fy_emit_accum_utf8_put(&emit->ea, 'v');
						break;
					case '\x1b':	// \e
						fy_emit_accum_utf8_put(&emit->ea, 'e');
						break;
					case 0x85:
						fy_emit_accum_utf8_put(&emit->ea, 'N');
						break;
					case 0xa0:
						fy_emit_accum_utf8_put(&emit->ea, '_');
						break;
					case 0x2028:
						fy_emit_accum_utf8_put(&emit->ea, 'L');
						break;
					case 0x2029:
						fy_emit_accum_utf8_put(&emit->ea, 'P');
						break;
					default:
						if ((unsigned int)c <= 0xff) {
							fy_emit_accum_utf8_put(&emit->ea, 'x');
							w = 2;
						} else if ((unsigned int)c <= 0xffff) {
							fy_emit_accum_utf8_put(&emit->ea, 'u');
							w = 4;
						} else if ((unsigned int)c <= 0xffffffff) {
							fy_emit_accum_utf8_put(&emit->ea, 'U');
							w = 8;
						}

						for (i = w - 1; i >= 0; i--) {
							digit = ((unsigned int)c >> (i * 4)) & 15;
							fy_emit_accum_utf8_put(&emit->ea,
									digit <= 9 ? ('0' + digit) : ('A' + digit - 10));
						}
						break;
					}

				} else {
					/* JSON escapes all others in \uXXXX and \uXXXX\uXXXX */
					w = 4;
					if ((unsigned int)c <= 0xffff) {
						fy_emit_accum_utf8_put(&emit->ea, 'u');
						for (i = w - 1; i >= 0; i--) {
							digit = ((unsigned int)c >> (i * 4)) & 15;
							fy_emit_accum_utf8_put(&emit->ea,
									digit <= 9 ? ('0' + digit) : ('A' + digit - 10));
						}
					} else {
						hi_surrogate = 0xd800 | ((((c >> 16) & 0x1f) - 1) << 6) | ((c >> 10) & 0x3f);
						lo_surrogate = 0xdc00 | (c & 0x3ff);

						fy_emit_accum_utf8_put(&emit->ea, 'u');
						for (i = w - 1; i >= 0; i--) {
							digit = ((unsigned int)hi_surrogate >> (i * 4)) & 15;
							fy_emit_accum_utf8_put(&emit->ea,
									digit <= 9 ? ('0' + digit) : ('A' + digit - 10));
						}

						fy_emit_accum_utf8_put(&emit->ea, '\\');
						fy_emit_accum_utf8_put(&emit->ea, 'u');
						for (i = w - 1; i >= 0; i--) {
							digit = ((unsigned int)lo_surrogate >> (i * 4)) & 15;
							fy_emit_accum_utf8_put(&emit->ea,
									digit <= 9 ? ('0' + digit) : ('A' + digit - 10));
						}
					}
				}
			} else
				fy_emit_accum_utf8_put(&emit->ea, c);
done:
			emit->flags &= ~FYEF_INDENTATION;
			spaces = false;
			breaks = false;
		}
	}
	fy_emit_output_accum(emit, wtype, &emit->ea);
	fy_emit_accum_finish(&emit->ea);
	fy_atom_iter_finish(&iter);

out:
	fy_emit_write_indicator(emit,
			qc == '\'' ? di_single_quote_end : di_double_quote_end,
			flags, indent, wtype);
}

bool fy_emit_token_write_block_hints(struct fy_emitter *emit, struct fy_token *fyt, int flags, int indent, char *chompp)
{
	char chomp = '\0';
	bool explicit_chomp = false;
	struct fy_atom *atom;

	atom = fy_token_atom(fyt);
	if (!atom) {
		emit->flags &= ~FYEF_OPEN_ENDED;
		chomp = '-';
		goto out;
	}

	if (atom->starts_with_ws || atom->starts_with_lb) {
		fy_emit_putc_simple(emit, fyewt_indicator, '0' + fy_emit_indent(emit));
		explicit_chomp = true;
	}

	if (fy_atom_style_is_block(atom->style) && atom->chomp_explicit) {
		/* atom was parsed as a block scalar; trust the stored chomp */
		switch ((enum fy_atom_chomp)atom->chomp) {
		case FYAC_STRIP:
			emit->flags &= ~FYEF_OPEN_ENDED;
			chomp = '-';
			break;
		case FYAC_KEEP:
			emit->flags |= FYEF_OPEN_ENDED;
			chomp = '+';
			break;
		case FYAC_CLIP:
		default:
			emit->flags &= ~FYEF_OPEN_ENDED;
			break;
		}
	} else {
		/* atom was not a block scalar; derive chomp from content */
		if (!atom->ends_with_lb) {
			emit->flags &= ~FYEF_OPEN_ENDED;
			chomp = '-';
		} else if (atom->trailing_lb) {
			emit->flags |= FYEF_OPEN_ENDED;
			chomp = '+';
		} else {
			emit->flags &= ~FYEF_OPEN_ENDED;
		}
	}

out:
	if (chomp)
		fy_emit_putc_simple(emit, fyewt_indicator, chomp);
	*chompp = chomp;
	return explicit_chomp;
}

void fy_emit_token_write_literal(struct fy_emitter *emit, struct fy_token *fyt, int flags, int indent)
{
	bool breaks;
	int c;
	char chomp;
	struct fy_atom *atom;
	struct fy_atom_iter iter;

	fy_emit_write_indicator(emit, di_bar, flags, indent, fyewt_indicator);

	fy_emit_token_write_block_hints(emit, fyt, flags, indent, &chomp);
	if (flags & DDNF_ROOT)
		indent += fy_emit_indent(emit);

	fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
	emit->flags |= FYEF_WHITESPACE | FYEF_INDENTATION;

	atom = fy_token_atom(fyt);
	if (!atom)
		goto out;

	breaks = true;

	fy_atom_iter_start(atom, &iter);
	fy_emit_accum_start(&emit->ea, emit->column, fy_token_atom_lb_mode(fyt));
	while ((c = fy_atom_iter_utf8_get(&iter)) > 0) {

		if (breaks) {
			fy_emit_write_indent(emit, indent);
			fy_emit_output_col_sync(emit, &emit->ea);
			breaks = false;
		}

		if (fy_is_lb_m(c, fy_token_atom_lb_mode(fyt))) {
			fy_emit_output_accum(emit, fyewt_literal_scalar, &emit->ea);
			emit->flags &= ~FYEF_INDENTATION;
			breaks = true;
		} else
			fy_emit_accum_utf8_put(&emit->ea, c);
	}
	fy_emit_output_accum(emit, fyewt_literal_scalar, &emit->ea);
	fy_emit_accum_finish(&emit->ea);
	fy_atom_iter_finish(&iter);

out:
	emit->flags &= ~FYEF_INDENTATION;
}

static inline bool fy_emit_can_use_original_folded_breaks(struct fy_emitter *emit,
                                                          struct fy_atom *atom)
{
	return fy_emit_is_original_mode(emit) && atom->fyi &&
	       atom->chomp_explicit &&
	       (atom->style & ~FYAS_MANUAL_MARK) == FYAS_FOLDED;
}

static void fy_emit_token_write_folded_original(struct fy_emitter *emit,
                                                struct fy_token *fyt, struct fy_atom *atom, int indent)
{
	struct fy_atom_raw_line_iter rliter;
	const struct fy_raw_line *rl;
	int w;

	fy_atom_raw_line_iter_start(atom, &rliter);
	fy_emit_accum_start(&emit->ea, emit->column, fy_token_atom_lb_mode(fyt));

	const unsigned int orig_indent = atom->increment;

	while ((rl = fy_atom_raw_line_iter_next(&rliter)) != NULL) {

		if (rl->content_len == 0) {
			/* blank line: emit newline only */
			fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
			emit->flags |= FYEF_WHITESPACE | FYEF_INDENTATION;
			continue;
		}

		/* content line: write indent, content, then newline */
		fy_emit_write_indent(emit, indent);
		fy_emit_output_col_sync(emit, &emit->ea);

		const char *s = rl->content_start;
		const char *e = s + rl->content_len;

		/* skip the block scalar's base indentation */
		{
			unsigned int skip = orig_indent;
			while (skip > 0 && s < e &&
			       (!atom->tabsize ? (*s == ' ') : fy_utf8_is_ws((unsigned char)*s))) {
				s++;
				skip--;
			}
		}

		while (s < e) {
			const int c = fy_utf8_get(s, (size_t) (e - s), &w);
			if (c <= 0)
				break;
			fy_emit_accum_utf8_put(&emit->ea, c);
			s += w;
		}
		fy_emit_output_accum(emit, fyewt_folded_scalar, &emit->ea);
		fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
		emit->flags |= FYEF_WHITESPACE | FYEF_INDENTATION;
	}

	fy_emit_accum_finish(&emit->ea);
	fy_atom_raw_line_iter_finish(&rliter);
}

void fy_emit_token_write_folded(struct fy_emitter *emit, struct fy_token *fyt, int flags, int indent)
{
	bool leading_spaces, breaks;
	int c, nrbreaks, nrbreakslim;
	char chomp;
	struct fy_atom *atom;
	struct fy_atom_iter iter;

	fy_emit_write_indicator(emit, di_greater, flags, indent, fyewt_indicator);

	fy_emit_token_write_block_hints(emit, fyt, flags, indent, &chomp);
	if (flags & DDNF_ROOT)
		indent += fy_emit_indent(emit);

	fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
	emit->flags |= FYEF_WHITESPACE | FYEF_INDENTATION;

	atom = fy_token_atom(fyt);
	if (!atom)
		return;

	/* in original mode with a parsed-as-folded atom, preserve original line breaks;
	 * chomp_explicit distinguishes parsed block scalars from event-created atoms */
	if (fy_emit_can_use_original_folded_breaks(emit, atom)) {
		fy_emit_token_write_folded_original(emit, fyt, atom, indent);
		return;
	}

	breaks = true;
	leading_spaces = true;

	fy_atom_iter_start(atom, &iter);
	fy_emit_accum_start(&emit->ea, emit->column, fy_token_atom_lb_mode(fyt));
	while ((c = fy_atom_iter_utf8_get(&iter)) > 0) {

		if (fy_is_lb_m(c, fy_token_atom_lb_mode(fyt))) {

			/* output run */
			if (!fy_emit_accum_empty(&emit->ea)) {
				fy_emit_output_accum(emit, fyewt_literal_scalar, &emit->ea);
				/* do not output a newline (indent) if at the end or
				 * this is a leading spaces line */
				if (!fy_is_z(fy_atom_iter_utf8_peek(&iter)) && !leading_spaces) {
					fy_emit_write_indent(emit, indent);
					fy_emit_output_col_sync(emit, &emit->ea);
				}
			}

			/* count the number of consecutive breaks */
			nrbreaks = 1;
			while (fy_is_lb_m((c = fy_atom_iter_utf8_peek(&iter)), fy_token_atom_lb_mode(fyt))) {
				nrbreaks++;
				(void)fy_atom_iter_utf8_get(&iter);
			}

			/* NOTE: Because the number of indents is tricky
			 * if it's a non blank, non end, it's the number of breaks
			 * if it's a blank, it's the number of breaks minus 1
			 * if it's the end, it's the number of breaks minus 2
			 */
			nrbreakslim = fy_is_z(c) ? 2 : fy_is_blank(c) ? 1 : 0;
			while (nrbreaks-- > nrbreakslim) {
				emit->flags &= ~FYEF_INDENTATION;
				fy_emit_write_indent(emit, indent);
				fy_emit_output_col_sync(emit, &emit->ea);
			}

			breaks = true;

		} else {

			/* if we had a break, output an indent */
			if (breaks) {
				fy_emit_write_indent(emit, indent);
				fy_emit_output_col_sync(emit, &emit->ea);

				/* if this line starts with whitespace we need to know */
				leading_spaces = fy_is_ws(c);
			}

			if (!breaks && fy_is_space(c) &&
			    !fy_is_space(fy_atom_iter_utf8_peek(&iter)) &&
			    fy_emit_accum_column(&emit->ea) > fy_emit_width(emit)) {
				fy_emit_output_accum(emit, fyewt_folded_scalar, &emit->ea);
				emit->flags &= ~FYEF_INDENTATION;
				fy_emit_write_indent(emit, indent);
				fy_emit_output_col_sync(emit, &emit->ea);
			} else
				fy_emit_accum_utf8_put(&emit->ea, c);

			breaks = false;
		}
	}
	fy_emit_output_accum(emit, fyewt_folded_scalar, &emit->ea);
	fy_emit_accum_finish(&emit->ea);
	fy_atom_iter_finish(&iter);
}

static enum fy_node_style
fy_emit_token_scalar_style(struct fy_emitter *emit, struct fy_token *fyt,
			   int flags, int indent, enum fy_node_style style,
			   struct fy_token *fyt_tag)
{
	const struct fy_token_analysis *ta = NULL;
	bool json, flow, is_null_scalar, is_json_plain;
	struct fy_atom *atom;
	const char *tag;
	size_t tag_len;

	atom = fy_token_atom(fyt);

	flow = fy_emit_is_flow_mode(emit) || (flags & DDNF_FLOW);

	/* check if style is allowed (i.e. no block styles in flow context) */
	if (flow && (style == FYNS_LITERAL || style == FYNS_FOLDED))
		style = FYNS_ANY;

	json = fy_emit_is_json_mode(emit);

	is_null_scalar = !atom || (fyt->type == FYTT_SCALAR && fyt->scalar.is_null);

	/* is this a plain json atom? */
	is_json_plain = (json || emit->source_json || fy_emit_is_dejson_mode(emit)) &&
			(is_null_scalar ||
			!fy_atom_strcmp(atom, "false") ||
			!fy_atom_strcmp(atom, "true") ||
			!fy_atom_strcmp(atom, "null") ||
			fy_atom_is_number(atom));

	if (json) {
		/* literal in JSON mode is output as quoted */
		if (style == FYNS_LITERAL || style == FYNS_FOLDED)
			return FYNS_DOUBLE_QUOTED;

		if (is_json_plain) {
			tag = fy_token_get_text(fyt_tag, &tag_len);

			/* XXX hardcoded string tag resultion */
			if (tag && tag_len &&
			     ((tag_len == 1 && *tag == '!') ||
			      (tag_len == 21 && !memcmp(tag, "tag:yaml.org,2002:str", 21))))
				return FYNS_DOUBLE_QUOTED;
		}

		/* JSON NULL, but with plain style */
		if ((style == FYNS_PLAIN || style == FYNS_ANY) &&
		    (is_null_scalar || (is_json_plain && !atom->size0)))
			return FYNS_PLAIN;

		return FYNS_DOUBLE_QUOTED;
	}

	ta = fy_token_text_analyze(fyt);

	/* if the style is block and we're a simple scalar key, this is not going to work */
	if ((style == FYNS_LITERAL || style == FYNS_FOLDED) && (flags & DDNF_SIMPLE_SCALAR_KEY))
		style = (ta->flags & FYTTAF_CAN_BE_PLAIN) ? FYNS_PLAIN : FYNS_DOUBLE_QUOTED;

	if (flow && (style == FYNS_ANY || style == FYNS_LITERAL || style == FYNS_FOLDED)) {

		/* if there's a linebreak, or ends in a colon, use double quoted style */
		if (ta->flags & (FYTTAF_HAS_ANY_LB | FYTTAF_ENDS_WITH_COLON)) {
			style = FYNS_DOUBLE_QUOTED;
			goto out;
		}

		/* anything not empty is double quoted here */
		style = (ta->flags & FYTTAF_EMPTY) ? FYNS_PLAIN :
			((ta->flags & FYTTAF_CAN_BE_PLAIN) ? FYNS_PLAIN : FYNS_DOUBLE_QUOTED);
	}

	/* try to pretify */
	if (!flow && fy_emit_is_pretty_mode(emit) &&
		(style == FYNS_ANY || style == FYNS_DOUBLE_QUOTED || style == FYNS_SINGLE_QUOTED)) {

		/* any original style can be a plain, but contains linebreaks, do a literal */
		if ((ta->flags & (FYTTAF_CAN_BE_PLAIN | FYTTAF_HAS_LB)) == (FYTTAF_CAN_BE_PLAIN | FYTTAF_HAS_LB)) {
			style = FYNS_LITERAL;
			goto out;
		}

		/* any style, can be just a plain, just make it so */
		if ((ta->flags & (FYTTAF_CAN_BE_PLAIN | FYTTAF_HAS_LB)) == FYTTAF_CAN_BE_PLAIN) {
			style = FYNS_PLAIN;
			goto out;
		}
	}

	if (!flow && emit->source_json && fy_emit_is_dejson_mode(emit)) {
		if (is_json_plain || (ta->flags & (FYTTAF_CAN_BE_PLAIN | FYTTAF_HAS_LB)) == FYTTAF_CAN_BE_PLAIN) {
			style = FYNS_PLAIN;
			goto out;
		}
	}

out:
	/* any zero (or non newline linebreak) -> double quoted */
	if (ta->flags & (FYTTAF_HAS_ZERO | FYTTAF_HAS_NON_NL_LB))
		style = FYNS_DOUBLE_QUOTED;

	if (style == FYNS_ANY && (ta->flags & FYTTAF_CAN_BE_PLAIN)) {
		if (!flow || (ta->flags & FYTTAF_CAN_BE_PLAIN_FLOW))
			style = FYNS_PLAIN;
	}

	if (style == FYNS_PLAIN) {
		if (flow && !(ta->flags & FYTTAF_CAN_BE_PLAIN_FLOW))
			style = (ta->flags & FYTTAF_CAN_BE_SINGLE_QUOTED) ? FYNS_SINGLE_QUOTED : FYNS_DOUBLE_QUOTED;

		/* plains in flow mode not being able to be plains
		 * - plain in block mode that can't be plain in flow mode
		 * - special handling for plains on start of line
		 */
		if ((flow && !(ta->flags & FYTTAF_CAN_BE_PLAIN_FLOW) && !(ta->flags & FYTTAF_CAN_BE_SIMPLE_KEY) && !is_null_scalar) ||
		    ((ta->flags & FYTTAF_QUOTE_AT_0) && indent == 0))
			style = FYNS_DOUBLE_QUOTED;

		/* make it double quoted, but only if not already over the width (and contains a space/lb) */
		if (style == FYNS_PLAIN && (ta->flags & (FYTTAF_HAS_LB | FYTTAF_HAS_WS)) &&
			emit->column < fy_emit_width(emit) && (emit->column + ta->maxspan) > fy_emit_width(emit))
			style = FYNS_DOUBLE_QUOTED;

		if (style == FYNS_PLAIN && !(ta->flags & FYTTAF_CAN_BE_PLAIN)) {
			style = FYNS_DOUBLE_QUOTED;
		}
	}

	if (style == FYNS_ANY && (ta->flags & FYTTAF_CAN_BE_SINGLE_QUOTED))
		style = FYNS_SINGLE_QUOTED;

	if (style == FYNS_SINGLE_QUOTED && !(ta->flags & FYTTAF_CAN_BE_SINGLE_QUOTED))
		style = FYNS_DOUBLE_QUOTED;

	if (style == FYNS_ANY && (ta->flags & FYTTAF_CAN_BE_DOUBLE_QUOTED))
		style = FYNS_DOUBLE_QUOTED;

	/* should never happen, but still */
	if (style == FYNS_ANY)
		style = FYNS_DOUBLE_QUOTED;

	return style;
}

void fy_emit_token_scalar(struct fy_emitter *emit, struct fy_token *fyt, int flags, int indent,
			  enum fy_node_style style, struct fy_token *fyt_tag)
{
	struct fy_emit_save_ctx *sc = &emit->s_sc;

	assert(style != FYNS_FLOW && style != FYNS_BLOCK);

	if (sc->flags & DDNF_HANGING_INDENT)
		fy_emit_write_indent(emit, sc->indent);

	/* root scalar with a comment needs special handling */
	if ((flags & DDNF_ROOT) && fy_emit_token_has_comment(emit, fyt, fycp_top))
		fy_emit_token_comment(emit, fyt, flags, indent, fycp_top);

	indent = fy_emit_increase_indent(emit, flags, indent);

	if (!fy_emit_whitespace(emit))
		fy_emit_write_ws(emit);

	style = fy_emit_token_scalar_style(emit, fyt, flags, indent, style, fyt_tag);

	switch (style) {
	case FYNS_ALIAS:
		fy_emit_token_write_alias(emit, fyt, flags, indent);
		break;
	case FYNS_PLAIN:
		fy_emit_token_write_plain(emit, fyt, flags, indent);
		break;
	case FYNS_DOUBLE_QUOTED:
		fy_emit_token_write_quoted(emit, fyt, flags, indent, '"');
		break;
	case FYNS_SINGLE_QUOTED:
		fy_emit_token_write_quoted(emit, fyt, flags, indent, '\'');
		break;
	case FYNS_LITERAL:
		fy_emit_token_write_literal(emit, fyt, flags, indent);
		break;
	case FYNS_FOLDED:
		fy_emit_token_write_folded(emit, fyt, flags, indent);
		break;
	default:
		break;
	}

	/* root scalar with a comment needs special handling */
	if ((flags & DDNF_ROOT) && fy_emit_token_has_comment(emit, fyt, fycp_right)) {
		fy_emit_token_comment(emit, fyt, flags, indent, fycp_right);
		sc->flags |= DDNF_HANGING_INDENT;
	}
}

void fy_emit_scalar(struct fy_emitter *emit, struct fy_node *fyn, int flags, int indent, bool is_key)
{
	enum fy_node_style style;

	/* default style */
	style = fyn ? fyn->style : FYNS_ANY;

	/* all JSON keys are double quoted */
	if (fy_emit_is_json_mode(emit) && is_key)
		style = FYNS_DOUBLE_QUOTED;

	fy_emit_token_scalar(emit,
			fyn ? fyn->scalar : NULL,
			flags, indent,
			style, fyn->tag);
}

static void fy_emit_sequence_prolog(struct fy_emitter *emit, struct fy_emit_save_ctx *sc,
				    struct fy_token *fyt)
{
	bool json = fy_emit_is_json_mode(emit);
	bool was_flow = sc->flow;

	/* skip top comment if parent sequence_item_prolog already emitted it */
	if (!(sc->flags & DDNF_SEQ) && fy_emit_token_has_comment(emit, fyt, fycp_top)) {
		fy_emit_token_comment(emit, fyt, sc->flags, sc->indent, fycp_top);
		sc->flags |= DDNF_HANGING_INDENT;
	}

	sc->old_indent = sc->indent;
	if (!json) {
		if (fy_emit_is_manual(emit))
			sc->flow = (sc->xstyle == FYNS_BLOCK && was_flow) || sc->xstyle == FYNS_FLOW;
		else if (fy_emit_is_block_mode(emit))
			sc->flow = sc->empty;
		else
			sc->flow = fy_emit_is_flow_mode(emit) || emit->flow_level || sc->flow_token || sc->empty;
	} else
		sc->flow = true;

	if (sc->flow) {
		if (!emit->flow_level) {
			sc->indent = fy_emit_increase_indent(emit, sc->flags, sc->indent);
			sc->old_indent = sc->indent;
		}

		sc->flags = (sc->flags | DDNF_FLOW) | (sc->flags & ~DDNF_INDENTLESS);
		fy_emit_write_indicator(emit, di_left_bracket, sc->flags, sc->indent, fyewt_indicator);

		/* we need an indent afterward if not compact */
		if (!fy_emit_sc_oneline(emit, sc))
			sc->flags |= DDNF_HANGING_INDENT;
	} else {
		sc->flags = (sc->flags & ~DDNF_FLOW);
	}

	if (!fy_emit_sc_oneline(emit, sc)) {
		if (was_flow || (sc->flags & (DDNF_ROOT | DDNF_SEQ))
		    || ((sc->flags & DDNF_MAP)
			&& (emit->xcfg.xflags & FYEXCF_INDENTED_SEQ_IN_MAP)))
			sc->indent = fy_emit_increase_indent(emit, sc->flags, sc->indent);
	}

	sc->flags &= ~DDNF_ROOT;
}

static void fy_emit_sequence_epilog(struct fy_emitter *emit, struct fy_emit_save_ctx *sc)
{
	if (sc->flow && (sc->flags & DDNF_HANGING_INDENT) && !fy_emit_sc_oneline(emit, sc) && !sc->empty)
		fy_emit_write_indent(emit, sc->old_indent);

	if (sc->flow || fy_emit_is_json_mode(emit)) {
		fy_emit_write_indicator(emit, di_right_bracket, sc->flags, sc->old_indent, fyewt_indicator);
	}
}

static void fy_emit_sequence_item_prolog(struct fy_emitter *emit, struct fy_emit_save_ctx *sc,
					 struct fy_token *fyt_value)
{
	bool has_comment;

	sc->flags |= DDNF_SEQ;

	has_comment = fy_emit_token_has_comment(emit, fyt_value, fycp_top);

	if (!fy_emit_sc_oneline(emit, sc) ||
	    ((fy_emit_is_compact(emit) || sc->flow) && emit->column >= fy_emit_width(emit)))
		fy_emit_write_indent(emit, sc->indent);

	if (has_comment) {
		fy_emit_token_comment(emit, fyt_value, sc->flags, sc->indent, fycp_top);
		sc->flags |= DDNF_HANGING_INDENT;
	}

	if (!sc->flow && !fy_emit_is_json_mode(emit))
		fy_emit_write_indicator(emit, di_dash, sc->flags, sc->indent, fyewt_indicator);
}

static void fy_emit_sequence_item_epilog(struct fy_emitter *emit, struct fy_emit_save_ctx *sc,
					 bool last, struct fy_token *fyt_value)
{
	bool needs_hanging_indent;

	if ((sc->flow || fy_emit_is_json_mode(emit)) && !last)
		fy_emit_write_indicator(emit, di_comma, sc->flags, sc->indent, fyewt_indicator);

	if (fy_emit_token_has_comment(emit, fyt_value, fycp_right)) {
		fy_emit_token_comment(emit, fyt_value, sc->flags, sc->indent, fycp_right);
		sc->flags |= DDNF_HANGING_INDENT;
	}

	needs_hanging_indent = sc->flow && !fy_emit_sc_oneline(emit, sc) && !sc->empty;

	if (last && needs_hanging_indent && (sc->flags & DDNF_HANGING_INDENT))
		fy_emit_write_indent(emit, sc->old_indent);
	else if (needs_hanging_indent)
		sc->flags |= DDNF_HANGING_INDENT;

	sc->flags &= ~DDNF_SEQ;
}

void fy_emit_sequence(struct fy_emitter *emit, struct fy_node *fyn, int flags, int indent)
{
	struct fy_node *fyni, *fynin;
	struct fy_token *fyt_value;
	bool last;
	struct fy_emit_save_ctx sct, *sc = &sct;

	memset(sc, 0, sizeof(*sc));

	sc->flags = flags;
	sc->indent = indent;
	sc->empty = fy_node_list_empty(&fyn->sequence);
	sc->flow_token = fyn->style == FYNS_FLOW;
	sc->oneline_flow = false;
	if (sc->flow_token && fy_emit_preserve_flow_layout(emit)) {
		const struct fy_mark *sm = fy_token_start_mark(fyn->sequence_start);
		const struct fy_mark *em = fy_token_end_mark(fyn->sequence_end);
		if (sm && em && sm->line == em->line)
			sc->oneline_flow = true;
	}
	sc->flow = !!(flags & DDNF_FLOW);
	sc->xstyle = fyn->style;
	sc->old_indent = sc->indent;

	fy_emit_sequence_prolog(emit, sc, fyn->sequence_start);

	for (fyni = fy_node_list_head(&fyn->sequence); fyni; fyni = fynin) {

		fynin = fy_node_next(&fyn->sequence, fyni);
		last = !fynin;
		fyt_value = fy_node_value_token(fyni);

		fy_emit_sequence_item_prolog(emit, sc, fyt_value);

		fy_emit_node_internal(emit, fyni, sc->flags & ~DDNF_ROOT, sc->indent, false);
		fy_emit_sequence_item_epilog(emit, sc, last, fyt_value);
	}

	fy_emit_sequence_epilog(emit, sc);

	/* emit right-comment attached to the closing bracket token */
	if (fy_emit_token_has_comment(emit, fyn->sequence_end, fycp_right))
		fy_emit_token_comment(emit, fyn->sequence_end, sc->flags, sc->indent, fycp_right);
}

static void fy_emit_mapping_prolog(struct fy_emitter *emit, struct fy_emit_save_ctx *sc,
				   struct fy_token *fyt)
{
	bool json = fy_emit_is_json_mode(emit);
	bool was_flow = sc->flow;

	/* skip top comment if parent sequence_item_prolog already emitted it */
	if (!(sc->flags & DDNF_SEQ) && fy_emit_token_has_comment(emit, fyt, fycp_top)) {
		fy_emit_token_comment(emit, fyt, sc->flags, sc->indent, fycp_top);
		sc->flags |= DDNF_HANGING_INDENT;
	}

	sc->old_indent = sc->indent;
	if (!json) {
		if (fy_emit_is_manual(emit))
			sc->flow = (sc->xstyle == FYNS_BLOCK && was_flow) || sc->xstyle == FYNS_FLOW;
		else if (fy_emit_is_block_mode(emit))
			sc->flow = sc->empty;
		else
			sc->flow = fy_emit_is_flow_mode(emit) || emit->flow_level || sc->flow_token || sc->empty;
	} else
		sc->flow = true;

	if (sc->flow) {
		if (!emit->flow_level) {
			sc->indent = fy_emit_increase_indent(emit, sc->flags, sc->indent);
			sc->old_indent = sc->indent;
		}

		sc->flags = (sc->flags | DDNF_FLOW) | (sc->flags & ~DDNF_INDENTLESS);
		fy_emit_write_indicator(emit, di_left_brace, sc->flags, sc->indent, fyewt_indicator);

		/* we need an indent afterward if not compact */
		if (!fy_emit_sc_oneline(emit, sc))
			sc->flags |= DDNF_HANGING_INDENT;
	} else {
		sc->flags &= ~(DDNF_FLOW | DDNF_INDENTLESS);
	}

	if (!fy_emit_sc_oneline(emit, sc) && !sc->empty)
		sc->indent = fy_emit_increase_indent(emit, sc->flags, sc->indent);

	sc->flags &= ~DDNF_ROOT;
}

static void fy_emit_mapping_epilog(struct fy_emitter *emit, struct fy_emit_save_ctx *sc)
{
	if (sc->flow || fy_emit_is_json_mode(emit)) {
		if ((sc->flags & DDNF_HANGING_INDENT) && !fy_emit_sc_oneline(emit, sc) && !sc->empty)
			fy_emit_write_indent(emit, sc->old_indent);
		fy_emit_write_indicator(emit, di_right_brace, sc->flags, sc->old_indent, fyewt_indicator);
	}
}

static void fy_emit_mapping_key_prolog(struct fy_emitter *emit, struct fy_emit_save_ctx *sc,
				       struct fy_token *fyt_key, bool simple_key)
{
	const struct fy_token_analysis *ta;
	bool do_indent, key_over, has_comment;

	sc->flags = DDNF_MAP | (sc->flags & DDNF_FLOW);

	if (!fy_emit_sc_oneline(emit, sc) ||
	    ((fy_emit_is_compact(emit) || sc->flow) && emit->column >= fy_emit_width(emit)))
		fy_emit_write_indent(emit, sc->indent);

	/* emit top comment on key token (interstitial mapping comment) */
	if (!sc->flow && fy_emit_token_has_comment(emit, fyt_key, fycp_top)) {
		fy_emit_token_comment(emit, fyt_key, sc->flags, sc->indent, fycp_top);
		sc->flags |= DDNF_HANGING_INDENT;
	}

	/* if we have a right comment then it's by a definition not a simple key */
	has_comment = fy_emit_token_has_comment(emit, fyt_key, fycp_right);
	if (simple_key /* && !has_comment */ ) {
		sc->flags |= DDNF_SIMPLE;
		if (fyt_key && fyt_key->type == FYTT_SCALAR)
			sc->flags |= DDNF_SIMPLE_SCALAR_KEY;
	} else {
		/* do not emit the ? in flow modes at all */
		if (fy_emit_is_flow_mode(emit))
			sc->flags |= DDNF_SIMPLE;
	}

	do_indent = false;
	if (!has_comment && !fy_emit_sc_oneline(emit, sc)) {
		if (fyt_key && fyt_key->type != FYTT_SCALAR)
			/* always indent on non scalar keys */
			key_over = true;
		else {
			/* for scalar keys, check if key + 2 + quotes go over */
			ta = fy_token_text_analyze(fyt_key);
			key_over = (emit->column + ta->maxspan + 2 + (!simple_key ? 2 : 0)) >= fy_emit_width(emit);
		}

		do_indent = key_over;
		if (!do_indent && !fy_emit_is_compact(emit))
			do_indent = !sc->flow || (sc->flags & DDNF_HANGING_INDENT);
	} else
		do_indent = false;

	if (do_indent)
		fy_emit_write_indent(emit, sc->indent);

	/* complex? */
	if (!(sc->flags & DDNF_SIMPLE)) {
		fy_emit_write_indicator(emit, di_question_mark, sc->flags, sc->indent, fyewt_indicator);
	}
}

static void fy_emit_mapping_key_epilog(struct fy_emitter *emit, struct fy_emit_save_ctx *sc,
				       struct fy_token *fyt_key)
{
	/* if the key is an alias, always output an extra whitespace */
	if (fyt_key && fyt_key->type == FYTT_ALIAS)
		fy_emit_write_ws(emit);

	sc->flags &= ~DDNF_MAP;

	if (!(sc->flags & DDNF_SIMPLE)) {

		if (fy_emit_token_has_comment(emit, fyt_key, fycp_right)) {
			fy_emit_token_comment(emit, fyt_key, sc->flags, sc->indent, fycp_right);
			sc->flags |= DDNF_HANGING_INDENT;
		}

		if (!emit->flow_level && !fy_emit_is_oneline_or_compact(emit))
			fy_emit_write_indent(emit, sc->indent);
		if (!fy_emit_whitespace(emit))
			fy_emit_write_ws(emit);
	} else {
	}

	fy_emit_write_indicator(emit, di_colon, sc->flags, sc->indent, fyewt_indicator);

	sc->flags &= ~DDNF_HANGING_INDENT;
	if ((sc->flags & DDNF_SIMPLE) && fy_emit_token_has_comment(emit, fyt_key, fycp_right)) {
		fy_emit_token_comment(emit, fyt_key, sc->flags, sc->indent, fycp_right);
		sc->flags |= DDNF_HANGING_INDENT;
	}

	sc->flags = DDNF_MAP | (sc->flags & (DDNF_FLOW | DDNF_HANGING_INDENT));
}

static void fy_emit_mapping_value_prolog(struct fy_emitter *emit, struct fy_emit_save_ctx *sc,
					 struct fy_token *fyt_value)
{
	const struct fy_token_analysis *ta;
	bool value_over;

	if (sc->flags & DDNF_HANGING_INDENT)
		fy_emit_write_indent(emit, sc->indent);

	/* don't do anything for those cases */
	if (!sc->flow || fy_emit_sc_oneline(emit, sc) || !fyt_value || fyt_value->type != FYTT_SCALAR)
		return;

	ta = fy_token_text_analyze(fyt_value);
	value_over = (emit->column + ta->maxspan) >= fy_emit_width(emit);

	if (value_over) {
		sc->indent = fy_emit_increase_indent(emit, sc->flags, sc->indent);
		fy_emit_write_indent(emit, sc->indent);
	}
}

static void fy_emit_mapping_value_epilog(struct fy_emitter *emit, struct fy_emit_save_ctx *sc,
					 bool last, struct fy_token *fyt_value)
{
	bool needs_hanging_indent;

	if ((sc->flow || fy_emit_is_json_mode(emit)) && !last)
		fy_emit_write_indicator(emit, di_comma, sc->flags, sc->indent, fyewt_indicator);

	if (fy_emit_token_has_comment(emit, fyt_value, fycp_right)) {
		fy_emit_token_comment(emit, fyt_value, sc->flags, sc->indent, fycp_right);
		sc->flags |= DDNF_HANGING_INDENT;
	}

	needs_hanging_indent = sc->flow && !fy_emit_sc_oneline(emit, sc) && !sc->empty;

	if (last && needs_hanging_indent && (sc->flags & DDNF_HANGING_INDENT))
		fy_emit_write_indent(emit, sc->old_indent);
	else if (needs_hanging_indent)
		sc->flags |= DDNF_HANGING_INDENT;

	sc->flags &= ~DDNF_MAP;
}

void fy_emit_mapping(struct fy_emitter *emit, struct fy_node *fyn, int flags, int indent)
{
	struct fy_node_pair *fynp, *fynpn, **fynpp = NULL;
	struct fy_token *fyt_key, *fyt_value;
	bool last, simple_key, used_malloc = false;
	const struct fy_token_analysis *tak = NULL, *tav;
	int i, count;
	struct fy_emit_save_ctx sct, *sc = &sct;

	memset(sc, 0, sizeof(*sc));

	sc->flags = flags;
	sc->indent = indent;
	sc->empty = fy_node_pair_list_empty(&fyn->mapping);
	sc->flow_token = fyn->style == FYNS_FLOW;
	sc->oneline_flow = false;
	if (sc->flow_token && fy_emit_preserve_flow_layout(emit)) {
		const struct fy_mark *sm = fy_token_start_mark(fyn->mapping_start);
		const struct fy_mark *em = fy_token_end_mark(fyn->mapping_end);
		if (sm && em && sm->line == em->line)
			sc->oneline_flow = true;
	}
	sc->flow = !!(flags & DDNF_FLOW);
	sc->xstyle = fyn->style;
	sc->old_indent = sc->indent;

	fy_emit_mapping_prolog(emit, sc, fyn->mapping_start);

	if (!(emit->xcfg.cfg.flags & (FYECF_SORT_KEYS | FYECF_STRIP_EMPTY_KV))) {
		fynp = fy_node_pair_list_head(&fyn->mapping);
		fynpp = NULL;
	} else {
		count = fy_node_mapping_item_count(fyn);

		/* heuristic, avoid allocation for small maps */
		if (count > 64) {
			fynpp = malloc((count + 1) * sizeof(*fynpp));
			fyd_error_check(fyn->fyd, fynpp, err_out,
					"malloc() failed");
			used_malloc = true;
		} else
			fynpp = alloca((count + 1) * sizeof(*fynpp));

		/* fill (removing empty KVs) */
		i = 0;
		for (fynp = fy_node_pair_list_head(&fyn->mapping); fynp;
				fynp = fy_node_pair_next(&fyn->mapping, fynp)) {

			/* strip key/value pair from the output if it's empty */
			if ((emit->xcfg.cfg.flags & FYECF_STRIP_EMPTY_KV) && fy_node_is_empty(fynp->value))
				continue;

			fynpp[i++] = fynp;
		}
		count = i;
		fynpp[count] = NULL;

		/* sort the keys */
		if (emit->xcfg.cfg.flags & FYECF_SORT_KEYS)
			fy_node_mapping_perform_sort(fyn, NULL, NULL, fynpp, count);

		i = 0;
		fynp = fynpp[i];
	}

	for (; fynp; fynp = fynpn) {

		if (!fynpp)
			fynpn = fy_node_pair_next(&fyn->mapping, fynp);
		else
			fynpn = fynpp[++i];

		last = !fynpn;
		fyt_key = fy_node_value_token(fynp->key);
		fyt_value = fy_node_value_token(fynp->value);

		FYD_NODE_ERROR_CHECK(fynp->fyd, fynp->key, FYEM_INTERNAL,
				!fy_emit_is_json_mode(emit) ||
					(fynp->key && fynp->key->type == FYNT_SCALAR),
					err_out, "Non scalar keys are not allowed in JSON emit mode");

		simple_key = false;
		if (fynp->key) {
			switch (fynp->key->type) {
			case FYNT_SCALAR:
				tak = fy_token_text_analyze(fynp->key->scalar);
				simple_key = fy_emit_is_json_mode(emit) ||
					     !!(tak->flags & FYTTAF_CAN_BE_SIMPLE_KEY);
				break;
			case FYNT_SEQUENCE:
				simple_key = fy_node_list_empty(&fynp->key->sequence);
				break;
			case FYNT_MAPPING:
				simple_key = fy_node_pair_list_empty(&fynp->key->mapping);
				break;
			}
		}

		/* special handling for compact mode simple key + scalar value*/
		if (fy_emit_is_compact(emit) && simple_key &&
		    fynp->value && fynp->value->type == FYNT_SCALAR && tak) {
			tav = fy_token_text_analyze(fynp->value->scalar);

			/* for plain key + value if we go over the width */
			if ((tav->flags & FYTTAF_CAN_BE_PLAIN) &&
			   (emit->column + tak->maxspan + 2 + tav->maxspan + 1) >= fy_emit_width(emit))
				fy_emit_write_indent(emit, sc->indent);
		}

		fy_emit_mapping_key_prolog(emit, sc, fyt_key, simple_key);
		if (fynp->key)
			fy_emit_node_internal(emit, fynp->key, (sc->flags & ~DDNF_ROOT), sc->indent, true);
		fy_emit_mapping_key_epilog(emit, sc, fyt_key);

		fy_emit_mapping_value_prolog(emit, sc, fyt_value);
		if (fynp->value)
			fy_emit_node_internal(emit, fynp->value, (sc->flags & ~DDNF_ROOT), sc->indent, false);
		fy_emit_mapping_value_epilog(emit, sc, last, fyt_value);
	}

	fy_emit_mapping_epilog(emit, sc);

	/* emit right-comment attached to the closing brace token */
	if (fy_emit_token_has_comment(emit, fyn->mapping_end, fycp_right))
		fy_emit_token_comment(emit, fyn->mapping_end, sc->flags, sc->indent, fycp_right);

err_out:
	if (fynpp && used_malloc)
		free(fynpp);
	return;
}

int fy_emit_common_document_start(struct fy_emitter *emit,
				  struct fy_document_state *fyds,
				  bool root_tag_or_anchor)
{
	struct fy_emit_save_ctx *sc = &emit->s_sc;
	struct fy_token *fyt_chk;
	const char *td_handle, *td_prefix;
	size_t td_handle_size, td_prefix_size;
	enum fy_emitter_cfg_flags flags = emit->xcfg.cfg.flags;
	enum fy_emitter_cfg_flags vd_flags = flags & FYECF_VERSION_DIR(FYECF_VERSION_DIR_MASK);
	enum fy_emitter_cfg_flags td_flags = flags & FYECF_TAG_DIR(FYECF_TAG_DIR_MASK);
	enum fy_emitter_cfg_flags dsm_flags = flags & FYECF_DOC_START_MARK(FYECF_DOC_START_MARK_MASK);
	bool vd, td, dsm;
	bool had_non_default_tag = false;
	bool strip_doc = false;

	if (!emit || !fyds || emit->fyds)
		return -1;

	strip_doc = !!(emit->xcfg.cfg.flags & FYECF_STRIP_DOC);

	emit->fyds = fy_document_state_ref(fyds);

	vd = ((vd_flags == FYECF_VERSION_DIR_AUTO && fyds->version_explicit) ||
	       vd_flags == FYECF_VERSION_DIR_ON) &&
	      !strip_doc;
	td = ((td_flags == FYECF_TAG_DIR_AUTO && fyds->tags_explicit) ||
	       td_flags == FYECF_TAG_DIR_ON) &&
	      !strip_doc;

	/* if either a version or directive tags exist, and no previous
	 * explicit document end existed, output one now
	 */
	if (!fy_emit_is_json_mode(emit) && (vd || td) &&
		(emit->flags & (FYEF_HAD_DOCUMENT_END | FYEF_HAD_DOCUMENT_OUTPUT)) ==
			(FYEF_HAD_DOCUMENT_END | FYEF_HAD_DOCUMENT_OUTPUT)) {
		if (emit->column)
			fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
		if (!strip_doc) {
			fy_emit_puts_simple(emit, fyewt_document_indicator, "...");
			emit->flags &= ~FYEF_WHITESPACE;
			emit->flags |= FYEF_HAD_DOCUMENT_END;
		}
	}

	/* top comment */
	if (fy_emit_token_has_comment(emit, fyds->fyt_ds, fycp_top)) {
		if (emit->column)
			fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
		fy_emit_token_comment(emit, fyds->fyt_ds, sc->flags, sc->indent, fycp_top);
	}
	/* right comment */
	if (fy_emit_token_has_comment(emit, fyds->fyt_ds, fycp_right)) {
		if (emit->column)
			fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
		fy_emit_token_comment(emit, fyds->fyt_ds, sc->flags, sc->indent, fycp_right);
		sc->flags |= DDNF_HANGING_INDENT;
	}

	if (!fy_emit_is_json_mode(emit) && vd) {
		if (emit->column)
			fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
		fy_emit_printf(emit, fyewt_version_directive, "%%YAML %d.%d",
					fyds->version.major, fyds->version.minor);
		fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
		emit->flags |= FYEF_WHITESPACE | FYEF_INDENTATION;
	}

	if (!fy_emit_is_json_mode(emit) && td) {

		for (fyt_chk = fy_token_list_first(&fyds->fyt_td); fyt_chk; fyt_chk = fy_token_next(&fyds->fyt_td, fyt_chk)) {

			td_handle = fy_tag_directive_token_handle(fyt_chk, &td_handle_size);
			td_prefix = fy_tag_directive_token_prefix(fyt_chk, &td_prefix_size);
			assert(td_handle && td_prefix);

			if (fy_tag_is_default_internal(td_handle, td_handle_size, td_prefix, td_prefix_size))
				continue;

			had_non_default_tag = true;

			if (emit->column)
				fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
			fy_emit_printf(emit, fyewt_tag_directive, "%%TAG %.*s %.*s",
					(int)td_handle_size, td_handle,
					(int)td_prefix_size, td_prefix);
			fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
			emit->flags |= FYEF_WHITESPACE | FYEF_INDENTATION;
		}
	}

	/* always output document start indicator:
	 * - was explicit
	 * - document has tags
	 * - document has an explicit version
	 * - root exists & has a tag or an anchor
	 */
	dsm = (dsm_flags == FYECF_DOC_START_MARK_AUTO &&
			(!fyds->start_implicit ||
			(fyds->tags_explicit && had_non_default_tag) ||
			fyds->version_explicit )) ||
	       dsm_flags == FYECF_DOC_START_MARK_ON;

	/* if there was previous output without document end */
	if (!dsm && (emit->flags & FYEF_HAD_DOCUMENT_OUTPUT) &&
	           !(emit->flags & FYEF_HAD_DOCUMENT_END))
		dsm = true;

	/* there was *any* document end output (and no document end) */
	if (!dsm && (emit->flags & FYEF_HAD_DOCUMENT_END_OUTPUT) &&
		    !(emit->flags & FYEF_HAD_DOCUMENT_END))
		dsm = true;

	/* output document start indicator if we should */
	if (dsm && !fy_emit_is_json_mode(emit))
		fy_emit_document_start_indicator(emit);

	/* clear that in any case */
	emit->flags &= ~FYEF_HAD_DOCUMENT_END;

	return 0;
}

int fy_emit_document_start(struct fy_emitter *emit, struct fy_document *fyd,
			   struct fy_node *fyn_root)
{
	struct fy_node *root;
	bool root_tag_or_anchor;
	int ret;

	if (!emit || !fyd || !fyd->fyds)
		return -1;

	root = fyn_root ? fyn_root : fy_document_root(fyd);

	root_tag_or_anchor = root && (root->tag || fy_document_lookup_anchor_by_node(fyd, root));

	ret = fy_emit_common_document_start(emit, fyd->fyds, root_tag_or_anchor);
	if (ret)
		return ret;

	emit->fyd = fyd;

	return 0;
}

int fy_emit_common_document_end(struct fy_emitter *emit, bool override_state, bool implicit_override)
{
	const struct fy_document_state *fyds;
	enum fy_emitter_cfg_flags flags = emit->xcfg.cfg.flags;
	enum fy_emitter_cfg_flags dem_flags = flags & FYECF_DOC_END_MARK(FYECF_DOC_END_MARK_MASK);
	bool implicit, dem;

	if (!emit || !emit->fyds)
		return -1;

	fyds = emit->fyds;

	implicit = fyds->end_implicit;
	if (fyds->started_explicit)
		implicit = false;
	else if (override_state)
		implicit = implicit_override;

	dem = ((dem_flags == FYECF_DOC_END_MARK_AUTO && !implicit) ||
		dem_flags == FYECF_DOC_END_MARK_ON) &&
	       !(emit->xcfg.cfg.flags & FYECF_STRIP_DOC);

	if (!(emit->xcfg.cfg.flags & FYECF_NO_ENDING_NEWLINE)) {
		if (emit->column != 0) {
			fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
			emit->flags |= FYEF_WHITESPACE | FYEF_INDENTATION;
		}

		if (!fy_emit_is_json_mode(emit) && dem) {
			fy_emit_puts_simple(emit, fyewt_document_indicator, "...");
			fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
			emit->flags |= FYEF_WHITESPACE | FYEF_INDENTATION;
			emit->flags |= FYEF_HAD_DOCUMENT_END;
		} else
			emit->flags &= ~FYEF_HAD_DOCUMENT_END;
	} else {
		if (!fy_emit_is_json_mode(emit) && dem) {
			if (emit->column != 0) {
				fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
				emit->flags |= FYEF_WHITESPACE | FYEF_INDENTATION;
			}
			fy_emit_puts_simple(emit, fyewt_document_indicator, "...");
			emit->flags &= ~(FYEF_WHITESPACE | FYEF_INDENTATION);
			emit->flags |= FYEF_HAD_DOCUMENT_END;
		} else
			emit->flags &= ~FYEF_HAD_DOCUMENT_END;
	}

	/* mark that we did output a document earlier */
	emit->flags |= FYEF_HAD_DOCUMENT_OUTPUT;

	/* stop our association with the document */
	fy_document_state_unref(emit->fyds);
	emit->fyds = NULL;

	return 0;
}

int fy_emit_document_end(struct fy_emitter *emit)
{
	int ret;

	ret = fy_emit_common_document_end(emit, false, false);
	if (ret)
		return ret;

	emit->fyd = NULL;
	return 0;
}

int fy_emit_common_explicit_document_end(struct fy_emitter *emit)
{
	if (!emit)
		return -1;

	if (emit->column != 0) {
		fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
		emit->flags |= FYEF_WHITESPACE | FYEF_INDENTATION;
	}

	if (!fy_emit_is_json_mode(emit)) {
		fy_emit_puts_simple(emit, fyewt_document_indicator, "...");
		fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
		emit->flags |= FYEF_WHITESPACE | FYEF_INDENTATION;
		emit->flags |= FYEF_HAD_DOCUMENT_END;
	} else
		emit->flags &= ~FYEF_HAD_DOCUMENT_END;

	/* mark that we did output a document earlier */
	emit->flags |= FYEF_HAD_DOCUMENT_OUTPUT;

	/* stop our association with the document */
	fy_document_state_unref(emit->fyds);
	emit->fyds = NULL;

	return 0;
}

int fy_emit_explicit_document_end(struct fy_emitter *emit)
{
	int ret;

	ret = fy_emit_common_explicit_document_end(emit);
	if (ret)
		return ret;

	emit->fyd = NULL;
	return 0;
}

void fy_emit_reset(struct fy_emitter *emit, bool reset_events)
{
	struct fy_eventp *fyep;

	emit->line = 0;
	emit->column = 0;
	emit->flow_level = 0;
	emit->output_error = 0;
	/* clear/set flags for a fresh start */
	emit->flags &= ~(FYEF_OPEN_ENDED | FYEF_HAD_DOCUMENT_START | FYEF_HAD_DOCUMENT_OUTPUT); 
	emit->flags |= FYEF_WHITESPACE | FYEF_INDENTATION;

	emit->state = FYES_NONE;

	/* reset the accumulator */
	fy_emit_accum_reset(&emit->ea);

	/* streaming mode indent */
	emit->s_indent = -1;
	/* streaming mode flags */
	emit->s_flags = DDNF_ROOT;

	fy_emit_save_ctx_cleanup(emit, &emit->s_sc);
	while (emit->sc_stack_top > 0)
		fy_emit_save_ctx_cleanup(emit, &emit->sc_stack[--emit->sc_stack_top]);

	emit->state_stack_top = 0;

	/* and release any queued events */
	if (reset_events) {
		while ((fyep = fy_eventp_list_pop(&emit->queued_events)) != NULL)
			fy_eventp_release(fyep);
	}
}

int fy_emit_setup(struct fy_emitter *emit, const struct fy_emitter_cfg *cfg)
{
	struct fy_diag *diag;

	if (!cfg)
		return -1;

	memset(emit, 0, sizeof(*emit));
	emit->output_fd = -1;

	/* is it extended config or not? */
	if (!(cfg->flags & FYECF_EXTENDED_CFG))
		emit->xcfg.cfg = *cfg;
	else
		emit->xcfg = *container_of(cfg, struct fy_emitter_xcfg, cfg);

	fy_emitter_fill_default_colors(emit);
	emit->output_fp = fy_emitter_get_output_fp(emit);
	if (!emit->output_fp)
		emit->output_fd = fy_emitter_get_output_fd(emit);

	if (!emit->xcfg.cfg.output) {
		if (!emit->output_fp && emit->output_fd < 0) {
			emit->xcfg.cfg.output = fy_emitter_null_output;
		} else {
			emit->xcfg.cfg.output = fy_emitter_default_output;
			/* for the default, turn on extended indicators */
			emit->xcfg.xflags |= FYEXCF_EXTENDED_INDICATORS;

			switch (emit->xcfg.xflags & (FYEXCF_COLOR_MASK << FYEXCF_COLOR_SHIFT)) {
			case FYEXCF_COLOR_AUTO:
				emit->output_colorize = emit->output_fp ?
						isatty(fileno(emit->output_fp)) :
						isatty(emit->output_fd);
				break;
			case FYEXCF_COLOR_FORCE:
				emit->output_colorize = true;
				break;
			default:
				emit->output_colorize = false;
				break;
			}
		}
	}

	diag = cfg->diag;

	if (!diag) {
		diag = fy_diag_create(NULL);
		if (!diag)
			return -1;
	} else
		fy_diag_ref(diag);

	emit->diag = diag;

	fy_emit_accum_init(&emit->ea, emit->ea_inplace_buf, sizeof(emit->ea_inplace_buf), 0, fylb_cr_nl);
	fy_eventp_list_init(&emit->queued_events);

	emit->state_stack = emit->state_stack_inplace;
	emit->state_stack_alloc = sizeof(emit->state_stack_inplace)/sizeof(emit->state_stack_inplace[0]);

	emit->sc_stack = emit->sc_stack_inplace;
	emit->sc_stack_alloc = sizeof(emit->sc_stack_inplace)/sizeof(emit->sc_stack_inplace[0]);

	fy_eventp_list_init(&emit->recycled_eventp);
	fy_token_list_init(&emit->recycled_token);

	/* suppress recycling if we must */
	emit->suppress_recycling_force = getenv("FY_VALGRIND") && !getenv("FY_VALGRIND_RECYCLING");
	emit->suppress_recycling = emit->suppress_recycling_force;

	if (!emit->suppress_recycling) {
		emit->recycled_eventp_list = &emit->recycled_eventp;
		emit->recycled_token_list = &emit->recycled_token;
	} else {
		emit->recycled_eventp_list = NULL;
		emit->recycled_token_list = NULL;
	}

	fy_emit_reset(emit, false);

	return 0;
}

void fy_emit_save_ctx_cleanup(struct fy_emitter *emit, struct fy_emit_save_ctx *sc)
{
	fy_token_unref_rl(emit->recycled_token_list, sc->fyt_last_key);
	sc->fyt_last_key = NULL;
	fy_token_unref_rl(emit->recycled_token_list, sc->fyt_last_value);
	sc->fyt_last_value = NULL;
	memset(sc, 0, sizeof(*sc));
}

void fy_emit_cleanup(struct fy_emitter *emit)
{
	struct fy_eventp *fyep;
	struct fy_token *fyt;

	/* call the finalizer if it exists */
	if (emit->finalizer)
		emit->finalizer(emit);

	fy_emit_save_ctx_cleanup(emit, &emit->s_sc);
	while (emit->sc_stack_top > 0)
		fy_emit_save_ctx_cleanup(emit, &emit->sc_stack[--emit->sc_stack_top]);

	while ((fyt = fy_token_list_pop(&emit->recycled_token)) != NULL)
		fy_token_free(fyt);

	while ((fyep = fy_eventp_list_pop(&emit->recycled_eventp)) != NULL)
		fy_eventp_free(fyep);

	fy_document_state_unref(emit->fyds);
	emit->fyds = NULL;

	fy_emit_accum_cleanup(&emit->ea);

	while ((fyep = fy_eventp_list_pop(&emit->queued_events)) != NULL)
		fy_eventp_release(fyep);

	if (emit->state_stack && emit->state_stack != emit->state_stack_inplace)
		free(emit->state_stack);

	if (emit->sc_stack && emit->sc_stack != emit->sc_stack_inplace)
		free(emit->sc_stack);

	fy_diag_unref(emit->diag);

	if (emit->owned_output_fp)
		fclose(emit->owned_output_fp);
}

int fy_emit_node_no_check(struct fy_emitter *emit, struct fy_node *fyn)
{
	if (fyn)
		fy_emit_node_internal(emit, fyn, DDNF_ROOT, -1, false);
	return 0;
}

int fy_emit_node(struct fy_emitter *emit, struct fy_node *fyn)
{
	int ret;

	ret = fy_emit_node_check(emit, fyn);
	if (ret)
		return ret;

	return fy_emit_node_no_check(emit, fyn);
}

int fy_emit_root_node_no_check(struct fy_emitter *emit, struct fy_node *fyn)
{
	if (!emit || !fyn)
		return -1;

	fy_emit_node_internal(emit, fyn, DDNF_ROOT, -1, false);

	return 0;
}

int fy_emit_root_node(struct fy_emitter *emit, struct fy_node *fyn)
{
	int ret;

	if (!emit || !fyn)
		return -1;

	ret = fy_emit_node_check(emit, fyn);
	if (ret)
		return ret;

	return fy_emit_root_node_no_check(emit, fyn);
}

void fy_emit_prepare_document_state(struct fy_emitter *emit, struct fy_document_state *fyds)
{
	if (!emit || !fyds)
		return;

	/* if the original document was JSON and the mode is ORIGINAL turn on JSON mode */
	emit->source_json = fyds && fyds->json_mode;
	emit->force_json = (emit->xcfg.cfg.flags & FYECF_MODE(FYECF_MODE_MASK)) == FYECF_MODE_ORIGINAL &&
			   emit->source_json;
}

int fy_emit_document_no_check(struct fy_emitter *emit, struct fy_document *fyd)
{
	int rc;

	rc = fy_emit_document_start(emit, fyd, NULL);
	if (rc)
		return rc;

	rc = fy_emit_root_node_no_check(emit, fyd->root);
	if (rc)
		return rc;

	rc = fy_emit_document_end(emit);

	fy_emit_reset(emit, false);

	return rc;
}

int fy_emit_document(struct fy_emitter *emit, struct fy_document *fyd)
{
	int ret;

	if (!emit)
		return -1;

	if (fyd) {
		fy_emit_prepare_document_state(emit, fyd->fyds);

		if (fyd->root) {
			ret = fy_emit_node_check(emit, fyd->root);
			if (ret)
				return ret;
		}
	}

	return fy_emit_document_no_check(emit, fyd);
}

struct fy_emitter *fy_emitter_create(const struct fy_emitter_cfg *cfg)
{
	struct fy_emitter *emit;
	int rc;

	if (!cfg)
		return NULL;

	emit = malloc(sizeof(*emit));
	if (!emit)
		return NULL;

	rc = fy_emit_setup(emit, cfg);
	if (rc) {
		free(emit);
		return NULL;
	}

	return emit;
}

void fy_emitter_destroy(struct fy_emitter *emit)
{
	if (!emit)
		return;

	fy_emit_cleanup(emit);

	free(emit);
}

const struct fy_emitter_cfg *fy_emitter_get_cfg(struct fy_emitter *emit)
{
	if (!emit)
		return NULL;

	return &emit->xcfg.cfg;
}

struct fy_diag *fy_emitter_get_diag(struct fy_emitter *emit)
{
	if (!emit || !emit->diag)
		return NULL;
	return fy_diag_ref(emit->diag);
}

int fy_emitter_set_diag(struct fy_emitter *emit, struct fy_diag *diag)
{
	struct fy_diag_cfg dcfg;

	if (!emit)
		return -1;

	/* default? */
	if (!diag) {
		fy_diag_cfg_default(&dcfg);
		diag = fy_diag_create(&dcfg);
		if (!diag)
			return -1;
	}

	fy_diag_unref(emit->diag);
	emit->diag = fy_diag_ref(diag);

	return 0;
}

void fy_emitter_set_finalizer(struct fy_emitter *emit,
		void (*finalizer)(struct fy_emitter *emit))
{
	if (!emit)
		return;
	emit->finalizer = finalizer;
}

struct fy_emit_buffer_state {
	char **bufp;
	size_t *sizep;
	char *buf;
	size_t size;
	size_t pos;
	size_t need;
	bool allocate_buffer;
	size_t maxsize;
};

static int do_buffer_output(struct fy_emitter *emit, enum fy_emitter_write_type type, const char *str, int leni, void *userdata)
{
	struct fy_emit_buffer_state *state = emit->xcfg.cfg.userdata;
	size_t left, pagesize, size, len;
	char *bufnew;

	if (leni < 0)
		return -1;

	/* convert to unsigned and use that */
	len = (size_t)leni;

	/* no funky business */
	if ((ssize_t)len < 0)
		return -1;

	state->need += len;
	left = state->size - state->pos;
	if (left < len) {
		if (!state->allocate_buffer)
			return 0;

		pagesize = sysconf(_SC_PAGESIZE);
		size = state->need + pagesize - 1;
		size = size - size % pagesize;

		bufnew = realloc(state->buf, size);
		if (!bufnew)
			return -1;
		state->buf = bufnew;
		state->size = size;
		left = state->size - state->pos;

	}

	if (len > left)
		len = left;
	if (state->buf)
		memcpy(state->buf + state->pos, str, len);
	state->pos += len;

	return (int)len;
}

static void
fy_emitter_str_finalizer(struct fy_emitter *emit)
{
	struct fy_emit_buffer_state *state;

	if (!emit || !(state = emit->xcfg.cfg.userdata))
		return;

	/* if the buffer is allowed to allocate_buffer... */
	if (state->allocate_buffer && state->buf)
		free(state->buf);
	free(state);

	emit->xcfg.cfg.userdata = NULL;
}

static struct fy_emitter *
fy_emitter_create_str_internal(enum fy_emitter_cfg_flags flags, char **bufp, size_t *sizep, bool allocate_buffer)
{
	struct fy_emitter *emit;
	struct fy_emitter_cfg emit_cfg;
	struct fy_emit_buffer_state *state;

	if (flags & FYECF_EXTENDED_CFG)
		return NULL;

	state = malloc(sizeof(*state));
	if (!state)
		return NULL;

	/* if any of these NULL, it's a allocation case */
	if ((!bufp || !sizep) && !allocate_buffer)
		return NULL;

	if (bufp && sizep) {
		state->bufp = bufp;
		state->buf = *bufp;
		state->sizep = sizep;
		state->size = *sizep;
		state->maxsize = state->size;
	} else {
		state->bufp = NULL;
		state->buf = NULL;
		state->sizep = NULL;
		state->size = 0;
		state->maxsize = 0;
	}
	state->pos = 0;
	state->need = 0;
	state->allocate_buffer = allocate_buffer;

	memset(&emit_cfg, 0, sizeof(emit_cfg));
	emit_cfg.output = do_buffer_output;
	emit_cfg.userdata = state;
	emit_cfg.flags = flags;

	emit = fy_emitter_create(&emit_cfg);
	if (!emit)
		goto err_out;

	/* set finalizer to cleanup */
	fy_emitter_set_finalizer(emit, fy_emitter_str_finalizer);

	return emit;

err_out:
	if (state)
		free(state);
	return NULL;
}

static int
fy_emitter_collect_str_internal(struct fy_emitter *emit, char **bufp, size_t *sizep)
{
	struct fy_emit_buffer_state *state;
	char *buf;
	size_t size;
	int rc;

	state = emit->xcfg.cfg.userdata;
	assert(state);

	/* if NULL, then use the values stored on the state */
	if (!bufp)
		bufp = state->bufp;
	if (!sizep)
		sizep = state->sizep;

	/* terminating zero */
	rc = do_buffer_output(emit, fyewt_terminating_zero, "\0", 1, state);
	if (rc != 1)
		goto err_out;

	/* if we are on a fixed buffer don't output */
	if (state->maxsize > 0 && state->need > state->maxsize)
		goto err_out;

	state->size = state->need;

	if (state->allocate_buffer) {
		/* resize */
		buf = realloc(state->buf, state->size);
		/* very likely since we shrink the buffer, but make sure we don't error out */
		if (buf)
			state->buf = buf;
	}

	/* retreive the buffer and size */
	size = state->size;
	if (size > 0 && state->buf[size-1] == '\0')
		size--;
	*sizep = size;
	*bufp = state->buf;

	/* reset the buffer, ownership now to the caller */
	state->buf = NULL;
	state->size = 0;
	state->pos = 0;
	state->bufp = NULL;
	state->sizep = NULL;

	return 0;

err_out:
	*bufp = NULL;
	*sizep = 0;
	return -1;
}

static int fy_emit_str_internal(struct fy_document *fyd,
				enum fy_emitter_cfg_flags flags,
				struct fy_node *fyn, char **bufp, size_t *sizep,
				bool allocate_buffer)
{
	struct fy_emitter *emit = NULL;
	int rc = -1;

	emit = fy_emitter_create_str_internal(flags, bufp, sizep, allocate_buffer);
	if (!emit)
		goto out_err;

	if (fyd) {
		fy_emit_prepare_document_state(emit, fyd->fyds);
		rc = 0;
		if (fyd->root)
			rc = fy_emit_node_check(emit, fyd->root);
		if (!rc)
			rc = fy_emit_document_no_check(emit, fyd);
	} else {
		rc = fy_emit_node_check(emit, fyn);
		if (!rc)
			rc = fy_emit_node_no_check(emit, fyn);
	}

	if (rc)
		goto out_err;

	rc = fy_emitter_collect_str_internal(emit, NULL, NULL);
	if (rc)
		goto out_err;

	/* OK, all done */

out_err:
	fy_emitter_destroy(emit);
	return rc;
}

int fy_emit_document_to_buffer(struct fy_document *fyd, enum fy_emitter_cfg_flags flags, char *buf, size_t size)
{
	int rc;

	if (!buf)
		return -1;

	rc = fy_emit_str_internal(fyd, flags, NULL, &buf, &size, false);
	if (rc != 0)
		return -1;
	if ((int)size < 0)
		return -1;
	return (int)size;
}

char *fy_emit_document_to_string(struct fy_document *fyd, enum fy_emitter_cfg_flags flags)
{
	char *buf;
	size_t size;
	int rc;

	buf = NULL;
	size = 0;
	rc = fy_emit_str_internal(fyd, flags, NULL, &buf, &size, true);
	if (rc != 0)
		return NULL;
	return buf;
}

struct fy_emitter *
fy_emit_to_buffer(enum fy_emitter_cfg_flags flags, char *buf, size_t size)
{
	if (!buf)
		return NULL;

	return fy_emitter_create_str_internal(flags, &buf, &size, false);
}

char *
fy_emit_to_buffer_collect(struct fy_emitter *emit, size_t *sizep)
{
	int rc;
	char *buf;

	if (!emit || !sizep)
		return NULL;

	rc = fy_emitter_collect_str_internal(emit, &buf, sizep);
	if (rc) {
		*sizep = 0;
		return NULL;
	}
	return buf;
}

struct fy_emitter *
fy_emit_to_string(enum fy_emitter_cfg_flags flags)
{
	return fy_emitter_create_str_internal(flags, NULL, NULL, true);
}

char *
fy_emit_to_string_collect(struct fy_emitter *emit, size_t *sizep)
{
	int rc;
	char *buf;

	if (!emit || !sizep)
		return NULL;

	rc = fy_emitter_collect_str_internal(emit, &buf, sizep);
	if (rc) {
		*sizep = 0;
		return NULL;
	}
	return buf;
}

static int do_file_output(struct fy_emitter *emit, enum fy_emitter_write_type type, const char *str, int leni, void *userdata)
{
	FILE *fp = userdata;
	size_t len, wrn;

	/* no funky stuff */
	if (leni < 0)
		return -1;

	len = (size_t)leni;
	wrn = fwrite(str, 1, len, fp);
	return (int)wrn;
}

int fy_emit_document_to_fp(struct fy_document *fyd, enum fy_emitter_cfg_flags flags,
			   FILE *fp)
{
	struct fy_emitter emit_state, *emit = &emit_state;
	struct fy_emitter_cfg emit_cfg;
	int rc;

	/* we don't allow an extended configuration */
	if (!fp || (flags & FYECF_EXTENDED_CFG))
		return -1;

	memset(&emit_cfg, 0, sizeof(emit_cfg));
	emit_cfg.output = do_file_output;
	emit_cfg.userdata = fp;
	emit_cfg.flags = flags;
	fy_emit_setup(emit, &emit_cfg);

	fy_emit_prepare_document_state(emit, fyd->fyds);

	rc = 0;
	if (fyd->root)
		rc = fy_emit_node_check(emit, fyd->root);

	rc = fy_emit_document_no_check(emit, fyd);

	fy_emit_cleanup(emit);

	return rc ? rc : 0;
}

int fy_emit_document_to_file(struct fy_document *fyd,
			     enum fy_emitter_cfg_flags flags,
			     const char *filename)
{
	FILE *fp;
	int rc;

	fp = filename ? fopen(filename, "wa") : stdout;
	if (!fp)
		return -1;

	rc = fy_emit_document_to_fp(fyd, flags, fp);

	if (fp != stdout)
		fclose(fp);

	return rc ? rc : 0;
}

static int do_fd_output(struct fy_emitter *emit, enum fy_emitter_write_type type, const char *str, int leni, void *userdata)
{
	size_t len;
	int fd;
	ssize_t wrn;
	int total;

	len = (size_t)leni;

	/* no funky stuff */
	if (len < 0)
		return -1;

	/* get the file descriptor */
	fd = (int)(uintptr_t)userdata;
	if (fd < 0)
		return -1;

	/* loop output to fd */
	total = 0;
	while (len > 0) {

		do {
			wrn = write(fd, str, len);
		} while (wrn == -1 && errno == EAGAIN);

		if (wrn == -1)
			return -1;

		if (wrn == 0)
			return total;

		len -= wrn;
		str += wrn;
		total += (int)wrn;
	}

	return total;
}

int fy_emit_document_to_fd(struct fy_document *fyd, enum fy_emitter_cfg_flags flags, int fd)
{
	struct fy_emitter emit_state, *emit = &emit_state;
	struct fy_emitter_cfg emit_cfg;
	int rc;

	if (fd < 0 || (flags & FYECF_EXTENDED_CFG))
		return -1;

	memset(&emit_cfg, 0, sizeof(emit_cfg));
	emit_cfg.output = do_fd_output;
	emit_cfg.userdata = (void *)(uintptr_t)fd;
	emit_cfg.flags = flags;
	fy_emit_setup(emit, &emit_cfg);

	fy_emit_prepare_document_state(emit, fyd->fyds);

	rc = 0;
	if (fyd->root)
		rc = fy_emit_node_check(emit, fyd->root);

	rc = fy_emit_document_no_check(emit, fyd);

	fy_emit_cleanup(emit);

	return rc ? rc : 0;
}

int fy_emit_node_to_buffer(struct fy_node *fyn, enum fy_emitter_cfg_flags flags, char *buf, size_t size)
{
	int rc;

	rc = fy_emit_str_internal(NULL, flags, fyn, &buf, &size, false);
	if (rc != 0)
		return -1;
	return (int)size;
}

char *fy_emit_node_to_string(struct fy_node *fyn, enum fy_emitter_cfg_flags flags)
{
	char *buf;
	size_t size;
	int rc;

	buf = NULL;
	size = 0;
	rc = fy_emit_str_internal(NULL, flags, fyn, &buf, &size, true);
	if (rc != 0)
		return NULL;
	return buf;
}

/* we can consume events only if the lookaheads
 * are satisfied. The lookaheads depends on the
 * styling mode. Some styling modes do not require
 * a lookahead at all but we do it anyway.
 */
static bool fy_emit_ready(struct fy_emitter *emit)
{
	struct fy_eventp *fyep;
	int need, count;

	/* When the head event starts a flow collection in ORIGINAL mode,
	 * buffer events on the same source line so that oneline_flow
	 * detection can compare start/end line marks before the prolog
	 * runs.  We only buffer events that share the start token's line;
	 * as soon as an event is on a different line we know the collection
	 * is multi-line and can proceed immediately.  This keeps the buffer
	 * bounded to a single line's worth of events rather than the
	 * entire collection.
	 */
	fyep = fy_eventp_list_head(&emit->queued_events);
	if (fyep && fy_emit_preserve_flow_layout(emit)) {
		struct fy_token *start_token = NULL;
		const struct fy_mark *sm;

		if (fyep->e.type == FYET_SEQUENCE_START)
			start_token = fyep->e.sequence_start.sequence_start;
		else if (fyep->e.type == FYET_MAPPING_START)
			start_token = fyep->e.mapping_start.mapping_start;

		/* Only buffer when we have a flow start token with valid
		 * source marks (skip synthetic events with 0:0:0 marks) */
		sm = start_token ? fy_token_start_mark(start_token) : NULL;

		if (start_token &&
		    (start_token->type == FYTT_FLOW_SEQUENCE_START ||
		     start_token->type == FYTT_FLOW_MAPPING_START) &&
		    sm && !(sm->line == 0 && sm->column == 0 && sm->input_pos == 0)) {
			int depth = 0;

			sm = fy_token_start_mark(start_token);

			/* Skip buffering for synthetic events (no source info) */
			if (!sm || (sm->line == 0 && sm->column == 0 && sm->input_pos == 0))
				goto normal;

			for (fyep = fy_eventp_list_head(&emit->queued_events); fyep;
					fyep = fy_eventp_next(&emit->queued_events, fyep)) {

				switch (fyep->e.type) {
				case FYET_SEQUENCE_START:
				case FYET_MAPPING_START:
					depth++;
					break;
				case FYET_SEQUENCE_END:
				case FYET_MAPPING_END:
					if (--depth == 0)
						return true;
					break;
				default:
					break;
				}

				/* If any event is on a different line, the
				 * collection is multi-line — stop buffering */
				if (depth > 0) {
					struct fy_token *et = fy_event_get_token(&fyep->e);
					const struct fy_mark *em = et ? fy_token_start_mark(et) : NULL;

					if (em && em->line != sm->line)
						return true;
				}
			}
			return false;
		}
	}
normal:

	count = 0;
	need = -1;
	for (fyep = fy_eventp_list_head(&emit->queued_events); fyep;
			fyep = fy_eventp_next(&emit->queued_events, fyep)) {

		count++;
		switch (fyep->e.type) {
		case FYET_DOCUMENT_START:
			need++;
			break;

		case FYET_SEQUENCE_START:
			if (need < 0)
				need = 2;
			break;

		case FYET_MAPPING_START:
			if (need < 0)
				need = 2;
			break;

		default:
			need = 0;
			break;
		}

		if (count >= need)
			return true;

	}

	return false;
}

extern const char *fy_event_type_txt[];

const char *fy_emitter_state_txt[] = {
	[FYES_NONE]			= "NONE",
	[FYES_STREAM_START]		= "STREAM_START",
	[FYES_FIRST_DOCUMENT_START]	= "FIRST_DOCUMENT_START",
	[FYES_DOCUMENT_START]		= "DOCUMENT_START",
	[FYES_DOCUMENT_CONTENT]		= "DOCUMENT_CONTENT",
	[FYES_DOCUMENT_END]		= "DOCUMENT_END",
	[FYES_SEQUENCE_FIRST_ITEM]	= "SEQUENCE_FIRST_ITEM",
	[FYES_SEQUENCE_ITEM]		= "SEQUENCE_ITEM",
	[FYES_MAPPING_FIRST_KEY]	= "MAPPING_FIRST_KEY",
	[FYES_MAPPING_KEY]		= "MAPPING_KEY",
	[FYES_MAPPING_SIMPLE_VALUE]	= "MAPPING_SIMPLE_VALUE",
	[FYES_MAPPING_VALUE]		= "MAPPING_VALUE",
	[FYES_END]			= "END",
};

struct fy_eventp *
fy_emit_next_event(struct fy_emitter *emit)
{
	if (!fy_emit_ready(emit))
		return NULL;

	return fy_eventp_list_pop(&emit->queued_events);
}

struct fy_eventp *
fy_emit_peek_next_event(struct fy_emitter *emit)
{
	return fy_eventp_list_head(&emit->queued_events);
}

bool fy_emit_streaming_sequence_empty(struct fy_emitter *emit)
{
	struct fy_eventp *fyepn;

	fyepn = fy_emit_peek_next_event(emit);

	/* should never happen, check on debugging mode */
	assert(fyepn);
	if (!fyepn)
		return false;

	return fyepn->e.type == FYET_SEQUENCE_END;
}

bool fy_emit_streaming_mapping_empty(struct fy_emitter *emit)
{
	struct fy_eventp *fyepn;

	fyepn = fy_emit_peek_next_event(emit);

	/* should never happen, check on debugging mode */
	assert(fyepn);
	if (!fyepn)
		return false;

	return fyepn->e.type == FYET_MAPPING_END;
}

/* Scan queued events to determine if a flow collection fits on one line.
 * Called after the START event has been popped; the queue begins with the
 * first child event.  We track nesting depth to find the matching END.
 * Returns true when start and end tokens are on the same source line.
 * Returns false for synthetic events (marks at 0:0:0) which have no
 * meaningful source position.
 */
static bool fy_emit_streaming_flow_oneline(struct fy_emitter *emit,
					   struct fy_token *start_token)
{
	struct fy_eventp *fyep;
	const struct fy_mark *sm, *em;
	struct fy_token *end_token;
	int depth = 1;

	sm = fy_token_start_mark(start_token);
	if (!sm || (sm->line == 0 && sm->column == 0 && sm->input_pos == 0))
		return false;

	for (fyep = fy_eventp_list_head(&emit->queued_events); fyep;
			fyep = fy_eventp_next(&emit->queued_events, fyep)) {

		switch (fyep->e.type) {
		case FYET_SEQUENCE_START:
		case FYET_MAPPING_START:
			depth++;
			break;
		case FYET_SEQUENCE_END:
			if (--depth == 0) {
				end_token = fyep->e.sequence_end.sequence_end;
				em = fy_token_end_mark(end_token);
				return em && sm->line == em->line;
			}
			break;
		case FYET_MAPPING_END:
			if (--depth == 0) {
				end_token = fyep->e.mapping_end.mapping_end;
				em = fy_token_end_mark(end_token);
				return em && sm->line == em->line;
			}
			break;
		default:
			break;
		}
	}

	return false;
}

static void fy_emit_goto_state(struct fy_emitter *emit, enum fy_emitter_state state)
{
	if (emit->state == state)
		return;

	emit->state = state;
}

static int fy_emit_push_state(struct fy_emitter *emit, enum fy_emitter_state state)
{
	enum fy_emitter_state *states;

	if (emit->state_stack_top >= emit->state_stack_alloc) {
		states = realloc(emit->state_stack == emit->state_stack_inplace ? NULL : emit->state_stack,
				sizeof(emit->state_stack[0]) * emit->state_stack_alloc * 2);
		if (!states)
			return -1;

		if (emit->state_stack == emit->state_stack_inplace)
			memcpy(states, emit->state_stack, sizeof(emit->state_stack[0]) * emit->state_stack_top);
		emit->state_stack = states;
		emit->state_stack_alloc *= 2;
	}
	emit->state_stack[emit->state_stack_top++] = state;

	return 0;
}

enum fy_emitter_state fy_emit_pop_state(struct fy_emitter *emit)
{
	if (!emit->state_stack_top)
		return FYES_NONE;

	return emit->state_stack[--emit->state_stack_top];
}

int fy_emit_push_sc(struct fy_emitter *emit, struct fy_emit_save_ctx *sc)
{
	struct fy_emit_save_ctx *scs;

	if (emit->sc_stack_top >= emit->sc_stack_alloc) {
		scs = realloc(emit->sc_stack == emit->sc_stack_inplace ? NULL : emit->sc_stack,
				sizeof(emit->sc_stack[0]) * emit->sc_stack_alloc * 2);
		if (!scs)
			return -1;

		if (emit->sc_stack == emit->sc_stack_inplace)
			memcpy(scs, emit->sc_stack, sizeof(emit->sc_stack[0]) * emit->sc_stack_top);
		emit->sc_stack = scs;
		emit->sc_stack_alloc *= 2;
	}
	emit->sc_stack[emit->sc_stack_top++] = *sc;

	return 0;
}

int fy_emit_pop_sc(struct fy_emitter *emit, struct fy_emit_save_ctx *sc)
{
	if (!emit->sc_stack_top)
		return -1;

	*sc = emit->sc_stack[--emit->sc_stack_top];

	return 0;
}

static int fy_emit_streaming_node(struct fy_emitter *emit, struct fy_parser *fyp, struct fy_eventp *fyep, int flags)
{
	struct fy_event *fye = &fyep->e;
	struct fy_emit_save_ctx *sc = &emit->s_sc;
	enum fy_node_style style, xstyle;
	int ret, s_flags, s_indent;

	/* force collections after --- to start at a new line */
	if (!fy_emit_is_oneline(emit) &&
			fye->type != FYET_ALIAS && fye->type != FYET_SCALAR &&
			(emit->s_flags & DDNF_ROOT) && emit->column != 0) {
		fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
		emit->flags |= FYEF_WHITESPACE | FYEF_INDENTATION;
	}

	emit->s_flags = flags;

	switch (fye->type) {
	case FYET_ALIAS:
		fy_emit_token_write_alias(emit, fye->alias.anchor, emit->s_flags, emit->s_indent);
		fy_emit_goto_state(emit, fy_emit_pop_state(emit));
		break;

	case FYET_SCALAR:
		/* if we're pretty and at column 0 (meaning it's a single scalar document) output --- */
		if ((emit->s_flags & DDNF_ROOT) && fy_emit_is_pretty_mode(emit) && !emit->column &&
				!fy_emit_is_flow_mode(emit) && !(emit->s_flags & DDNF_FLOW)) {
			fy_emit_document_start_indicator(emit);
			emit->fyds->started_explicit = true;
		}
		fy_emit_common_node_preamble(emit, fye->scalar.value, fye->scalar.anchor, fye->scalar.tag, emit->s_flags, emit->s_indent);
		style = fye->scalar.value ?
				fy_node_style_from_scalar_style(fye->scalar.value->scalar.style) :
				FYNS_PLAIN;
		fy_emit_token_scalar(emit, fye->scalar.value, emit->s_flags, emit->s_indent, style, fye->scalar.tag);
		fy_emit_goto_state(emit, fy_emit_pop_state(emit));
		break;

	case FYET_SEQUENCE_START:

		/* save this context */
		ret = fy_emit_push_sc(emit, sc);
		if (ret)
			return ret;

		s_flags = emit->s_flags;
		s_indent = emit->s_indent;

		if (fye->sequence_start.sequence_start)
			xstyle = fye->sequence_start.sequence_start->type == FYTT_BLOCK_SEQUENCE_START ?
				FYNS_BLOCK : FYNS_FLOW;
		else
			xstyle = FYNS_ANY;

		fy_emit_common_node_preamble(emit, fye->sequence_start.sequence_start,
					     fye->sequence_start.anchor, fye->sequence_start.tag,
					     emit->s_flags, emit->s_indent);

		/* create new context */
		memset(sc, 0, sizeof(*sc));
		sc->flags = emit->s_flags & (DDNF_ROOT | DDNF_SEQ | DDNF_MAP);
		sc->indent = emit->s_indent;
		sc->empty = fy_emit_streaming_sequence_empty(emit);
		sc->flow_token = xstyle == FYNS_FLOW;
		if (sc->flow_token && fy_emit_preserve_flow_layout(emit))
			sc->oneline_flow = fy_emit_streaming_flow_oneline(emit,
					fye->sequence_start.sequence_start);
		sc->flow = !!(s_flags & DDNF_FLOW);
		sc->xstyle = xstyle;
		sc->old_indent = sc->indent;
		sc->s_flags = s_flags;
		sc->s_indent = s_indent;

		fy_emit_sequence_prolog(emit, sc, fye->sequence_start.sequence_start);
		sc->flags &= ~DDNF_MAP;
		sc->flags |= DDNF_SEQ;

		emit->s_flags = sc->flags;
		emit->s_indent = sc->indent;

		fy_emit_goto_state(emit, FYES_SEQUENCE_FIRST_ITEM);
		break;

	case FYET_MAPPING_START:
		/* save this context */
		ret = fy_emit_push_sc(emit, sc);
		if (ret)
			return ret;

		s_flags = emit->s_flags;
		s_indent = emit->s_indent;

		if (fye->mapping_start.mapping_start)
			xstyle = fye->mapping_start.mapping_start->type == FYTT_BLOCK_MAPPING_START ?
				FYNS_BLOCK : FYNS_FLOW;
		else
			xstyle = FYNS_ANY;

		fy_emit_common_node_preamble(emit, fye->mapping_start.mapping_start,
					     fye->mapping_start.anchor, fye->mapping_start.tag,
					     emit->s_flags, emit->s_indent);

		/* create new context */
		memset(sc, 0, sizeof(*sc));
		sc->flags = emit->s_flags & (DDNF_ROOT | DDNF_SEQ | DDNF_MAP);
		sc->indent = emit->s_indent;
		sc->empty = fy_emit_streaming_mapping_empty(emit);
		sc->flow_token = xstyle == FYNS_FLOW;
		if (sc->flow_token && fy_emit_preserve_flow_layout(emit))
			sc->oneline_flow = fy_emit_streaming_flow_oneline(emit,
					fye->mapping_start.mapping_start);
		sc->flow = !!(s_flags & DDNF_FLOW);
		sc->xstyle = xstyle;
		sc->old_indent = sc->indent;
		sc->s_flags = s_flags;
		sc->s_indent = s_indent;

		fy_emit_mapping_prolog(emit, sc, fye->mapping_start.mapping_start);
		sc->flags &= ~DDNF_SEQ;
		sc->flags |= DDNF_MAP;

		emit->s_flags = sc->flags;
		emit->s_indent = sc->indent;

		fy_emit_goto_state(emit, FYES_MAPPING_FIRST_KEY);
		break;

	default:
		fy_error(emit->diag, "%s: expected ALIAS|SCALAR|SEQUENCE_START|MAPPING_START", __func__);
		return -1;
	}

	return 0;
}

static int fy_emit_handle_stream_start(struct fy_emitter *emit, struct fy_parser *fyp, struct fy_eventp *fyep)
{
	struct fy_event *fye = &fyep->e;

	if (fye->type != FYET_STREAM_START) {
		fy_error(emit->diag, "%s: expected FYET_STREAM_START", __func__);
		return -1;
	}

	fy_document_state_unref(emit->fyds);
	emit->fyds = NULL;

	fy_emit_reset(emit, false);

	fy_emit_goto_state(emit, FYES_FIRST_DOCUMENT_START);

	return 0;
}

static int fy_emit_handle_document_start(struct fy_emitter *emit, struct fy_parser *fyp, struct fy_eventp *fyep, bool first)
{
	struct fy_event *fye = &fyep->e;
	struct fy_document_state *fyds;

	if (fye->type != FYET_DOCUMENT_START &&
	    fye->type != FYET_STREAM_END) {
		fy_error(emit->diag, "%s: expected FYET_DOCUMENT_START|FYET_STREAM_END", __func__);
		return -1;
	}

	if (fye->type == FYET_STREAM_END) {
		fy_emit_goto_state(emit, FYES_END);
		return 0;
	}

	/* transfer ownership to the emitter */
	fyds = fye->document_start.document_state;
	// fye->document_start.document_state = NULL;

	/* prepare (i.e. adapt to the document state) */
	fy_emit_prepare_document_state(emit, fyds);

	fy_emit_common_document_start(emit, fyds, false);

	fy_emit_goto_state(emit, FYES_DOCUMENT_CONTENT);

	return 0;
}

static int fy_emit_handle_document_end(struct fy_emitter *emit, struct fy_parser *fyp, struct fy_eventp *fyep)
{
	struct fy_event *fye = &fyep->e;
	int ret;

	if (fye->type != FYET_DOCUMENT_END) {
		fy_error(emit->diag, "%s: expected FYET_DOCUMENT_END", __func__);
		return -1;
	}

	ret = fy_emit_common_document_end(emit, true, fye->document_end.implicit);
	if (ret)
		return ret;

	fy_emit_reset(emit, false);
	fy_emit_goto_state(emit, FYES_DOCUMENT_START);
	emit->flags |= FYEF_HAD_DOCUMENT_END_OUTPUT;
	return 0;
}

static int fy_emit_handle_document_content(struct fy_emitter *emit, struct fy_parser *fyp, struct fy_eventp *fyep)
{
	struct fy_event *fye = &fyep->e;
	int ret;

	/* empty document? */
	if (fye->type == FYET_DOCUMENT_END)
		return fy_emit_handle_document_end(emit, fyp, fyep);

	ret = fy_emit_push_state(emit, FYES_DOCUMENT_END);
	if (ret)
		return ret;

	return fy_emit_streaming_node(emit, fyp, fyep, DDNF_ROOT);
}

static int fy_emit_handle_sequence_item(struct fy_emitter *emit, struct fy_parser *fyp, struct fy_eventp *fyep, bool first)
{
	struct fy_event *fye = &fyep->e;
	struct fy_emit_save_ctx *sc = &emit->s_sc;
	struct fy_token *fyt_item = NULL;
	int ret;

	switch (fye->type) {
	case FYET_SEQUENCE_END:
		fy_emit_sequence_item_epilog(emit, sc, true, sc->fyt_last_value);

		fy_emit_token_unref(emit, fyp, sc->fyt_last_value);
		sc->fyt_last_value = NULL;

		/* emit epilog */
		fy_emit_sequence_epilog(emit, sc);

		/* restore indent and flags */
		emit->s_indent = sc->s_indent;
		emit->s_flags = sc->s_flags;

		/* pop state */
		ret = fy_emit_pop_sc(emit, sc);
		assert(!ret);

		/* pop state */
		fy_emit_goto_state(emit, fy_emit_pop_state(emit));
		return ret;

	case FYET_ALIAS:
		fyt_item = fye->alias.anchor;
		break;
	case FYET_SCALAR:
		fyt_item = fye->scalar.value;
		break;
	case FYET_SEQUENCE_START:
		fyt_item = fye->sequence_start.sequence_start;
		break;
	case FYET_MAPPING_START:
		fyt_item = fye->mapping_start.mapping_start;
		break;
	default:
		fy_error(emit->diag, "%s: expected SEQUENCE_END|ALIAS|SCALAR|SEQUENCE_START|MAPPING_START", __func__);
		return -1;
	}

	if (!first)
		fy_emit_sequence_item_epilog(emit, sc, false, sc->fyt_last_value);
	fy_emit_token_unref(emit, fyp, sc->fyt_last_value);
	sc->fyt_last_value = NULL;

	ret = fy_emit_push_state(emit, FYES_SEQUENCE_ITEM);
	if (ret)
		return ret;

	/* reset indent and flags for each item */
	emit->s_indent = sc->indent;
	emit->s_flags = sc->flags;

	sc->fyt_last_value = fyt_item;

	fy_emit_sequence_item_prolog(emit, sc, fyt_item);

	ret = fy_emit_streaming_node(emit, fyp, fyep, sc->flags);

	switch (fye->type) {
	case FYET_ALIAS:
		fye->alias.anchor = NULL;	/* take ownership */
		break;
	case FYET_SCALAR:
		fye->scalar.value = NULL;	/* take ownership */
		break;
	case FYET_SEQUENCE_START:
		fye->sequence_start.sequence_start = NULL;	/* take ownership */
		break;
	case FYET_MAPPING_START:
		fye->mapping_start.mapping_start = NULL;	/* take ownership */
		break;
	default:
		break;
	}

	return ret;
}

static int fy_emit_handle_mapping_key(struct fy_emitter *emit, struct fy_parser *fyp, struct fy_eventp *fyep, bool first)
{
	struct fy_event *fye = &fyep->e;
	struct fy_emit_save_ctx *sc = &emit->s_sc;
	struct fy_token *fyt_key = NULL;
	const struct fy_token_analysis *ta;
	int ret;
	bool simple_key;

	simple_key = false;

	switch (fye->type) {
	case FYET_MAPPING_END:
		fy_emit_mapping_value_epilog(emit, sc, true, sc->fyt_last_value);

		/* emit epilog */
		fy_emit_mapping_epilog(emit, sc);

		fy_emit_token_unref(emit, fyp, sc->fyt_last_key);
		sc->fyt_last_key = NULL;
		fy_emit_token_unref(emit, fyp, sc->fyt_last_value);
		sc->fyt_last_value = NULL;

		/* restore indent and flags */
		emit->s_indent = sc->s_indent;
		emit->s_flags = sc->s_flags;

		/* pop state */
		ret = fy_emit_pop_sc(emit, sc);
		assert(!ret);

		/* pop state */
		fy_emit_goto_state(emit, fy_emit_pop_state(emit));
		return ret;

	case FYET_ALIAS:
		fyt_key = fye->alias.anchor;
		simple_key = true;
		break;
	case FYET_SCALAR:
		fyt_key = fye->scalar.value;
		ta = fy_token_text_analyze(fyt_key);
		simple_key = !!(ta->flags & FYTTAF_CAN_BE_SIMPLE_KEY);
		break;
	case FYET_SEQUENCE_START:
		fyt_key = fye->sequence_start.sequence_start;
		simple_key = fy_emit_streaming_sequence_empty(emit);
		break;
	case FYET_MAPPING_START:
		fyt_key = fye->mapping_start.mapping_start;
		simple_key = fy_emit_streaming_mapping_empty(emit);
		break;
	default:
		fy_error(emit->diag, "%s: expected MAPPING_END|ALIAS|SCALAR|SEQUENCE_START|MAPPING_START", __func__);
		return -1;
	}

	ret = fy_emit_push_state(emit, FYES_MAPPING_VALUE);
	if (ret)
		return ret;

	/* reset indent and flags for each key/value pair */
	emit->s_indent = sc->indent;
	emit->s_flags = sc->flags;

	if (!first)
		fy_emit_mapping_value_epilog(emit, sc, false, sc->fyt_last_value);

	fy_emit_token_unref(emit, fyp, sc->fyt_last_key);
	sc->fyt_last_key = NULL;
	fy_emit_token_unref(emit, fyp, sc->fyt_last_value);
	sc->fyt_last_value = NULL;

	sc->fyt_last_key = fyt_key;

	fy_emit_mapping_key_prolog(emit, sc, fyt_key, simple_key);

	ret = fy_emit_streaming_node(emit, fyp, fyep, sc->flags);

	switch (fye->type) {
	case FYET_ALIAS:
		fye->alias.anchor = NULL;	/* take ownership */
		break;
	case FYET_SCALAR:
		fye->scalar.value = NULL;	/* take ownership */
		break;
	case FYET_SEQUENCE_START:
		fye->sequence_start.sequence_start = NULL;	/* take ownership */
		break;
	case FYET_MAPPING_START:
		fye->mapping_start.mapping_start = NULL;	/* take ownership */
		break;
	default:
		break;
	}

	return ret;
}

static int fy_emit_handle_mapping_value(struct fy_emitter *emit, struct fy_parser *fyp, struct fy_eventp *fyep, bool simple)
{
	struct fy_event *fye = &fyep->e;
	struct fy_emit_save_ctx *sc = &emit->s_sc;
	struct fy_token *fyt_value = NULL;
	int ret;

	switch (fye->type) {
	case FYET_ALIAS:
		fyt_value = fye->alias.anchor;
		break;
	case FYET_SCALAR:
		fyt_value = fye->scalar.value;	/* take ownership */
		break;
	case FYET_SEQUENCE_START:
		fyt_value = fye->sequence_start.sequence_start;
		break;
	case FYET_MAPPING_START:
		fyt_value = fye->mapping_start.mapping_start;
		break;
	default:
		fy_error(emit->diag, "%s: expected ALIAS|SCALAR|SEQUENCE_START|MAPPING_START", __func__);
		return -1;
	}

	ret = fy_emit_push_state(emit, FYES_MAPPING_KEY);
	if (ret)
		return ret;

	fy_emit_mapping_key_epilog(emit, sc, sc->fyt_last_key);

	sc->fyt_last_value = fyt_value;

	fy_emit_mapping_value_prolog(emit, sc, fyt_value);

	ret = fy_emit_streaming_node(emit, fyp, fyep, sc->flags);

	switch (fye->type) {
	case FYET_ALIAS:
		fye->alias.anchor = NULL;	/* take ownership */
		break;
	case FYET_SCALAR:
		fye->scalar.value = NULL;	/* take ownership */
		break;
	case FYET_SEQUENCE_START:
		fye->sequence_start.sequence_start = NULL;	/* take ownership */
		break;
	case FYET_MAPPING_START:
		fye->mapping_start.mapping_start = NULL;	/* take ownership */
		break;
	default:
		break;
	}

	return ret;
}

int fy_emit_event_from_parser(struct fy_emitter *emit, struct fy_parser *fyp, struct fy_event *fye)
{
	struct fy_eventp *fyep;
	int ret;

	if (!emit || !fye)
		return -1;

	/* we're using the raw emitter interface, now mark first state */
	if (emit->state == FYES_NONE)
		emit->state = FYES_STREAM_START;

	/* handle reset (parser error recovery) */
	if (fye->type == FYET_STREAM_START && emit->state != FYES_STREAM_START) {
		if (emit->column != 0)
			fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
		fy_emit_puts_simple(emit, fyewt_document_indicator, "...");
		fy_emit_putc_simple(emit, fyewt_linebreak, '\n');
		emit->state = FYES_STREAM_START;
	}

	fyep = container_of(fye, struct fy_eventp, e);

	fy_eventp_list_add_tail(&emit->queued_events, fyep);

	ret = 0;
	while ((fyep = fy_emit_next_event(emit)) != NULL) {

		switch (emit->state) {
		case FYES_STREAM_START:
			ret = fy_emit_handle_stream_start(emit, fyp, fyep);
			break;

		case FYES_FIRST_DOCUMENT_START:
		case FYES_DOCUMENT_START:
			ret = fy_emit_handle_document_start(emit, fyp, fyep,
					emit->state == FYES_FIRST_DOCUMENT_START);
			break;

		case FYES_DOCUMENT_CONTENT:
			ret = fy_emit_handle_document_content(emit, fyp, fyep);
			break;

		case FYES_DOCUMENT_END:
			ret = fy_emit_handle_document_end(emit, fyp, fyep);
			break;

		case FYES_SEQUENCE_FIRST_ITEM:
		case FYES_SEQUENCE_ITEM:
			ret = fy_emit_handle_sequence_item(emit, fyp, fyep,
					emit->state == FYES_SEQUENCE_FIRST_ITEM);
			break;

		case FYES_MAPPING_FIRST_KEY:
		case FYES_MAPPING_KEY:
			ret = fy_emit_handle_mapping_key(emit, fyp, fyep,
					emit->state == FYES_MAPPING_FIRST_KEY);
			break;

		case FYES_MAPPING_SIMPLE_VALUE:
		case FYES_MAPPING_VALUE:
			ret = fy_emit_handle_mapping_value(emit, fyp, fyep,
					emit->state == FYES_MAPPING_SIMPLE_VALUE);
			break;

		case FYES_END:
			ret = -1;
			break;

		default:
			assert(1);      /* Invalid state. */
		}

		/* always release the event */
		if (!fyp)
			fy_eventp_release(fyep);
		else
			fy_parse_eventp_recycle(fyp, fyep);

		if (ret)
			break;
	}

	return ret;
}

int fy_emit_event(struct fy_emitter *emit, struct fy_event *fye)
{
	return fy_emit_event_from_parser(emit, NULL, fye);
}

struct fy_document_state *
fy_emitter_get_document_state(struct fy_emitter *emit)
{
	return emit ? emit->fyds : NULL;
}

/* ANSI colors and escapes */
#define A_RESET			"\x1b[0m"
#define A_BLACK			"\x1b[30m"
#define A_RED			"\x1b[31m"
#define A_GREEN			"\x1b[32m"
#define A_YELLOW		"\x1b[33m"
#define A_BLUE			"\x1b[34m"
#define A_MAGENTA		"\x1b[35m"
#define A_CYAN			"\x1b[36m"
#define A_LIGHT_GRAY		"\x1b[37m"	/* dark white is gray */
#define A_GRAY			"\x1b[1;30m"
#define A_BRIGHT_RED		"\x1b[1;31m"
#define A_BRIGHT_GREEN		"\x1b[1;32m"
#define A_BRIGHT_YELLOW		"\x1b[1;33m"
#define A_BRIGHT_BLUE		"\x1b[1;34m"
#define A_BRIGHT_MAGENTA	"\x1b[1;35m"
#define A_BRIGHT_CYAN		"\x1b[1;36m"
#define A_WHITE			"\x1b[1;37m"

static const char *default_colors[FYEWT_COUNT] = {
	[fyewt_document_indicator] 		= A_CYAN,
	[fyewt_tag_directive]			= A_YELLOW,
	[fyewt_version_directive]		= A_YELLOW,
	[fyewt_indent]				= A_GREEN,	// when visible only
	[fyewt_indicator]			= A_YELLOW,	// in extended mode we produce more
	[fyewt_whitespace]			= A_GREEN,	// when visible only
	[fyewt_plain_scalar]			= A_WHITE,
	[fyewt_single_quoted_scalar]		= A_YELLOW,
	[fyewt_double_quoted_scalar]		= A_YELLOW,
	[fyewt_literal_scalar]			= A_YELLOW,
	[fyewt_folded_scalar]			= A_YELLOW,
	[fyewt_anchor]				= A_BRIGHT_GREEN,
	[fyewt_tag]				= A_BRIGHT_GREEN,
	[fyewt_linebreak]			= A_GREEN,	// when visible only
	[fyewt_alias]				= A_BRIGHT_GREEN,
	[fyewt_terminating_zero]		= A_BRIGHT_RED,	// when visible only
	[fyewt_plain_scalar_key]		= A_BRIGHT_CYAN,
	[fyewt_single_quoted_scalar_key]	= A_BRIGHT_CYAN,
	[fyewt_double_quoted_scalar_key]	= A_BRIGHT_CYAN,
	[fyewt_comment]				= A_BRIGHT_BLUE,
	// extended
	[fyewt_indicator_question_mark]		= A_MAGENTA,
	[fyewt_indicator_colon]			= A_MAGENTA,
	[fyewt_indicator_dash]			= A_MAGENTA,
	[fyewt_indicator_left_bracket]		= A_MAGENTA,
	[fyewt_indicator_right_bracket]		= A_MAGENTA,
	[fyewt_indicator_left_brace]		= A_MAGENTA,
	[fyewt_indicator_right_brace]		= A_MAGENTA,
	[fyewt_indicator_comma]			= A_MAGENTA,
	[fyewt_indicator_bar]			= A_MAGENTA,
	[fyewt_indicator_greater]		= A_MAGENTA,
	[fyewt_indicator_single_quote_start]	= A_YELLOW,
	[fyewt_indicator_single_quote_end]	= A_YELLOW,
	[fyewt_indicator_double_quote_start]	= A_YELLOW,
	[fyewt_indicator_double_quote_end]	= A_YELLOW,
	[fyewt_indicator_ambersand]		= A_BRIGHT_GREEN,
	[fyewt_indicator_star]			= A_MAGENTA,
	[fyewt_indicator_chomp]			= A_MAGENTA,
	[fyewt_indicator_explicit_indent]	= A_MAGENTA,
};

static void fy_emitter_fill_default_colors(struct fy_emitter *fye)
{
	unsigned int i;

	for (i = 0; i < FYEWT_COUNT; i++) {
		if (!fye->xcfg.colors[i])
			fye->xcfg.colors[i] = default_colors[i];
	}
}

static FILE *fy_emitter_get_output_fp(struct fy_emitter *fye)
{
	switch (fye->xcfg.xflags & (FYEXCF_OUTPUT_MASK << FYEXCF_OUTPUT_SHIFT)) {
	case FYEXCF_OUTPUT_STDOUT:
		return stdout;
	case FYEXCF_OUTPUT_STDERR:
		return stderr;
	case FYEXCF_OUTPUT_FILE:
		return fye->xcfg.output_fp;
	case FYEXCF_OUTPUT_FILENAME:
		if (!fye->owned_output_fp && fye->xcfg.output_filename)
			fye->owned_output_fp = fopen(fye->xcfg.output_filename, "wb");
		return fye->owned_output_fp;
	default:
		break;
	}
	return NULL;
}

static int fy_emitter_get_output_fd(struct fy_emitter *fye)
{
	switch (fye->xcfg.xflags & (FYEXCF_OUTPUT_MASK << FYEXCF_OUTPUT_SHIFT)) {
	case FYEXCF_OUTPUT_STDOUT:
		return STDOUT_FILENO;
	case FYEXCF_OUTPUT_STDERR:
		return STDERR_FILENO;
	case FYEXCF_OUTPUT_FD:
		return fye->xcfg.output_fd;
	default:
		break;
	}
	return -1;
}


static int fy_emitter_null_output(struct fy_emitter *fye, enum fy_emitter_write_type type, const char *str, int len, void *userdata)
{
	return len;
}

static inline ssize_t raw_default_output(FILE *fp, int fd, const char *data, size_t len)
{
	ssize_t wrn, twrn;

	if (fp)
		return fwrite(data, 1, len, fp);

	wrn = 0;
	if (fd >= 0) {
		while (len > 0) {
			do {
				twrn = write(fd, data, len);
			} while (twrn == -1 && errno == EAGAIN);
			if (twrn <= 0)
				return wrn;
			data += twrn;
			len -= (size_t)twrn;
			wrn += twrn;
		}
	}
	return wrn;
}

int fy_emitter_default_output(struct fy_emitter *fye, enum fy_emitter_write_type type, const char *str, int len, void *userdata)
{
	struct fy_emitter_default_output_data d_local, *d;
	FILE *fp;
	int fd;
	int ret, w;
	const char *color = NULL;
	const char *s, *e;
	const char *visible;

	d = userdata;
	if (!d) {
		d = &d_local;
		fp = fye->output_fp;
		fd = fye->output_fd;
		d->colorize = fye->output_colorize;
		d->visible = !!(fye->xcfg.xflags & FYEXCF_VISIBLE_WS);
	} else {
		fp = d->fp;
		fd = -1;
	}

	if (!fp && fd < 0)
		return len;

	s = str;
	e = str + len;

	if (d->colorize) {

		color = fye->xcfg.colors[type];

		/* visible whitespace */
		switch (type) {
		case fyewt_indent:
		case fyewt_whitespace:
		case fyewt_linebreak:
		case fyewt_terminating_zero:

			/* if not visible don't bother */
			if (!d->visible) {
				color = NULL;
				break;
			}

			if (color)
				raw_default_output(fp, fd, color, strlen(color));

			switch (type) {
			case fyewt_indent:
				/* open box - U+2423 */
				visible = "\xe2\x90\xa3";
				break;
			case fyewt_whitespace:
				/* symbol for space - U+2420 */
				/* symbol for interpunct - U+00B7 */
				visible = "\xc2\xb7";
				break;
			case fyewt_linebreak:
				/* down arrow - U+2193 */
				visible = "\xe2\x86\x93" "\n";
				break;
			case fyewt_terminating_zero:
				visible = "\\0";
				break;
			default:
				visible = "";	/* should never happen */
				break;

			}

			while (s < e && (w = fy_utf8_width_by_first_octet(((uint8_t)*s))) > 0) {
				raw_default_output(fp, fd, visible, strlen(visible));
				s += w;
			}

			if (color)
				raw_default_output(fp, fd, A_RESET, strlen(A_RESET));
			return len;

		default:
			break;
		}
	} else
		color = NULL;

	/* don't output the terminating zero */
	if (type == fyewt_terminating_zero)
		return len;

	if (color)
		raw_default_output(fp, fd, color, strlen(color));

	ret = (int)raw_default_output(fp, fd, str, len);

	if (color)
		raw_default_output(fp, fd, A_RESET, strlen(A_RESET));

	return ret;
}

int fy_document_default_emit_to_fp(struct fy_document *fyd, FILE *fp)
{
	struct fy_emitter emit_local, *emit = &emit_local;
	struct fy_emitter_cfg ecfg_local, *ecfg = &ecfg_local;
	struct fy_emitter_default_output_data d_local, *d = &d_local;
	int rc;

	memset(d, 0, sizeof(*d));
	d->fp = fp;
	d->colorize = isatty(fileno(fp));
	d->visible = false;

	memset(ecfg, 0, sizeof(*ecfg));
	ecfg->diag = fyd->diag;
	ecfg->userdata = d;

	rc = fy_emit_setup(emit, ecfg);
	if (rc)
		goto err_setup;

	fy_emit_prepare_document_state(emit, fyd->fyds);

	rc = 0;
	if (fyd->root)
		rc = fy_emit_node_check(emit, fyd->root);

	rc = fy_emit_document_no_check(emit, fyd);
	if (rc)
		goto err_emit;

	fy_emit_cleanup(emit);

	return 0;

err_emit:
	fy_emit_cleanup(emit);
err_setup:
	return -1;
}

int fy_emit_body_node(struct fy_emitter *emit, struct fy_node *fyn)
{
	struct fy_document_iterator *fydi = NULL;
	struct fy_event *fye;
	struct fy_document_iterator_cfg cfg;
	int rc;

	memset(&cfg, 0, sizeof(cfg));
	cfg.flags = FYDICF_WANT_BODY_EVENTS;
	cfg.fyd = fyn->fyd;
	cfg.iterate_root = fyn;

	fydi = fy_document_iterator_create_cfg(&cfg);
	if (!fydi)
		goto err_out;

	while ((fye = fy_document_iterator_generate_next(fydi)) != NULL) {
		rc = fy_emit_event(emit, fye);
		if (rc)
			goto err_out;
	}

	fy_document_iterator_destroy(fydi);
	return 0;

err_out:
	fy_document_iterator_destroy(fydi);
	return -1;
}
