from collections import Iterable

from .buffer import Buffer
from .core import TickProcess

def NodeFactory(router, **kwargs):
    def factory(network, nid):
        return Node(network, nid, router=router, **kwargs)
    return factory


class Node(TickProcess):
    ''''''
    def __init__(self, network, nid,
                 buffer_size=None, tick_time=1, router=None):
        ''''''
        super().__init__(tick_time)
        self.env = network.env
        self.network = network
        self.id = nid

        self.buffer = Buffer(self.env, capacity=buffer_size)

        if router is None:
            router = routers['direct']
        self.router = router(self)

        self.start(self.env)

    def route_packets(self):
        packets_to_delete = []

        for packet in self.buffer:
            # remove expired packets from buffer
            if packet.expired(self.env.now):
                packets_to_delete.append(packet)
                continue

            # ask the router what to do
            targets, reason, delete = self.router(packet)

            # check if there are targets to send to
            if targets is None:
                continue

            # allow for targets to be iterable or single item
            if not isinstance(targets, Iterable):
                targets = [targets]

            # send the packet
            self.send(targets, packet, reason)

            # if the router requested deletion from the buffer do it
            if delete:
                packets_to_delete.append(packet)

        for packet in packets_to_delete:
            self.buffer.remove(packet)

    def process(self, **kwargs):
        ''''''
        while True:
            yield self.tick()

    def send(self, targets, packet, reason):
        # TODO: transfer delay
        packet.send(self, targets, reason=reason)
        for target in targets:
            self.network.send_link(self, target, packet)

    def recv(self, packet):
        if packet.destination == self:
            packet.recv()
        else:
            self.buffer.add(packet)

    @property
    def community(self):
        return self.network.community[self]

    @property
    def links(self):
        '''
        Returns a list of connected links.
        '''
        links = {
            met: data
            for met, data in self.network[self].items()
            if data['state']
        }
        return links

    def __repr__(self):
        return 'Node(id={})'.format(self.id)