text: /*	$Id$ */
  /*
   * Copyright (c) 2017--2021 Kristaps Dzonsons <kristaps@bsd.lv>
   *
   * Permission to use, copy, modify, and distribute this software for any
   * purpose with or without fee is hereby granted, provided that the above
   * copyright notice and this permission notice appear in all copies.
   *
   * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
   * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
   * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
   * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
   * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
   * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
   */
  #include "config.h"
  
  #if HAVE_SYS_QUEUE
  # include <sys/queue.h>
  #endif
  
  #include <assert.h>
  #include <ctype.h>
  #include <stdint.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  
  #include "lowdown.h"
  #include "extern.h"
  
  static	const char *const names[LOWDOWN__MAX] = {
  	"LOWDOWN_ROOT",			/* LOWDOWN_ROOT */
  	"LOWDOWN_BLOCKCODE",            /* LOWDOWN_BLOCKCODE */
  	"LOWDOWN_BLOCKQUOTE",           /* LOWDOWN_BLOCKQUOTE */
  	"LOWDOWN_DEFINITION",		/* LOWDOWN_DEFINITION */
  	"LOWDOWN_DEFINITION_TITLE",	/* LOWDOWN_DEFINITION_TITLE */
  	"LOWDOWN_DEFINITION_DATA",	/* LOWDOWN_DEFINITION_DATA */
  	"LOWDOWN_HEADER",               /* LOWDOWN_HEADER */
  	"LOWDOWN_HRULE",                /* LOWDOWN_HRULE */
  	"LOWDOWN_LIST",                 /* LOWDOWN_LIST */
  	"LOWDOWN_LISTITEM",             /* LOWDOWN_LISTITEM */
  	"LOWDOWN_PARAGRAPH",            /* LOWDOWN_PARAGRAPH */
  	"LOWDOWN_TABLE_BLOCK",          /* LOWDOWN_TABLE_BLOCK */
  	"LOWDOWN_TABLE_HEADER",         /* LOWDOWN_TABLE_HEADER */
  	"LOWDOWN_TABLE_BODY",           /* LOWDOWN_TABLE_BODY */
  	"LOWDOWN_TABLE_ROW",            /* LOWDOWN_TABLE_ROW */
  	"LOWDOWN_TABLE_CELL",           /* LOWDOWN_TABLE_CELL */
  	"LOWDOWN_BLOCKHTML",            /* LOWDOWN_BLOCKHTML */
  	"LOWDOWN_LINK_AUTO",            /* LOWDOWN_LINK_AUTO */
  	"LOWDOWN_CODESPAN",             /* LOWDOWN_CODESPAN */
  	"LOWDOWN_DOUBLE_EMPHASIS",      /* LOWDOWN_DOUBLE_EMPHASIS */
  	"LOWDOWN_EMPHASIS",             /* LOWDOWN_EMPHASIS */
  	"LOWDOWN_HIGHLIGHT",            /* LOWDOWN_HIGHLIGHT */
  	"LOWDOWN_IMAGE",                /* LOWDOWN_IMAGE */
  	"LOWDOWN_LINEBREAK",            /* LOWDOWN_LINEBREAK */
  	"LOWDOWN_LINK",                 /* LOWDOWN_LINK */
  	"LOWDOWN_TRIPLE_EMPHASIS",      /* LOWDOWN_TRIPLE_EMPHASIS */
  	"LOWDOWN_STRIKETHROUGH",        /* LOWDOWN_STRIKETHROUGH */
  	"LOWDOWN_SUPERSCRIPT",          /* LOWDOWN_SUPERSCRIPT */
  	"LOWDOWN_FOOTNOTE",		/* LOWDOWN_FOOTNOTE */
  	"LOWDOWN_MATH_BLOCK",           /* LOWDOWN_MATH_BLOCK */
  	"LOWDOWN_RAW_HTML",             /* LOWDOWN_RAW_HTML */
  	"LOWDOWN_ENTITY",               /* LOWDOWN_ENTITY */
  	"LOWDOWN_NORMAL_TEXT",          /* LOWDOWN_NORMAL_TEXT */
  	"LOWDOWN_DOC_HEADER",           /* LOWDOWN_DOC_HEADER */
  	"LOWDOWN_META",			/* LOWDOWN_META */
  };
  
  static int
  rndr_indent(struct lowdown_buf *ob, size_t indent)
  {
  	size_t	 i;
  
  	for (i = 0; i < indent; i++)
  		if (!HBUF_PUTSL(ob, "  "))
  			return 0;
  	return 1;
  }
  
  static int
  rndr_short(struct lowdown_buf *ob, const struct lowdown_buf *b)
  {
  	size_t	 i;
  
  	for (i = 0; i < 20 && i < b->size; i++)
  		if (b->data[i] == '\n') {
  			if (!HBUF_PUTSL(ob, "\\n"))
  				return 0;
  		} else if (b->data[i] == '\t') {
  			if (!HBUF_PUTSL(ob, "\\t"))
  				return 0;
  		} else if (iscntrl((unsigned char)b->data[i])) {
  			if (!hbuf_putc(ob, '?'))
  				return 0;
  		} else {
  			if (!hbuf_putc(ob, b->data[i]))
  				return 0;
  		}
  
  	if (i < b->size && !HBUF_PUTSL(ob, "..."))
  		return 0;
  	return 1;
  }
  
  static int
  rndr(struct lowdown_buf *ob,
  	const struct lowdown_node *root, size_t indent)
  {
  	const struct lowdown_node	*n;
  	struct lowdown_buf		*tmp;
  
  	if (!rndr_indent(ob, indent))
  		return 0;
  	if (root->chng == LOWDOWN_CHNG_INSERT && 
  	    !HBUF_PUTSL(ob, "INSERT: "))
  		return 0;
  	if (root->chng == LOWDOWN_CHNG_DELETE && 
  	    !HBUF_PUTSL(ob, "DELETE: "))
  		return 0;
  	if (!hbuf_printf(ob, "%s (%zu)", names[root->type], root->id))
  		return 0;
  	if (!hbuf_putc(ob, '\n'))
  		return 0;
  
  	switch (root->type) {
  	case LOWDOWN_PARAGRAPH:
  		if (!rndr_indent(ob, indent + 1))
  			return 0;
  		if (!hbuf_printf(ob, "lines: %zu, blank-after: %d\n", 
  		    root->rndr_paragraph.lines,
  		    root->rndr_paragraph.beoln))
  			return 0;
  		break;
  	case LOWDOWN_IMAGE:
  		if (!rndr_indent(ob, indent + 1))
  			return 0;
  		if (!hbuf_printf(ob, "source: "))
  			return 0;
  		if (!rndr_short(ob, &root->rndr_image.link))
  			return 0;
  		if (root->rndr_image.dims.size) {
  			if (!HBUF_PUTSL(ob, "("))
  				return 0;
  			if (!rndr_short(ob, &root->rndr_image.dims))
  				return 0;
  			if (!HBUF_PUTSL(ob, ")"))
  				return 0;
  		}
  		if (!HBUF_PUTSL(ob, "\n"))
  			return 0;
  		if (root->rndr_image.title.size) {
  			if (!rndr_indent(ob, indent + 1))
  				return 0;
  			if (!hbuf_printf(ob, "title: "))
  				return 0;
  			if (!rndr_short(ob, &root->rndr_image.title))
  				return 0;
  			if (!HBUF_PUTSL(ob, "\n"))
  				return 0;
  		}
  		if (root->rndr_image.alt.size) {
  			if (!rndr_indent(ob, indent + 1))
  				return 0;
  			if (!hbuf_printf(ob, "alt: "))
  				return 0;
  			if (!rndr_short(ob, &root->rndr_image.alt))
  				return 0;
  			if (!HBUF_PUTSL(ob, "\n"))
  				return 0;
  		}
  		if (root->rndr_image.dims.size) {
  			if (!rndr_indent(ob, indent + 1))
  				return 0;
  			if (!hbuf_printf(ob, "dims: "))
  				return 0;
  			if (!rndr_short(ob, &root->rndr_image.dims))
  				return 0;
  			if (!HBUF_PUTSL(ob, "\n"))
  				return 0;
  		}
  		if (root->rndr_image.attr_width.size) {
  			if (!rndr_indent(ob, indent + 1))
  				return 0;
  			if (!hbuf_printf(ob, "width (extended): "))
  				return 0;
  			if (!rndr_short(ob, &root->rndr_image.attr_width))
  				return 0;
  			if (!HBUF_PUTSL(ob, "\n"))
  				return 0;
  		}
  		if (root->rndr_image.attr_height.size) {
  			if (!rndr_indent(ob, indent + 1))
  				return 0;
  			if (!hbuf_printf(ob, "height (extended): "))
  				return 0;
  			if (!rndr_short(ob, &root->rndr_image.attr_height))
  				return 0;
  			if (!HBUF_PUTSL(ob, "\n"))
  				return 0;
  		}
  		if (root->rndr_image.attr_cls.size > 0) {
  			if (!rndr_indent(ob, indent + 1))
  				return 0;
  			if (!HBUF_PUTSL(ob, "class: "))
  				return 0;
  			if (!hbuf_putb(ob, &root->rndr_image.attr_cls))
  				return 0;
  			if (!HBUF_PUTSL(ob, "\n"))
  				return 0;
  		}
  		if (root->rndr_image.attr_id.size > 0) {
  			if (!rndr_indent(ob, indent + 1))
  				return 0;
  			if (!HBUF_PUTSL(ob, "id: "))
  				return 0;
  			if (!hbuf_putb(ob, &root->rndr_image.attr_id))
  				return 0;
  			if (!HBUF_PUTSL(ob, "\n"))
  				return 0;
  		}
  		break;
  	case LOWDOWN_HEADER:
  		if (!rndr_indent(ob, indent + 1))
  			return 0;
  		if (!hbuf_printf(ob, "level: %zu\n",
  		    root->rndr_header.level))
  			return 0;
  		if (root->rndr_header.attr_cls.size > 0) {
  			if (!rndr_indent(ob, indent + 1))
  				return 0;
  			if (!HBUF_PUTSL(ob, "class: "))
  				return 0;
  			if (!hbuf_putb(ob, &root->rndr_header.attr_cls))
  				return 0;
  			if (!HBUF_PUTSL(ob, "\n"))
  				return 0;
  		}
  		if (root->rndr_header.attr_id.size > 0) {
  			if (!rndr_indent(ob, indent + 1))
  				return 0;
  			if (!HBUF_PUTSL(ob, "id: "))
  				return 0;
  			if (!hbuf_putb(ob, &root->rndr_header.attr_id))
  				return 0;
  			if (!HBUF_PUTSL(ob, "\n"))
  				return 0;
  		}
  		break;
  	case LOWDOWN_RAW_HTML:
  		if (!rndr_indent(ob, indent + 1))
  			return 0;
  		if (!hbuf_printf(ob, "data: %zu Bytes: ",
  		    root->rndr_raw_html.text.size))
  			return 0;
  		if (!rndr_short(ob, &root->rndr_raw_html.text))
  			return 0;
  		if (!HBUF_PUTSL(ob, "\n"))
  			return 0;
  		break;
  	case LOWDOWN_BLOCKHTML:
  		if (!rndr_indent(ob, indent + 1))
  			return 0;
  		if (!hbuf_printf(ob, "data: %zu Bytes: ",
  		    root->rndr_blockhtml.text.size))
  			return 0;
  		if (!rndr_short(ob, &root->rndr_blockhtml.text))
  			return 0;
  		if (!HBUF_PUTSL(ob, "\n"))
  			return 0;
  		break;
  	case LOWDOWN_BLOCKCODE:
  		if (!rndr_indent(ob, indent + 1))
  			return 0;
  		if (!hbuf_printf(ob, "data: %zu Bytes: ",
  		    root->rndr_blockcode.text.size))
  			return 0;
  		if (!rndr_short(ob, &root->rndr_blockcode.text))
  			return 0;
  		if (!HBUF_PUTSL(ob, "\n"))
  			return 0;
  		break;
  	case LOWDOWN_DEFINITION:
  		if (!rndr_indent(ob, indent + 1))
  			return 0;
  		if (!hbuf_printf(ob, "scope: %s\n",
  		    HLIST_FL_BLOCK & root->rndr_definition.flags ? 
  		    "block" : "span"))
  			return 0;
  		break;
  	case LOWDOWN_TABLE_BLOCK:
  		if (!rndr_indent(ob, indent + 1))
  			return 0;
  		if (!hbuf_printf(ob, "columns: %zu\n", 
  		    root->rndr_table.columns))
  			return 0;
  		break;
  	case LOWDOWN_TABLE_CELL:
  		if (!rndr_indent(ob, indent + 1))
  			return 0;
  		if (!hbuf_printf(ob, "current: %zu\n", 
  		    root->rndr_table_cell.col))
  			return 0;
  		break;
  	case LOWDOWN_LISTITEM:
  		if (!rndr_indent(ob, indent + 1))
  			return 0;
  		if (!hbuf_printf(ob, "scope: %s\n",
  		    (root->rndr_listitem.flags & HLIST_FL_BLOCK) ?
  		    "block" : "span"))
  			return 0;
  		if (!(root->rndr_listitem.flags &
  		     (HLIST_FL_CHECKED | HLIST_FL_UNCHECKED)))
  			break;
  		if (!rndr_indent(ob, indent + 1))
  			return 0;
  		if (!hbuf_printf(ob, "check status: %s\n",
  		    (root->rndr_listitem.flags & HLIST_FL_CHECKED) ?
  		    "checked" : "unchecked"))
  			return 0;
  		break;
  	case LOWDOWN_LIST:
  		if (!rndr_indent(ob, indent + 1))
  			return 0;
  		if (!hbuf_printf(ob, "list type: %s\n",
  		    HLIST_FL_ORDERED & root->rndr_list.flags ? 
  		    "ordered" : "unordered"))
  			return 0;
  		break;
  	case LOWDOWN_META:
  		if (!rndr_indent(ob, indent + 1))
  			return 0;
  		if (!hbuf_printf(ob, "key: "))
  			return 0;
  		if (!rndr_short(ob, &root->rndr_meta.key))
  			return 0;
  		if (!HBUF_PUTSL(ob, "\n"))
  			return 0;
  		break;
  	case LOWDOWN_MATH_BLOCK:
  		if (!rndr_indent(ob, indent + 1))
  			return 0;
  		if (!hbuf_printf(ob, "blockmode: %s\n",
  		    root->rndr_math.blockmode ?
  		    "block" : "inline"))
  			return 0;
  		if (!rndr_indent(ob, indent + 1))
  			return 0;
  		if (!hbuf_printf(ob, "data: %zu Bytes: ",
  		    root->rndr_math.text.size))
  			return 0;
  		if (!rndr_short(ob, &root->rndr_math.text))
  			return 0;
  		if (!HBUF_PUTSL(ob, "\n"))
  			return 0;
  		break;
  	case LOWDOWN_ENTITY:
  		if (!rndr_indent(ob, indent + 1))
  			return 0;
  		if (!hbuf_printf(ob, "value: "))
  			return 0;
  		if (!rndr_short(ob, &root->rndr_entity.text))
  			return 0;
  		if (!HBUF_PUTSL(ob, "\n"))
  			return 0;
  		break;
  	case LOWDOWN_LINK_AUTO:
  		if (root->rndr_autolink.link.size) {
  			if (!rndr_indent(ob, indent + 1))
  				return 0;
  			if (!HBUF_PUTSL(ob, "link: "))
  				return 0;
  			if (!rndr_short(ob, &root->rndr_autolink.link))
  				return 0;
  			if (!HBUF_PUTSL(ob, "\n"))
  				return 0;
  		}
  		break;
  	case LOWDOWN_LINK:
  		if (root->rndr_link.title.size) {
  			if (!rndr_indent(ob, indent + 1))
  				return 0;
  			if (!HBUF_PUTSL(ob, "title: "))
  				return 0;
  			if (!rndr_short(ob, &root->rndr_link.title))
  				return 0;
  			if (!HBUF_PUTSL(ob, "\n"))
  				return 0;
  		}
  		if (root->rndr_link.link.size) {
  			if (!rndr_indent(ob, indent + 1))
  				return 0;
  			if (!HBUF_PUTSL(ob, "link: "))
  				return 0;
  			if (!rndr_short(ob, &root->rndr_link.link))
  				return 0;
  			if (!HBUF_PUTSL(ob, "\n"))
  				return 0;
  		}
  		if (root->rndr_link.attr_cls.size > 0) {
  			if (!rndr_indent(ob, indent + 1))
  				return 0;
  			if (!HBUF_PUTSL(ob, "class: "))
  				return 0;
  			if (!hbuf_putb(ob, &root->rndr_link.attr_cls))
  				return 0;
  			if (!HBUF_PUTSL(ob, "\n"))
  				return 0;
  		}
  		if (root->rndr_link.attr_id.size > 0) {
  			if (!rndr_indent(ob, indent + 1))
  				return 0;
  			if (!HBUF_PUTSL(ob, "id: "))
  				return 0;
  			if (!hbuf_putb(ob, &root->rndr_link.attr_id))
  				return 0;
  			if (!HBUF_PUTSL(ob, "\n"))
  				return 0;
  		}
  		break;
  	case LOWDOWN_NORMAL_TEXT:
  		if (!rndr_indent(ob, indent + 1))
  			return 0;
  		if (!hbuf_printf(ob, "data: %zu Bytes: ",
  		    root->rndr_normal_text.text.size))
  			return 0;
  		if (!rndr_short(ob, &root->rndr_normal_text.text))
  			return 0;
  		if (!HBUF_PUTSL(ob, "\n"))
  			return 0;
  		break;
  	default:
  		break;
  	}
  
  	if ((tmp = hbuf_new(64)) == NULL)
  		return 0;
  
  	TAILQ_FOREACH(n, &root->children, entries)
  		if (!rndr(tmp, n, indent + 1)) {
  			hbuf_free(tmp);
  			return 0;
  		}
  
  	hbuf_putb(ob, tmp);
  	hbuf_free(tmp);
  	return 1;
  }
  
  int
  lowdown_tree_rndr(struct lowdown_buf *ob,
  	const struct lowdown_node *root)
  {
  
  	return rndr(ob, root, 0);
  }
  
  