import re
import bs4 as beautifulsoup
import sys

inlineRegex = r"""
(?P<em>[*_]{1,7}) (?P<textEm>(?:\\[*]|[^*])*) (?P=em)                              # emphasis
|
[`] (?P<textCode>(?:\\[`]|[^`])*) [`]                                              # inline code
|
(?P<imageFlag>!?) \[ (?P<urlText>[^\[\]]*) \] \((?P<urlDestination>[^\(\)]*)\)     # hyperlink or media
|
(?P<strike>~{2}) (?P<textStrike>(?:\\[~]|[^~])*) (~{2})                            # strikethrough
|
(?P<diff>\-\-|\+\+) (?P<textDiff>(?:\\[-+]|[^-+])*) (?P=diff)                      # diffs
"""


def leading(string, character):
    return len(string) - len(string.lstrip(character))


def trailing(string, character):
    return len(string) - len(string.rstrip(character))


class Element:
    def __init__(self):
        self.classes = []
        self.content = None
        pass

    def __repr__(self):
        return "Void block"

    @property
    def tag_name(self):
        return "m-void"


class Container(Element):
    def __init__(self, content):
        super().__init__()
        self.content = parse_line(content)

    def __repr__(self):
        return "Generic container element: " + repr(self.content)


class Heading(Container):
    def __init__(self, content, level):
        super().__init__(content)
        self.level = level
        pass

    def __repr__(self):
        return f"Heading level {self.level}:\n\t" + repr(self.content)

    @property
    def tag_name(self):
        return "h" + str(self.level)


class Paragraph(Container):
    def __init__(self):
        super().__init__("")

    def addLine(self, content):
        self.content.extend([*parse_line(content), " "])

    def __repr__(self):
        return "Paragraph:\n\t" + repr(self.content)

    @property
    def tag_name(self):
        return "p"


class Emphasis(Container):
    def __init__(self, content, value):
        super().__init__(content)
        self.value = value
        if value >= 4:
            self.classes.append("emphasis-3")
        if value % 4 >= 2:
            self.classes.append("emphasis-2")
        if value % 2:
            self.classes.append("emphasis-1")

    def __repr__(self):
        return f"Emphasis ({self.value}): " + repr(self.content)

    @property
    def tag_name(self):
        return "em" if self.value == 1 else "strong"


class Code(Element):
    def __init__(self, content):
        super().__init__()
        self.content = [content]

    def __repr__(self):
        return f"Inline code: {self.content}"

    @property
    def tag_name(self):
        return "code"


class Strikethrough(Container):
    def __init__(self, content):
        super().__init__(content)

    def __repr__(self):
        return f"Strikethrough: {repr(self.content)}"

    @property
    def tag_name(self):
        return "s"


class Diff(Container):
    def __init__(self, content, value):
        super().__init__(content)
        self.value = value

    def __repr__(self):
        return f"Diff ({self.value}): {self.content}"

    @property
    def tag_name(self):
        return "ins" if self.value == "++" else "del"


class Link(Element):
    def __init__(self, content, destination, image=False):
        super().__init__()
        self.content = content
        self.destination = destination
        self.image = image

    def __repr__(self):
        return f"{'Image' if self.image else 'Link'}: {self.text} -> {self.destination}"

    @property
    def tag_name(self):
        return "a"


class Image(Link):
    def __init__(self, text, destination):
        super().__init__(text, destination, True)

    @property
    def tag_name(self):
        return "img"


def parse_line(source):
    if trailing(source, "\\") == 1:
        source = source.rstrip("\\")
        source += "\n"

    tokens = []
    pattern = re.compile(inlineRegex, re.MULTILINE | re.DOTALL | re.VERBOSE)
    matches = pattern.finditer(source)

    lookup = 0
    for i in matches:
        l = i.start()
        r = i.end()
        tokens.append(source[lookup:l])

        lookup = r

        if i.group("em"):
            tokens.append(Emphasis(i.group("textEm"), len(i.group("em"))))
        if i.group("textCode"):
            tokens.append(Code(i.group("textCode")))
        if i.group("strike"):
            tokens.append(Strikethrough(i.group("textStrike")))
        if i.group("diff"):
            tokens.append(Diff(i.group("textDiff"), i.group("diff")))
        if i.group("urlText"):
            tokens.append(Link(i.group("urlText"), i.group("urlDestination")))
        if i.group("imageFlag"):
            tokens.append(Image(i.group("urlText"), i.group("urlDestination")))

    tokens.append(source[lookup:])

    return tokens


def tokenise(source):
    tokens = []

    current_block = Element

    for line in source.split("\n"):
        if not line.strip():
            # Void block

            tokens.append(current_block)
            current_block = Element()
        elif line.startswith("#") and leading(line.lstrip("#"), " "):
            tokens.append(current_block)

            content = line.lstrip("#").strip()
            current_block = Heading(content, leading(line, "#"))
        else:
            if not isinstance(current_block, Paragraph):
                # Paragraph is default

                tokens.append(current_block)
                current_block = Paragraph()

            current_block.addLine(line.strip())

    tokens.append(current_block)

    return tokens


def make_html(ast):
    soup = beautifulsoup.BeautifulSoup()
    for i in ast:
        # Use bs4 to generate HTML
        if isinstance(i, str):
            soup.append(i)
        elif hasattr(i, "content") and i.tag_name != "m-void":
            tag = soup.new_tag(str(i.tag_name))
            try:
                if isinstance(i.content, list):
                    tag.append(make_html(i.content))
                elif i.content:
                    tag.string = i.content
            except AttributeError as exc:
                # print(i)
                print(exc, file=sys.stderr)
                ...
            soup.append(tag)
    return soup


if __name__ == '__main__':
    # Generate an AST from a markdown file
    ast = tokenise(
            """
# Hello World!
## Title 1
### Part 1
#### Chapter _1_
##### Article 1
###### Section 1
Lorem **i`p`sum**
dolor `sit` amet

...
"""
    )
    # for i in ast:
    #     print(repr(i))

    # Now convert the AST to HTML
    print(make_html(ast).prettify(formatter=beautifulsoup.formatter.HTMLFormatter(indent=4)))

