# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.

class NodeVisitor(object):
    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(object):
    def __init__(self, data=None):
        self.data = data
        self.parent = None
        self.children = []

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

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

    def __repr__(self):
        return "<%s %s>" % (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)
        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 xrange(index):
                assert other.data != 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 isinstance(other, ValueNode):
            if self.children:
                assert not isinstance(self.children[-1], 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):
    pass


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
