from flask import Markup, escape
from typing import List, Optional, Union, Tuple
import types


tag_names = ["a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio", "b", "base", "basefont", "bdi", "bdo", "big", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "data", "datalist", "dd", "del", "details", "dfn", "dialog", "dir", "div", "dl", "dt", "em", "embed", "fieldset", "figcaption", "figure", "font", "footer", "form", "frame", "frameset", "head", "header", "hgroup", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "html", "i", "iframe", "img", "input", "ins", "kbd", "keygen", "label", "legend", "li", "link", "main", "map", "mark", "menu", "menuitem", "meta", "meter", "nav", "noframes", "noscript", "object", "ol", "optgroup", "option", "output", "p", "param", "picture", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "script", "section", "select", "small", "source", "span", "strike", "strong", "style", "sub", "summary", "sup", "svg", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"]


class EscapeError(RuntimeError):
    pass


def escape_attribute(x:str) -> str:
    return str(x).replace("&", "&").replace('"', """)


def escape_attribute_name(x:str) -> str:
    for c in "<>='\" ":
        if c in x:
            raise EscapeError
    return x


def escape_tag_name(x:str) -> str:
    return escape_attribute_name(x)

Element = Union[str, Markup, 'Bucket']


def indent_str(indent, s):
    return "    "*indent + s + "\n" if indent is not None else s

class Bucket:
    content: List[Element]
    is_paired: bool
    builder: 'Builder'
    def __init__(self, builder):
        self.builder = builder
        self.is_paired = False
        self.content = []
        self.before_tag = None

    def add(self, x: Element):
        if isinstance(x, types.FunctionType):
            before_current_tag = self.builder.current_tag
            self.builder.current_tag = self
            try:
                x(self)
            finally:
                self.builder.current_tag = before_current_tag
        else:
            self.is_paired = True
            self.content.append(x)

    def __call__(self, *arg, **kvarg):
        for i in arg:
            self.add(i)
        for k, v in kvarg.items():
            self.add_attribute(remove_leading_underscore(k), v)
        return self

    def add_tag(self, name: str, attributes: List[Tuple[str, str]]):
        t = Tag(self.builder, name, attributes)
        self.add(t)
        return t

    def line(self):
        t = Line(self.builder)
        self.add(t)
        return t

    def _line(self):
        return Line(self)

    def bucket(self):
        t = Bucket(self.builder)
        self.add(t)
        return t

    def _bucket(self):
        return Bucket(self)

    def print(self):
        out = []
        self.serialize_append_to_list(out, 0)
        return Markup("".join(out))

    def print_file(self):
        out = ["<!DOCTYPE html>\n"]
        self.serialize_append_to_list(out, 0)
        return Markup("".join(out))

    def __enter__(self):
        if self.before_tag is not None:
            raise RuntimeError("Duplicit __enter__")
        self.before_tag = self.builder.current_tag
        self.builder.current_tag = self
        return self

    def __exit__(self, exc_type, exc_value, exc_traceback):
        if self.before_tag is None:
            raise RuntimeError("__exit__ before __enter__")
        self.builder.current_tag = self.before_tag
        self.before_tag = None

    def serialize_append_to_list(self, out, indent):
        for i in self.content:
            if isinstance(i, Bucket):
                i.serialize_append_to_list(out, indent)
            elif isinstance(i, Markup):
                for j in i.__html__().split("\n"):
                    out.append(indent_str(indent, j))
            else:
                for j in escape(str(i)).split("\n"):
                    out.append(indent_str(indent, j))


class Tag(Bucket):
    name: str
    attributes: List[Tuple[str, str]]

    def __init__(self, builder, name: str, attributes: List[Tuple[str, str]]):
        super().__init__(builder)
        self.name = name
        self.attributes = attributes

    def add_attribute(self, k, v):
        self.attributes.append((k, v))

    def format_attributes(self):
        return " ".join(f'{escape_attribute_name(i[0])}="{escape_attribute(i[1])}"' for i in self.attributes)

    def serialize_append_to_list(self, out, indent):
        if self.is_paired:
            out.append(indent_str(indent, f"<{escape_tag_name(self.name)} {self.format_attributes()}>"))
            super().serialize_append_to_list(out, indent + 1 if indent is not None else None)
            out.append(indent_str(indent, f"</{escape_tag_name(self.name)}>"))
        else:
            out.append(indent_str(indent, f"<{escape_tag_name(self.name)} {self.format_attributes()} />"))


class Line(Bucket):
    def serialize_append_to_list(self, out, indent):
        if indent is None:
            super().serialize_append_to_list(out, None)
        else:
            out.append(indent_str(indent,"")[:-1])
            super().serialize_append_to_list(out, None)
            out.append("\n")



class Builder:
    current_tag: Bucket
    root_tag: Bucket
    def __init__(self, tag: Bucket = None):
        if tag is None:
            tag = Bucket(self)
        self.root_tag = tag
        self.current_tag = tag

    def add_tag(self, name: str, attributes: List[Tuple[str, str]] = []):
        return self.current_tag.add_tag(name, attributes)

    def line(self):
        return self.current_tag.line()

    def _line(self):
        return Line(self)

    def bucket(self):
        return self.current_tag.bucket()

    def _bucket(self):
        return Bucket(self)

    def __call__(self, *arg, **kvarg):
        self.current_tag(*arg, **kvarg)
        return self

    def print(self):
        return self.root_tag.print()

    def print_file(self):
        return self.root_tag.print_file()


for name in tag_names:
    def run(name):
        def l1(self, *args, **kvarg):
            return self.add_tag(name, [(remove_leading_underscore(k), v) for k, v in kvarg.items()])(*args)
        def l2(self, *args, **kvarg):
            return Tag(self, name, [(remove_leading_underscore(k), v) for k, v in kvarg.items()])(*args)
        def l3(self, *args, **kvarg):
            return Tag(self.builder, name, [(remove_leading_underscore(k), v) for k, v in kvarg.items()])(*args)
        setattr(Builder, name, l1)
        setattr(Builder, f"_{name}", l2)
        setattr(Bucket, name, l1)
        setattr(Bucket, f"_{name}", l3)
    run(name)


class HtmlBuilder(Builder):
    def __init__(self):
        super().__init__(Tag(self, "html", []))


def remove_leading_underscore(s):
    if s == "":
        return s
    if s[0] == "_":
        return s[1:]
    return s


class WrapAfterBuilder(Builder):
    def __init__(self, f):
        super().__init__(Bucket(self))
        self.wrap_done = False
        self.wrap_function = f

    def wrap(self, *arg, **kvarg):
        if self.wrap_done:
            return
        self.wrap_done = True
        content = self.root_tag.content
        self.root_tag = None
        self.current_tag = None
        self.root_tag = self.wrap_function(self, content, *arg, **kvarg) or self.root_tag
        return self

    def print(self, *arg, **kvarg):
        self.wrap()
        return super().print(*arg, **kvarg)

    def print_file(self, *arg, **kvarg):
        self.wrap()
        return super().print_file(*arg, **kvarg)


def WrapAfterBuilder_decorator(f):
    def l():
        return WrapAfterBuilder(f)
    return l