import re

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):
        pass

    def __repr__(self):
        return "Void block"


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)


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)


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

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


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

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


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

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


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

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


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

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


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


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


if __name__ == '__main__':
    for i in _tokenise(
            """
# Hello World!
## Title 1
### Part 1
#### Chapter _1_
##### Article 1
###### Section 1
Lorem **i`p`sum**
dolor `sit` amet

...
"""
    ):
        print(repr(i))

