"""
HTML renderer for mistletoe.
"""

import re
import sys
from urllib.parse import quote

from mistletoe.renderers.base import BaseRenderer

if sys.version_info < (3, 4):
    from mistletoe import _html as html
else:
    import html


class HTMLRenderer(BaseRenderer):
    """HTML renderer class."""

    def __init__(self, find_blocks=None, find_spans=None):
        """Initialise the renderer

        :param find_blocks: override the default block tokens (classes or class paths)
        :param find_spans: override the default span tokens (classes or class paths)
        """
        super().__init__(find_blocks=find_blocks, find_spans=find_spans)
        self._suppress_ptag_stack = [False]
        # html.entities.html5 includes entitydefs not ending with ';',
        # CommonMark seems to hate them, so...
        self._stdlib_charref = html._charref
        _charref = re.compile(
            r"&(#[0-9]+;" r"|#[xX][0-9a-fA-F]+;" r"|[^\t\n\f <&#;]{1,32};)"
        )
        html._charref = _charref

    def __exit__(self, *args):
        super().__exit__(*args)
        html._charref = self._stdlib_charref

    def render_to_plain(self, token):
        if token.children is not None:
            inner = [self.render_to_plain(child) for child in token.children]
            return "".join(inner)
        return self.escape_html(token.content)

    def render_strong(self, token):
        template = "<strong>{}</strong>"
        return template.format(self.render_inner(token))

    def render_emphasis(self, token):
        template = "<em>{}</em>"
        return template.format(self.render_inner(token))

    def render_inline_code(self, token):
        template = "<code>{}</code>"
        inner = html.escape(token.children[0].content)
        return template.format(inner)

    def render_strikethrough(self, token):
        template = "<del>{}</del>"
        return template.format(self.render_inner(token))

    def render_image(self, token):
        template = '<img src="{}" alt="{}"{} />'
        if token.title:
            title = ' title="{}"'.format(self.escape_html(token.title))
        else:
            title = ""
        return template.format(token.src, self.render_to_plain(token), title)

    def render_link(self, token):
        template = '<a href="{target}"{title}>{inner}</a>'
        target = self.escape_url(token.target)
        if token.title:
            title = ' title="{}"'.format(self.escape_html(token.title))
        else:
            title = ""
        inner = self.render_inner(token)
        return template.format(target=target, title=title, inner=inner)

    def render_auto_link(self, token):
        template = '<a href="{target}">{inner}</a>'
        if token.mailto:
            target = "mailto:{}".format(token.target)
        else:
            target = self.escape_url(token.target)
        inner = self.render_inner(token)
        return template.format(target=target, inner=inner)

    def render_escape_sequence(self, token):
        return self.render_inner(token)

    def render_raw_text(self, token):
        return self.escape_html(token.content)

    @staticmethod
    def render_html_span(token):
        return token.content

    def render_heading(self, token):
        template = "<h{level}>{inner}</h{level}>"
        inner = self.render_inner(token)
        return template.format(level=token.level, inner=inner)

    def render_quote(self, token):
        elements = ["<blockquote>"]
        self._suppress_ptag_stack.append(False)
        elements.extend([self.render(child) for child in token.children])
        self._suppress_ptag_stack.pop()
        elements.append("</blockquote>")
        return "\n".join(elements)

    def render_paragraph(self, token):
        if self._suppress_ptag_stack[-1]:
            return "{}".format(self.render_inner(token))
        return "<p>{}</p>".format(self.render_inner(token))

    def render_block_code(self, token):
        template = "<pre><code{attr}>{inner}</code></pre>"
        if token.language:
            attr = ' class="{}"'.format(
                "language-{}".format(self.escape_html(token.language))
            )
        else:
            attr = ""
        inner = html.escape(token.children[0].content)
        return template.format(attr=attr, inner=inner)

    def render_list(self, token):
        template = "<{tag}{attr}>\n{inner}\n</{tag}>"
        if token.start_at is not None:
            tag = "ol"
            attr = ' start="{}"'.format(token.start_at) if token.start_at != 1 else ""
        else:
            tag = "ul"
            attr = ""
        self._suppress_ptag_stack.append(not token.loose)
        inner = "\n".join([self.render(child) for child in token.children])
        self._suppress_ptag_stack.pop()
        return template.format(tag=tag, attr=attr, inner=inner)

    def render_list_item(self, token):
        if len(token.children) == 0:
            return "<li></li>"
        inner = "\n".join([self.render(child) for child in token.children])
        inner_template = "\n{}\n"
        if self._suppress_ptag_stack[-1]:
            if token.children[0].__class__.__name__ == "Paragraph":
                inner_template = inner_template[1:]
            if token.children[-1].__class__.__name__ == "Paragraph":
                inner_template = inner_template[:-1]
        return "<li>{}</li>".format(inner_template.format(inner))

    def render_table(self, token):
        # This is actually gross and I wonder if there's a better way to do it.
        #
        # The primary difficulty seems to be passing down alignment options to
        # reach individual cells.
        template = "<table>\n{inner}</table>"
        if hasattr(token, "header"):
            head_template = "<thead>\n{inner}</thead>\n"
            head_inner = self.render_table_row(token.header, is_header=True)
            head_rendered = head_template.format(inner=head_inner)
        else:
            head_rendered = ""
        body_template = "<tbody>\n{inner}</tbody>\n"
        body_inner = self.render_inner(token)
        body_rendered = body_template.format(inner=body_inner)
        return template.format(inner=head_rendered + body_rendered)

    def render_table_row(self, token, is_header=False):
        template = "<tr>\n{inner}</tr>\n"
        inner = "".join(
            [self.render_table_cell(child, is_header) for child in token.children]
        )
        return template.format(inner=inner)

    def render_table_cell(self, token, in_header=False):
        template = "<{tag}{attr}>{inner}</{tag}>\n"
        tag = "th" if in_header else "td"
        if token.align is None:
            align = "left"
        elif token.align == 0:
            align = "center"
        elif token.align == 1:
            align = "right"
        attr = ' align="{}"'.format(align)
        inner = self.render_inner(token)
        return template.format(tag=tag, attr=attr, inner=inner)

    @staticmethod
    def render_thematic_break(token):
        return "<hr />"

    @staticmethod
    def render_line_break(token):
        return "\n" if token.soft else "<br />\n"

    @staticmethod
    def render_html_block(token):
        return token.content

    def render_document(self, token):
        self.link_definitions.update(token.link_definitions)
        inner = "\n".join([self.render(child) for child in token.children])
        return "{}\n".format(inner) if inner else ""

    @staticmethod
    def escape_html(raw):
        return html.escape(html.unescape(raw)).replace("&#x27;", "'")

    @staticmethod
    def escape_url(raw):
        """
        Escape urls to prevent code injection craziness. (Hopefully.)
        """
        return html.escape(quote(html.unescape(raw), safe="/#:()*?=%@+,&"))
