"""Provides data structures that represent parsed Modelverse bytecode graphs."""

class Instruction(object):
    """Represents a Modelverse bytecode instruction."""
    def __init__(self):
        self.next_instruction = None
        self.debug_information = None

    def get_directly_reachable(self):
        """Gets all instructions that are directly reachable from this instruction."""
        raise NotImplementedError()

    def get_reachable(self):
        """Gets the set of all instructions that are reachable from the given instruction, including
           this instruction."""
        results = set()
        stack = [self]
        while len(stack) > 0:
            instr = stack.pop()
            results.add(instr)
            for other in instr.get_directly_reachable():
                if other not in results:
                    stack.append(other)

        return results

class VariableNode(object):
    """Represents a variable node, which has an identifier and an optional name."""
    def __init__(self, node_id, name):
        self.node_id = node_id
        self.name = name

    def __str__(self):
        return 'var(%d, %s)' % (self.node_id, self.name)

    def __repr__(self):
        return 'VariableNode(%r, %r)' % (self.node_id, self.name)

class SelectInstruction(Instruction):
    """Represents an 'if/else' instruction."""
    def __init__(self, condition, if_clause, else_clause):
        Instruction.__init__(self)
        self.condition = condition
        self.if_clause = if_clause
        self.else_clause = else_clause

    def get_directly_reachable(self):
        """Gets all instructions that are directly reachable from this instruction."""
        if self.else_clause is None:
            return (self.condition, self.if_clause)
        else:
            return (self.condition, self.if_clause, self.else_clause)

    constructor_parameters = (
        ('cond', Instruction),
        ('then', Instruction),
        ('else', Instruction))

    def __repr__(self):
        return '@%r: SelectInstruction(@%r, @%r, @%r)' % (
            id(self), id(self.condition), id(self.if_clause), id(self.else_clause))

class WhileInstruction(Instruction):
    """Represents a 'while' instruction."""
    def __init__(self, condition, body):
        Instruction.__init__(self)
        self.condition = condition
        self.body = body

    def get_directly_reachable(self):
        """Gets all instructions that are directly reachable from this instruction."""
        return (self.condition, self.body)

    constructor_parameters = (
        ('cond', Instruction),
        ('body', Instruction))

    def __repr__(self):
        return '@%r: WhileInstruction(@%r, @%r)' % (
            id(self), id(self.condition), id(self.body))

class BreakInstruction(Instruction):
    """Represents a 'break' instruction."""
    def __init__(self, loop):
        Instruction.__init__(self)
        self.loop = loop

    def get_directly_reachable(self):
        """Gets all instructions that are directly reachable from this instruction."""
        return (self.loop,)

    constructor_parameters = (('while', WhileInstruction),)

    def __repr__(self):
        return '@%r: BreakInstruction(@%r)' % (
            id(self), id(self.loop))

class ContinueInstruction(Instruction):
    """Represents a 'continue' instruction."""
    def __init__(self, loop):
        Instruction.__init__(self)
        self.loop = loop

    def get_directly_reachable(self):
        """Gets all instructions that are directly reachable from this instruction."""
        return (self.loop,)

    constructor_parameters = (('while', WhileInstruction),)

    def __repr__(self):
        return '@%r: ContinueInstruction(@%r)' % (
            id(self), id(self.loop))

class ReturnInstruction(Instruction):
    """Represents a 'return' instruction, which terminates the current function
       and optionally returns a value."""
    def __init__(self, value):
        Instruction.__init__(self)
        self.value = value

    def get_directly_reachable(self):
        """Gets all instructions that are directly reachable from this instruction."""
        return (self.value,)

    constructor_parameters = (('value', Instruction),)

    def __repr__(self):
        return '@%r: ReturnInstruction(@%r)' % (
            id(self), id(self.value))

class CallInstruction(Instruction):
    """Represents a 'call' instruction, which calls a function with an argument
       list, encoded as a list of name-instruction tuples."""
    def __init__(self, target, argument_list):
        Instruction.__init__(self)
        self.target = target
        self.argument_list = argument_list

    def get_directly_reachable(self):
        """Gets all instructions that are directly reachable from this instruction."""
        return (self.target,) + tuple((value for _, value in self.argument_list))

    def __repr__(self):
        return '@%r: CallInstruction(@%r, [%s])' % (
            id(self), id(self.target),
            ', '.join(['%s=@%r' % (name, id(value)) for name, value in self.argument_list]))

class ConstantInstruction(Instruction):
    """Represents a 'constant' instruction, which produces a reference
       to a constant node."""
    def __init__(self, constant_id):
        Instruction.__init__(self)
        self.constant_id = constant_id
        assert self.constant_id is not None

    def get_directly_reachable(self):
        """Gets all instructions that are directly reachable from this instruction."""
        return ()

    constructor_parameters = (('node', int),)

    def __repr__(self):
        return '@%r: ConstantInstruction(%r)' % (id(self), self.constant_id)

class InputInstruction(Instruction):
    """Represents an 'input' instruction, which pops a node from the input
       queue."""
    def __init__(self):
        Instruction.__init__(self)

    def get_directly_reachable(self):
        """Gets all instructions that are directly reachable from this instruction."""
        return ()

    constructor_parameters = ()

    def __repr__(self):
        return '@%r: InputInstruction()' % id(self)

class OutputInstruction(Instruction):
    """Represents an 'output' instruction, which pushes a node onto the output
       queue."""
    def __init__(self, value):
        Instruction.__init__(self)
        self.value = value

    def get_directly_reachable(self):
        """Gets all instructions that are directly reachable from this instruction."""
        return (self.value,)

    constructor_parameters = (('value', Instruction),)

    def __repr__(self):
        return '@%r: OutputInstruction(@%r)' % (
            id(self), id(self.value))

class DeclareInstruction(Instruction):
    """Represents a 'declare' instruction, which declares a local variable."""
    def __init__(self, variable):
        Instruction.__init__(self)
        self.variable = variable

    def get_directly_reachable(self):
        """Gets all instructions that are directly reachable from this instruction."""
        return ()

    constructor_parameters = (('var', VariableNode),)

    def __repr__(self):
        return '@%r: DeclareInstruction(%r)' % (
            id(self), self.variable)

class GlobalInstruction(Instruction):
    """Represents a 'global' instruction, which declares a global variable."""
    def __init__(self, variable):
        Instruction.__init__(self)
        self.variable = variable

    def get_directly_reachable(self):
        """Gets all instructions that are directly reachable from this instruction."""
        return ()

    constructor_parameters = (('var', VariableNode),)

    def __repr__(self):
        return '@%r: GlobalInstruction(%r)' % (
            id(self), self.variable)

class ResolveInstruction(Instruction):
    """Represents a 'resolve' instruction, which resolves a variable node/name as
       either a local or global variable."""
    def __init__(self, variable):
        Instruction.__init__(self)
        self.variable = variable

    def get_directly_reachable(self):
        """Gets all instructions that are directly reachable from this instruction."""
        return ()

    constructor_parameters = (('var', VariableNode),)

    def __repr__(self):
        return '@%r: ResolveInstruction(%r)' % (
            id(self), self.variable)

class AccessInstruction(Instruction):
    """Represents an 'access' instruction, which loads the node pointed to by a
       pointer node."""
    def __init__(self, pointer):
        Instruction.__init__(self)
        self.pointer = pointer

    def get_directly_reachable(self):
        """Gets all instructions that are directly reachable from this instruction."""
        return (self.pointer,)

    constructor_parameters = (('var', Instruction),)

    def __repr__(self):
        return '@%r: AccessInstruction(@%r)' % (
            id(self), id(self.pointer))

class AssignInstruction(Instruction):
    """Represents an 'assign' instruction, which sets the node pointed to by a
       pointer node to the given node."""
    def __init__(self, pointer, value):
        Instruction.__init__(self)
        self.pointer = pointer
        self.value = value

    def get_directly_reachable(self):
        """Gets all instructions that are directly reachable from this instruction."""
        return (self.pointer, self.value)

    constructor_parameters = (
        ('var', Instruction),
        ('value', Instruction))

    def __repr__(self):
        return '@%r: AssignInstruction(@%r, @%r)' % (
            id(self), id(self.pointer), id(self.value))

INSTRUCTION_TYPE_MAPPING = {
    'if' : SelectInstruction,
    'while' : WhileInstruction,
    'return' : ReturnInstruction,
    'constant' : ConstantInstruction,
    'resolve' : ResolveInstruction,
    'declare' : DeclareInstruction,
    'global' : GlobalInstruction,
    'assign' : AssignInstruction,
    'access' : AccessInstruction,
    'output' : OutputInstruction,
    'input' : InputInstruction,
    'call' : CallInstruction,
    'break' : BreakInstruction,
    'continue' : ContinueInstruction
}
"""Maps instruction names to types."""
