import math
import random
import numpy
from classes.Node import Node
from classes.Client import Client
import os


class Network(object):

    def __init__(self, env, type, conf, loggers):
        self.env = env
        self.conf = conf
        self.topology = {}
        self.type = type
        self.loggers = loggers

        self.clients = [Client(env, conf, self, loggers = loggers, label=i) for i in range(int(conf["clients"]["number"]))]

        if type == "p2p":
            self.peers = [Node(env, conf, self, id="Peer%s" % i, loggers = loggers) for i in range(int(conf["clients"]["number"]))]
            self.topology["Type"] = "p2p"
            self.init_p2p()
        else:
            if type == "cascade":
                self.topology["Type"] = "cascade"
                self.mixnodes = [Node(env, conf, self, id="M%s" % i, loggers = loggers) for i in range(self.conf["network"]["cascade"]["cascade_len"])]
                self.init_cascade()
            elif type == "stratified":
                self.topology["Type"] = "stratified"
                num_mixnodes = int(self.conf["network"]["stratified"]["layers"]) * int(self.conf["network"]["stratified"]["layer_size"])
                self.mixnodes = [Node(env, conf, self, id="M%s" % i, loggers = loggers) for i in range(num_mixnodes)]
                self.init_stratified()
            elif type == "multi_cascade":
                self.topology["Type"] = "multi_cascade"
                num_mixnodes = int(self.conf["network"]["multi_cascade"]["cascade_len"]) * int(self.conf["network"]["multi_cascade"]["num_cascades"])
                self.mixnodes = [Node(env, conf, self, id="M%s" % i, loggers = loggers) for i in range(num_mixnodes)]
                self.init_multi_cascade()
            else:
                raise Exception("Didn't recognize the network type")
        print("Current topology: ", self.topology["Type"])
        # print("Batching yes/no: ", self.conf["mixnodes"]["batch"])

    def init_p2p(self):
        self.topology["peers"] = self.peers.copy()

    def init_cascade(self):
        self.topology["cascade"] = self.mixnodes.copy()

    def init_multi_cascade(self):
        num_cascades = int(self.conf["network"]["multi_cascade"]["num_cascades"])
        cascade_len = int(self.conf["network"]["multi_cascade"]["cascade_len"])
        ind_cascades = [self.mixnodes[x:x+cascade_len] for x in range(0, len(self.mixnodes), cascade_len)]
        self.topology["cascades"] = ind_cascades

    def init_stratified(self):
        num_layers = int(self.conf["network"]["stratified"]["layers"])
        mixes_per_layer = int(self.conf["network"]["stratified"]["layer_size"])

        layers = [self.mixnodes[i * mixes_per_layer:(i + 1) * mixes_per_layer] for i in range(0, num_layers)]
        self.topology["Layers"] =  layers

        for i in range(0, num_layers - 1):
            for j in range(0, mixes_per_layer):
                self.topology[self.mixnodes[i * mixes_per_layer + j]] = layers[i + 1]

    def select_random_route(self):
        tmp_route = []

        if self.topology["Type"] == "stratified":
            tmp_route = [random.choice(L) for L in self.topology["Layers"]]
        elif self.topology["Type"] == "cascade":
            tmp_route = self.topology["cascade"].copy()
        elif self.topology["Type"] == "multi_cascade":
            tmp_route = random.choice(self.topology["cascades"])
        elif self.topology["Type"] == "p2p":
            length = self.conf["network"]["p2p"]["path_length"]
            tmp_route = random.sample(self.peers, length)

        return tmp_route


    def forward_packet(self, packet):
        ''' Function responsible for forwarding the packet, i.e.,
            checking what is the next hop of the packet and triggering the
            process_packet function by a particular node.

            Keyword arguments:
            packet - the packet to be forwarded.
        '''
        # TODO: If needed, some network delay can be added.
        yield self.env.timeout(0)

        # print(packet.current_node, packet.route, packet.dest)
        next_node = packet.route[packet.current_node + 1]
        packet.current_node += 1
        self.env.process(next_node.process_packet(packet))


    def __repr__(self):
        return "topology: " + str(self.topology)
