# mypy: allow-untyped-defs

class NodeVisitor:
    def visit(self, node):
        # This is ugly as hell, but we don't have multimethods and
        # they aren't trivial to fake without access to the class
        # object from the class body
        func = getattr(self, "visit_%s" % (node.__class__.__name__))
        return func(node)


class Node:
    def __init__(self, data=None, comments=None):
        self.data = data
        self.parent = None
        self.children = []
        self.comments = comments or []

    def append(self, other):
        other.parent = self
        self.children.append(other)

    def remove(self):
        self.parent.children.remove(self)

    def __repr__(self):
        return f"<{self.__class__.__name__} {self.data}>"

    def __str__(self):
        rv = [repr(self)]
        for item in self.children:
            rv.extend("  %s" % line for line in str(item).split("\n"))
        return "\n".join(rv)

    def __eq__(self, other):
        if not (self.__class__ == other.__class__ and
                self.data == other.data and
                len(self.children) == len(other.children)):
            return False
        for child, other_child in zip(self.children, other.children):
            if not child == other_child:
                return False
        return True

    def copy(self):
        new = self.__class__(self.data, self.comments)
        for item in self.children:
            new.append(item.copy())
        return new


class DataNode(Node):
    def append(self, other):
        # Append that retains the invariant that child data nodes
        # come after child nodes of other types
        other.parent = self
        if isinstance(other, DataNode):
            self.children.append(other)
        else:
            index = len(self.children)
            while index > 0 and isinstance(self.children[index - 1], DataNode):
                index -= 1
            for i in range(index):
                if other.data == self.children[i].data:
                    raise ValueError("Duplicate key %s" % self.children[i].data)
            self.children.insert(index, other)


class KeyValueNode(Node):
    def append(self, other):
        # Append that retains the invariant that conditional nodes
        # come before unconditional nodes
        other.parent = self
        if not isinstance(other, (ListNode, ValueNode, ConditionalNode)):
            raise TypeError
        if isinstance(other, (ListNode, ValueNode)):
            if self.children:
                assert not isinstance(self.children[-1], (ListNode, ValueNode))
            self.children.append(other)
        else:
            if self.children and isinstance(self.children[-1], ValueNode):
                self.children.insert(len(self.children) - 1, other)
            else:
                self.children.append(other)


class ListNode(Node):
    def append(self, other):
        other.parent = self
        self.children.append(other)


class ValueNode(Node):
    def append(self, other):
        raise TypeError


class AtomNode(ValueNode):
    pass


class ConditionalNode(Node):
    def append(self, other):
        if not len(self.children):
            if not isinstance(other, (BinaryExpressionNode, UnaryExpressionNode, VariableNode)):
                raise TypeError
        else:
            if len(self.children) > 1:
                raise ValueError
            if not isinstance(other, (ListNode, ValueNode)):
                raise TypeError
        other.parent = self
        self.children.append(other)


class UnaryExpressionNode(Node):
    def __init__(self, operator, operand):
        Node.__init__(self)
        self.append(operator)
        self.append(operand)

    def append(self, other):
        Node.append(self, other)
        assert len(self.children) <= 2

    def copy(self):
        new = self.__class__(self.children[0].copy(),
                             self.children[1].copy())
        return new


class BinaryExpressionNode(Node):
    def __init__(self, operator, operand_0, operand_1):
        Node.__init__(self)
        self.append(operator)
        self.append(operand_0)
        self.append(operand_1)

    def append(self, other):
        Node.append(self, other)
        assert len(self.children) <= 3

    def copy(self):
        new = self.__class__(self.children[0].copy(),
                             self.children[1].copy(),
                             self.children[2].copy())
        return new


class UnaryOperatorNode(Node):
    def append(self, other):
        raise TypeError


class BinaryOperatorNode(Node):
    def append(self, other):
        raise TypeError


class IndexNode(Node):
    pass


class VariableNode(Node):
    pass


class StringNode(Node):
    pass


class NumberNode(ValueNode):
    pass
