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

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

#define safe_strlen(s) cmark_strbuf_safe_strlen(s)
#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)

static inline void outc(cmark_renderer *renderer,
                        cmark_escaping escape,
                        int32_t c,
                        unsigned char nextc)
{
	if (escape == LITERAL) {
		cmark_render_code_point(renderer, c);
		return;
	}

	switch(c) {
	case 123: // '{'
	case 125: // '}'
	case 35: // '#'
	case 37: // '%'
	case 38: // '&'
		cmark_render_ascii(renderer, "\\");
		cmark_render_code_point(renderer, c);
		break;
	case 36: // '$'
	case 95: // '_'
		if (escape == NORMAL) {
			cmark_render_ascii(renderer, "\\");
		}
		cmark_render_code_point(renderer, c);
		break;
	case 45 : // '-'
		if (nextc == 45) { // prevent ligature
			cmark_render_ascii(renderer, "\\-");
		} else {
			cmark_render_ascii(renderer, "-");
		}
		break;
	case 126: // '~'
		if (escape == NORMAL) {
			cmark_render_ascii(renderer, "\\textasciitilde{}");
		} else {
			cmark_render_code_point(renderer, c);
		}
		break;
	case 94: // '^'
		cmark_render_ascii(renderer, "\\^{}");
		break;
	case 92: // '\\'
		if (escape == URL) {
			// / acts as path sep even on windows:
			cmark_render_ascii(renderer, "/");
		} else {
			cmark_render_ascii(renderer, "\\textbackslash{}");
		}
		break;
	case 124: // '|'
		cmark_render_ascii(renderer, "\\textbar{}");
		break;
	case 60: // '<'
		cmark_render_ascii(renderer, "\\textless{}");
		break;
	case 62: // '>'
		cmark_render_ascii(renderer, "\\textgreater{}");
		break;
	case 91: // '['
	case 93: // ']'
		cmark_render_ascii(renderer, "{");
		cmark_render_code_point(renderer, c);
		cmark_render_ascii(renderer, "}");
		break;
	case 34: // '"'
		cmark_render_ascii(renderer, "\\textquotedbl{}");
		// requires \usepackage[T1]{fontenc}
		break;
	case 39: // '\''
		cmark_render_ascii(renderer, "\\textquotesingle{}");
		// requires \usepackage{textcomp}
		break;
	case 160: // nbsp
		cmark_render_ascii(renderer, "~");
		break;
	case 8230: // hellip
		cmark_render_ascii(renderer, "\\ldots{}");
		break;
	case 8216: // lsquo
		if (escape == NORMAL) {
			cmark_render_ascii(renderer, "`");
		} else {
			cmark_render_code_point(renderer, c);
		}
		break;
	case 8217: // rsquo
		if (escape == NORMAL) {
			cmark_render_ascii(renderer, "\'");
		} else {
			cmark_render_code_point(renderer, c);
		}
		break;
	case 8220: // ldquo
		if (escape == NORMAL) {
			cmark_render_ascii(renderer, "``");
		} else {
			cmark_render_code_point(renderer, c);
		}
		break;
	case 8221: // rdquo
		if (escape == NORMAL) {
			cmark_render_ascii(renderer, "''");
		} else {
			cmark_render_code_point(renderer, c);
		}
		break;
	case 8212: // emdash
		if (escape == NORMAL) {
			cmark_render_ascii(renderer, "---");
		} else {
			cmark_render_code_point(renderer, c);
		}
		break;
	case 8211: // endash
		if (escape == NORMAL) {
			cmark_render_ascii(renderer, "--");
		} else {
			cmark_render_code_point(renderer, c);
		}
		break;
	default:
		cmark_render_code_point(renderer, c);
	}
}

typedef enum  {
	NO_LINK,
	URL_AUTOLINK,
	EMAIL_AUTOLINK,
	NORMAL_LINK
} link_type;

static link_type
get_link_type(cmark_node *node)
{
	size_t title_len, url_len;
	cmark_node *link_text;
	char *realurl;
	int realurllen;
	bool isemail = false;

	if (node->type != CMARK_NODE_LINK) {
		return NO_LINK;
	}

	const char* url = cmark_node_get_url(node);
	cmark_chunk url_chunk = cmark_chunk_literal(url);

	url_len = safe_strlen(url);
	if (url_len == 0 || scan_scheme(&url_chunk, 0) == 0) {
		return NO_LINK;
	}

	const char* title = cmark_node_get_title(node);
	title_len = safe_strlen(title);
	// if it has a title, we can't treat it as an autolink:
	if (title_len > 0) {
		return NORMAL_LINK;
	}

	link_text = node->first_child;
	cmark_consolidate_text_nodes(link_text);
	realurl = (char*)url;
	realurllen = url_len;
	if (strncmp(realurl, "mailto:", 7) == 0) {
		realurl += 7;
		realurllen -= 7;
		isemail = true;
	}
	if (realurllen == link_text->as.literal.len &&
	    strncmp(realurl,
	            (char*)link_text->as.literal.data,
	            link_text->as.literal.len) == 0) {
		if (isemail) {
			return EMAIL_AUTOLINK;
		} else {
			return URL_AUTOLINK;
		}
	} else {
		return NORMAL_LINK;
	}
}

static int
S_get_enumlevel(cmark_node *node)
{
	int enumlevel = 0;
	cmark_node *tmp = node;
	while (tmp) {
		if (tmp->type == CMARK_NODE_LIST &&
		    cmark_node_get_list_type(node) == CMARK_ORDERED_LIST) {
			enumlevel++;
		}
		tmp = tmp->parent;
	}
	return enumlevel;
}

static int
S_render_node(cmark_renderer *renderer,
              cmark_node *node,
              cmark_event_type ev_type,
              int options)
{
	int list_number;
	char list_number_string[20];
	bool entering = (ev_type == CMARK_EVENT_ENTER);
	cmark_list_type list_type;
	const char* roman_numerals[] = { "", "i", "ii", "iii", "iv", "v",
	                                 "vi", "vii", "viii", "ix", "x"
	                               };

	// avoid warning about unused parameter:
	(void)(options);

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

	case CMARK_NODE_BLOCK_QUOTE:
		if (entering) {
			LIT("\\begin{quote}");
			CR();
		} else {
			LIT("\\end{quote}");
			BLANKLINE();
		}
		break;

	case CMARK_NODE_LIST:
		list_type = cmark_node_get_list_type(node);
		if (entering) {
			LIT("\\begin{");
			LIT(list_type == CMARK_ORDERED_LIST ?
			    "enumerate" : "itemize");
			LIT("}");
			CR();
			list_number = cmark_node_get_list_start(node);
			if (list_number > 1) {
				sprintf(list_number_string,
				        "%d", list_number);
				LIT("\\setcounter{enum");
				LIT((char *)roman_numerals[S_get_enumlevel(node)]);
				LIT("}{");
				OUT(list_number_string, false, NORMAL);
				LIT("}");
				CR();
			}
		} else {
			LIT("\\end{");
			LIT(list_type == CMARK_ORDERED_LIST ?
			    "enumerate" : "itemize");
			LIT("}");
			BLANKLINE();
		}
		break;

	case CMARK_NODE_ITEM:
		if (entering) {
			LIT("\\item ");
		} else {
			CR();
		}
		break;

	case CMARK_NODE_HEADER:
		if (entering) {
			switch (cmark_node_get_header_level(node)) {
			case 1:
				LIT("\\section");
				break;
			case 2:
				LIT("\\subsection");
				break;
			case 3:
				LIT("\\subsubsection");
				break;
			case 4:
				LIT("\\paragraph");
				break;
			case 5:
				LIT("\\subparagraph");
				break;
			}
			LIT("{");
		} else {
			LIT("}");
			BLANKLINE();
		}
		break;

	case CMARK_NODE_CODE_BLOCK:
		CR();
		LIT("\\begin{verbatim}");
		CR();
		OUT(cmark_node_get_literal(node), false, LITERAL);
		CR();
		LIT("\\end{verbatim}");
		BLANKLINE();
		break;

	case CMARK_NODE_HTML:
		break;

	case CMARK_NODE_HRULE:
		BLANKLINE();
		LIT("\\begin{center}\\rule{0.5\\linewidth}{\\linethickness}\\end{center}");
		BLANKLINE();
		break;

	case CMARK_NODE_PARAGRAPH:
		if (!entering) {
			BLANKLINE();
		}
		break;

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

	case CMARK_NODE_LINEBREAK:
		LIT("\\\\");
		CR();
		break;

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

	case CMARK_NODE_CODE:
		LIT("\\texttt{");
		OUT(cmark_node_get_literal(node), false, NORMAL);
		LIT("}");
		break;

	case CMARK_NODE_INLINE_HTML:
		break;

	case CMARK_NODE_STRONG:
		if (entering) {
			LIT("\\textbf{");
		} else {
			LIT("}");
		}
		break;

	case CMARK_NODE_EMPH:
		if (entering) {
			LIT("\\emph{");
		} else {
			LIT("}");
		}
		break;

	case CMARK_NODE_LINK:
		if (entering) {
			const char* url = cmark_node_get_url(node);
			// requires \usepackage{hyperref}
			switch(get_link_type(node)) {
			case URL_AUTOLINK:
				LIT("\\url{");
				OUT(url, false, URL);
				break;
			case EMAIL_AUTOLINK:
				LIT("\\href{");
				OUT(url, false, URL);
				LIT("}\\nolinkurl{");
				break;
			case NORMAL_LINK:
				LIT("\\href{");
				OUT(url, false, URL);
				LIT("}{");
				break;
			case NO_LINK:
				LIT("{");  // error?
			}
		} else {
			LIT("}");
		}

		break;

	case CMARK_NODE_IMAGE:
		if (entering) {
			LIT("\\protect\\includegraphics{");
			// requires \include{graphicx}
			OUT(cmark_node_get_url(node), false, URL);
			LIT("}");
			return 0;
		}
		break;

	default:
		assert(false);
		break;
	}

	return 1;
}

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