#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "config.h"
#include "cmark.h"
#include "node.h"
#include "buffer.h"
#include "utf8.h"
#include "render.h"

#define OUT(s, wrap, escaping) renderer->out(renderer, s, wrap, escaping)
#define LIT(s) renderer->out(renderer, s, false, LITERAL)
#define CR() renderer->cr(renderer)
#define BLANKLINE() renderer->blankline(renderer)

// Functions to convert cmark_nodes to groff man strings.
static
void S_outc(cmark_renderer *renderer,
            cmark_escaping escape,
            int32_t c,
            unsigned char nextc)
{
	(void)(nextc);

	if (escape == LITERAL) {
		cmark_render_code_point(renderer, c);
		return;
	}

	switch(c) {
	case 46:
		if (renderer->begin_line) {
			cmark_render_ascii(renderer, "\\&.");
		} else {
			cmark_render_code_point(renderer, c);
		}
		break;
	case 39:
		if (renderer->begin_line) {
			cmark_render_ascii(renderer, "\\&'");
		} else {
			cmark_render_code_point(renderer, c);
		}
		break;
	case 45:
		cmark_render_ascii(renderer, "\\-");
		break;
	case 92:
		cmark_render_ascii(renderer, "\\e");
		break;
	case 8216: // left single quote
		cmark_render_ascii(renderer, "\\[oq]");
		break;
	case 8217: // right single quote
		cmark_render_ascii(renderer, "\\[cq]");
		break;
	case 8220: // left double quote
		cmark_render_ascii(renderer, "\\[lq]");
		break;
	case 8221: // right double quote
		cmark_render_ascii(renderer, "\\[rq]");
		break;
	case 8212: // em dash
		cmark_render_ascii(renderer, "\\[em]");
		break;
	case 8211: // en dash
		cmark_render_ascii(renderer, "\\[en]");
		break;
	default:
		cmark_render_code_point(renderer, c);
	}
}

static int
S_render_node(cmark_renderer *renderer,
              cmark_node *node,
              cmark_event_type ev_type,
              int options)
{
	cmark_node *tmp;
	int list_number;
	bool entering = (ev_type == CMARK_EVENT_ENTER);

	// avoid unused parameter error:
	(void)(options);

	switch (node->type) {
	case CMARK_NODE_DOCUMENT:
		break;

	case CMARK_NODE_BLOCK_QUOTE:
		if (entering) {
			CR();
			LIT(".RS");
			CR();
		} else {
			CR();
			LIT(".RE");
			CR();
		}
		break;

	case CMARK_NODE_LIST:
		break;

	case CMARK_NODE_ITEM:
		if (entering) {
			CR();
			LIT(".IP ");
			if (cmark_node_get_list_type(node->parent) ==
			    CMARK_BULLET_LIST) {
				LIT("\\[bu] 2");
			} else {
				list_number = cmark_node_get_list_start(node->parent);
				tmp = node;
				while (tmp->prev) {
					tmp = tmp->prev;
					list_number += 1;
				}
				char list_number_s[20];
				sprintf(list_number_s, "\"%d.\" 4", list_number);
				LIT(list_number_s);
			}
			CR();
		} else {
			CR();
		}
		break;

	case CMARK_NODE_HEADER:
		if (entering) {
			CR();
			LIT(cmark_node_get_header_level(node) == 1 ?
			    ".SH" : ".SS");
			CR();
		} else {
			CR();
		}
		break;

	case CMARK_NODE_CODE_BLOCK:
		CR();
		LIT(".IP\n.nf\n\\f[C]\n");
		OUT(cmark_node_get_literal(node),
		    false,
		    NORMAL);
		CR();
		LIT("\\f[]\n.fi");
		CR();
		break;

	case CMARK_NODE_HTML:
		break;

	case CMARK_NODE_HRULE:
		CR();
		LIT(".PP\n  *  *  *  *  *");
		CR();
		break;

	case CMARK_NODE_PARAGRAPH:
		if (entering) {
			// no blank line if first paragraph in list:
			if (node->parent &&
			    node->parent->type == CMARK_NODE_ITEM &&
			    node->prev == NULL) {
				// no blank line or .PP
			} else {
				CR();
				LIT(".PP");
				CR();
			}
		} else {
			CR();
		}
		break;

	case CMARK_NODE_TEXT:
		OUT(cmark_node_get_literal(node), true, NORMAL);
		break;

	case CMARK_NODE_LINEBREAK:
		LIT(".PD 0\n.P\n.PD");
		CR();
		break;

	case CMARK_NODE_SOFTBREAK:
		if (renderer->width == 0) {
			CR();
		} else {
			OUT(" ", true, LITERAL);
		}
		break;

	case CMARK_NODE_CODE:
		LIT("\\f[C]");
		OUT(cmark_node_get_literal(node), true, NORMAL);
		LIT("\\f[]");
		break;

	case CMARK_NODE_INLINE_HTML:
		break;

	case CMARK_NODE_STRONG:
		if (entering) {
			LIT("\\f[B]");
		} else {
			LIT("\\f[]");
		}
		break;

	case CMARK_NODE_EMPH:
		if (entering) {
			LIT("\\f[I]");
		} else {
			LIT("\\f[]");
		}
		break;

	case CMARK_NODE_LINK:
		if (!entering) {
			LIT(" (");
			OUT(cmark_node_get_url(node), true, URL);
			LIT(")");
		}
		break;

	case CMARK_NODE_IMAGE:
		if (entering) {
			LIT("[IMAGE: ");
		} else {
			LIT("]");
		}
		break;

	default:
		assert(false);
		break;
	}

	return 1;
}

char *cmark_render_man(cmark_node *root, int options, int width)
{
	return cmark_render(root, options, width, S_outc, S_render_node);
}