from nengo.builder.builder import Builder
from nengo.builder.operator import Reset, SimPyFunc
from nengo.builder.signal import Signal
from nengo.exceptions import BuildError
from nengo.node import Node
from nengo.processes import Process
from nengo.rc import rc
from nengo.utils.numpy import is_array_like


@Builder.register(Node)
def build_node(model, node):
    """Builds a `.Node` object into a model.

    The node build function is relatively simple. It involves creating input
    and output signals, and connecting them with an `.Operator` that depends
    on the type of ``node.output``.

    Parameters
    ----------
    model : Model
        The model to build into.
    node : Node
        The node to build.

    Notes
    -----
    Sets ``model.params[node]`` to ``None``.
    """

    # input signal
    if not is_array_like(node.output) and node.size_in > 0:
        sig_in = Signal(shape=node.size_in, name=f"{node}.in")
        model.add_op(Reset(sig_in))
    else:
        sig_in = None

    # Provide output
    if node.output is None:
        sig_out = sig_in
    elif isinstance(node.output, Process):
        sig_out = Signal(shape=node.size_out, name=f"{node}.out")
        model.build(node.output, sig_in, sig_out, mode="set")
    elif callable(node.output):
        sig_out = (
            Signal(shape=node.size_out, name=f"{node}.out")
            if node.size_out > 0
            else None
        )
        model.add_op(SimPyFunc(output=sig_out, fn=node.output, t=model.time, x=sig_in))
    elif is_array_like(node.output):
        sig_out = Signal(node.output.astype(rc.float_dtype), name=f"{node}.out")
    else:
        raise BuildError(f"Invalid node output type '{type(node.output).__name__}'")

    model.sig[node]["in"] = sig_in
    model.sig[node]["out"] = sig_out
    model.params[node] = None
