from textwrap import indent
from typing import Any, Dict, Iterable, List, cast

from ..core import BaseRenderer, BlockState
from ..util import strip_end
from ._list import render_list


class RSTRenderer(BaseRenderer):
    """A renderer for converting Markdown to ReST."""
    NAME = 'rst'

    #: marker symbols for heading
    HEADING_MARKERS = {
      1: '=',
      2: '-',
      3: '~',
      4: '^',
      5: '"',
      6: "'",
    }
    INLINE_IMAGE_PREFIX = 'img-'

    def iter_tokens(
        self, tokens: Iterable[Dict[str, Any]], state: BlockState
    ) -> Iterable[str]:
        prev = None
        for tok in tokens:
            # ignore blank line
            if tok['type'] == 'blank_line':
                continue
            tok['prev'] = prev
            prev = tok
            yield self.render_token(tok, state)

    def __call__(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
        state.env['inline_images'] = []
        out = self.render_tokens(tokens, state)
        # special handle for line breaks
        out += '\n\n'.join(self.render_referrences(state)) + '\n'
        return strip_end(out)

    def render_referrences(self, state: BlockState) -> Iterable[str]:
        images = state.env['inline_images']
        for index, token in enumerate(images):
            attrs = token['attrs']
            alt = self.render_children(token, state)
            ident = self.INLINE_IMAGE_PREFIX + str(index)
            yield '.. |' + ident + '| image:: ' + attrs['url'] + '\n   :alt: ' + alt

    def render_children(self, token: Dict[str, Any], state: BlockState) -> str:
        children = token['children']
        return self.render_tokens(children, state)

    def text(self, token: Dict[str, Any], state: BlockState) -> str:
        text = cast(str, token["raw"])
        return text.replace("|", r"\|")

    def emphasis(self, token: Dict[str, Any], state: BlockState) -> str:
        return '*' + self.render_children(token, state) + '*'

    def strong(self, token: Dict[str, Any], state: BlockState) -> str:
        return '**' + self.render_children(token, state) + '**'

    def link(self, token: Dict[str, Any], state: BlockState) -> str:
        attrs = token['attrs']
        text = self.render_children(token, state)
        return "`" + text + " <" + cast(str, attrs["url"]) + ">`__"

    def image(self, token: Dict[str, Any], state: BlockState) -> str:
        refs: List[Dict[str, Any]] = state.env["inline_images"]
        index = len(refs)
        refs.append(token)
        return '|' + self.INLINE_IMAGE_PREFIX + str(index) + '|'

    def codespan(self, token: Dict[str, Any], state: BlockState) -> str:
        return "``" + cast(str, token["raw"]) + "``"

    def linebreak(self, token: Dict[str, Any], state: BlockState) -> str:
        return '<linebreak>'

    def softbreak(self, token: Dict[str, Any], state: BlockState) -> str:
        return ' '

    def inline_html(self, token: Dict[str, Any], state: BlockState) -> str:
        # rst does not support inline html
        return ''

    def paragraph(self, token: Dict[str, Any], state: BlockState) -> str:
        children = token['children']
        if len(children) == 1 and children[0]['type'] == 'image':
            image = children[0]
            attrs = image["attrs"]
            title = cast(str, attrs.get("title"))
            alt = self.render_children(image, state)
            text = ".. figure:: " + cast(str, attrs["url"])
            if title:
                text += '\n   :alt: ' + title
            text += '\n\n' + indent(alt, '   ')
        else:
            text = self.render_tokens(children, state)
            lines = text.split('<linebreak>')
            if len(lines) > 1:
                text = '\n'.join('| ' + line for line in lines)
        return text + '\n\n'

    def heading(self, token: Dict[str, Any], state: BlockState) -> str:
        attrs = token['attrs']
        text = self.render_children(token, state)
        marker = self.HEADING_MARKERS[attrs['level']]
        return text + '\n' + marker * len(text) + '\n\n'

    def thematic_break(self, token: Dict[str, Any], state: BlockState) -> str:
        return '--------------\n\n'

    def block_text(self, token: Dict[str, Any], state: BlockState) -> str:
        return self.render_children(token, state) + '\n'

    def block_code(self, token: Dict[str, Any], state: BlockState) -> str:
        attrs = token.get("attrs", {})
        info = cast(str, attrs.get("info"))
        code = indent(cast(str, token["raw"]), "   ")
        if info:
            lang = info.split()[0]
            return '.. code:: ' + lang + '\n\n' + code + '\n'
        else:
            return '::\n\n' + code + '\n\n'

    def block_quote(self, token: Dict[str, Any], state: BlockState) -> str:
        text = indent(self.render_children(token, state), '   ')
        prev = token['prev']
        ignore_blocks = (
            'paragraph',
            'thematic_break',
            'linebreak',
            'heading',
        )
        if prev and prev['type'] not in ignore_blocks:
            text = '..\n\n' + text
        return text

    def block_html(self, token: Dict[str, Any], state: BlockState) -> str:
        raw = token['raw']
        return '.. raw:: html\n\n' + indent(raw, '   ') + '\n\n'

    def block_error(self, token: Dict[str, Any], state: BlockState) -> str:
        return ''

    def list(self, token: Dict[str, Any], state: BlockState) -> str:
        return render_list(self, token, state)
