"""XML parser"""

from xml.dom.minidom import Node
from tonnikala.ir.nodes import (
    Element,
    If,
    For,
    Define,
    Import,
    EscapedText,
    Block,
    Extends,
    Expression,
    Code,
    With,
    EmptyAttrVal,
)
from ..expr import handle_text_node  # TODO: move this elsewhere.
from ..ir.generate import BaseDOMIRGenerator
from .docparser import TonnikalaHTMLParser


class TonnikalaIRGenerator(BaseDOMIRGenerator):
    control_prefix = "py:"

    TRANSLATABLE_ATTRS = {"title", "alt", "placeholder", "value", "caption"}
    UNTRANSLATABLE_ELEMENTS = {"style", "script"}

    def __init__(self, translatable=False, *a, **kw):
        if "control_prefix" in kw:
            self.control_prefix = kw.pop("control_prefix") + ":"

        self.xmlns = "xmlns:" + self.control_prefix.replace(":", "")

        super(TonnikalaIRGenerator, self).__init__(*a, **kw)
        self.state["translatable"] = translatable

    def is_translatable(self):
        return bool(self.state.get("translatable"))

    def set_translatable(self, is_translatable):
        self.state["translatable"] = is_translatable

    def get_guard_expression(self, dom_node):
        return self.grab_and_remove_control_attr(dom_node, "strip")

    def generate_attributes_for_node(self, dom_node, ir_node):
        attrs_node = self.grab_and_remove_control_attr(dom_node, "attrs")
        if dom_node.hasAttribute(self.xmlns):
            dom_node.removeAttribute(self.xmlns)

        # if there are control nodes present at this stage, complain aloud
        for k, v in dom_node.attributes.items():
            if k.startswith(self.control_prefix):
                self.syntax_error("Unknown control attribute {}".format(k), node=v)

        # allow handling None as attributes; i.e. html attributes without value
        def handle_attribute_value(k, v):
            if v is not None:
                return handle_text_node(v, translatable=self.is_attr_translatable(k))
            else:
                return EmptyAttrVal()

        attrs = [
            (k, handle_attribute_value(k, v)) for (k, v) in dom_node.attributes.items()
        ]

        self.generate_attributes(ir_node, attrs=attrs, dynamic_attrs=attrs_node)

    def control_name(self, name):
        if name.startswith(self.control_prefix):
            return name.replace(self.control_prefix, "", 1)

        return None

    # noinspection PyMethodMayBeStatic
    def is_control_name(self, name, to_match):
        return self.control_name(name) == to_match

    # noinspection PyMethodMayBeStatic
    def grab_and_remove_control_attr(self, dom_node, name):
        name = self.control_prefix + name
        if dom_node.hasAttribute(name):
            value = dom_node.getAttribute(name)
            dom_node.removeAttribute(name)
            return value

        return None

    def enter_node(self, ir_node):
        if isinstance(ir_node, Element):
            if ir_node.name in self.UNTRANSLATABLE_ELEMENTS:
                self.state["translatable"] = False

        super(TonnikalaIRGenerator, self).enter_node(ir_node)

    def syntax_error(self, message, node=None, lineno=None):
        if lineno is None:
            if node:
                lineno = getattr(node, "position", (0, 0))[0]
            else:
                lineno = 0

        BaseDOMIRGenerator.syntax_error(self, message=message, lineno=lineno)

    def grab_mandatory_attribute(self, dom_node, name):
        if not dom_node.hasAttribute(name):
            self.syntax_error(
                message="<{}> does not have the required attribute '{}'".format(
                    dom_node.name, name
                ),
                node=dom_node,
            )
        return dom_node.getAttribute(name)

    def create_control_nodes(self, dom_node):
        name = dom_node.tagName

        ir_node_stack = []

        def make_control_node(node_name, irtype, *attrs):
            if not self.is_control_name(name, node_name):
                return

            args = [self.grab_mandatory_attribute(dom_node, i) for i in attrs]
            irnode = irtype(*args)
            irnode.position = dom_node.position
            ir_node_stack.append(irnode)

        make_control_node("extends", Extends, "href")
        make_control_node("block", Block, "name")
        make_control_node("if", If, "test")
        make_control_node("for", For, "each")
        make_control_node("def", Define, "function")
        make_control_node("import", Import, "href", "alias")
        make_control_node("with", With, "vars")
        make_control_node("vars", With, "names")

        # TODO: add all node types in order
        generate_element = not bool(ir_node_stack)

        def make_control_node_of_attr(irtype, name):
            attr = self.grab_and_remove_control_attr(dom_node, name)
            if attr is not None:
                irnode = irtype(attr)
                irnode.position = getattr(attr, "position", (0, 0))
                ir_node_stack.append(irnode)

        make_control_node_of_attr(Block, "block")
        make_control_node_of_attr(If, "if")
        make_control_node_of_attr(For, "for")
        make_control_node_of_attr(Define, "def")
        make_control_node_of_attr(With, "with")
        make_control_node_of_attr(With, "vars")

        # TODO: add all control attrs in order
        if not ir_node_stack:
            return True, None, None

        top = ir_node_stack[0]
        bottom = ir_node_stack[-1]

        # link children
        while True:
            current = ir_node_stack.pop()

            if not ir_node_stack:
                break

            ir_node_stack[-1].add_child(current)

        return generate_element, top, bottom

    def generate_element_node(self, dom_node):
        generate_element, topmost, bottom = self.create_control_nodes(dom_node)

        guard_expression = self.get_guard_expression(dom_node)

        # on :strip="" the expression is to be set to "1"
        if guard_expression is not None and not guard_expression.strip():
            guard_expression = "1"

        # facility to replace children for content control attr
        overridden_children = None
        content = self.grab_and_remove_control_attr(dom_node, "content")
        if content:
            overridden_children = [Expression(content)]

        if self.is_control_name(dom_node.tagName, "replace"):
            replace = self.grab_mandatory_attribute(dom_node, "value")
        else:
            replace = self.grab_and_remove_control_attr(dom_node, "replace")

        add_children = True
        el_ir_node = None
        if replace is not None:
            el_ir_node = Expression(replace)
            add_children = False
            generate_element = False

        if generate_element:
            el_ir_node = Element(dom_node.tagName, guard_expression=guard_expression)
            self.generate_attributes_for_node(dom_node, el_ir_node)

        if not topmost:
            topmost = el_ir_node

        if el_ir_node:
            if bottom:
                bottom.add_child(el_ir_node)

            bottom = el_ir_node

        if add_children:
            if overridden_children:
                bottom.children = overridden_children
            else:
                self.add_children(self.child_iter(dom_node), bottom)

        # raise syntax error for unknown control node names
        if generate_element and self.control_name(dom_node.tagName):
            self.syntax_error(
                "Unknown control element <{}>".format(dom_node.tagName), node=dom_node
            )

        return topmost

    def generate_ir_node(self, dom_node):
        node_t = dom_node.nodeType

        if node_t == Node.ELEMENT_NODE:
            return self.generate_element_node(dom_node)

        if node_t == Node.TEXT_NODE:
            ir_node = handle_text_node(
                dom_node.nodeValue,
                is_cdata=self.state["is_cdata"],
                translatable=self.is_translatable(),
            )
            return ir_node

        if node_t == Node.COMMENT_NODE:
            ir_node = EscapedText("<!--" + dom_node.nodeValue + "-->")
            return ir_node

        if node_t == Node.PROCESSING_INSTRUCTION_NODE:
            ir_node = Code(dom_node.nodeValue.strip())
            return ir_node

        if node_t == Node.DOCUMENT_TYPE_NODE:
            ir_node = EscapedText(dom_node.toxml())
            return ir_node

        self.syntax_error("Unhandled node type %d" % node_t, node=dom_node)

    def is_attr_translatable(self, attr_name):
        return (
            bool(self.state.get("translatable"))
            and attr_name in self.TRANSLATABLE_ATTRS
        )


def parse(filename, string, translatable=False):
    parser = TonnikalaHTMLParser(filename, string)
    parsed = parser.parse()
    generator = TonnikalaIRGenerator(
        document=parsed, translatable=translatable, filename=filename, source=string
    )
    tree = generator.generate_tree()
    tree = generator.flatten_element_nodes(tree)
    tree = generator.merge_text_nodes(tree)
    return tree


def parse_js(filename, string, translatable=False):
    parser = TonnikalaHTMLParser(filename, string)
    parsed = parser.parse()
    generator = TonnikalaIRGenerator(
        document=parsed,
        translatable=translatable,
        control_prefix="js",
        filename=filename,
        source=string,
    )
    tree = generator.generate_tree()
    tree = generator.flatten_element_nodes(tree)
    tree = generator.merge_text_nodes(tree)
    return tree
