import geopy
import geopy.distance
from geopy.distance import vincenty
import agent
from numpy.random import multivariate_normal
from numpy.random import uniform
from numpy.random import randint
from numpy.random import choice
from scipy.misc import comb
import numpy as np
from collections import deque

from math import sqrt

from itertools import product
import nodes as allNodes
from nodes import SpatialNode
from nodes import Consumer
from nodes import Supplier
from nodes import Distributor

from edges import Edge

from math import inf
from math import sqrt
from scipy import ndimage
from math import ceil
import random

from sklearn.cluster import DBSCAN
from sklearn import metrics
from sklearn.preprocessing import StandardScaler

import agentTasks


class GermanNetworkGen:
    ''' A generator for a simple Network
    '''
    def __init__(self, num_suppliers, generation):
        ''' Generates a few nodes and conects them :/
        '''

        seeds=[[9.1755,49.0406],
                [13.666,54.140],
                [12.293,48.605],
                [7.320,52.471],
                [7.486,50.408],
                [9.345,53.850],
                [10.402,48.515],
                [8.435,49.251],
                [9.409,52.034],
                [10.402,48.514],
                [6.362,51.021],
                [6.409,50.594],
                [14.334,51.495],
                [6.405,51.347],
                [11.341,48.461],
                [8.187,49.047],
                [13.381,52.729]]

        num_suppliers=len(seeds)
        spread=2#0.05*sqrt(num_suppliers)
        mean_position=[8.508943, 47.405807]
        min_distance_to_powerplant_in_km = geopy.distance.vincenty(mean_position,np.array(mean_position)+np.array([0,0.03])).km
        

        edge_capacity = 1

        #print("Generating suppliers...")

        self.suppliers = [
            Supplier(geopy.Point(pos), capacity, fixed_costs,
                     costs_per_kwh,operation_capacity) for 
                pos, capacity, fixed_costs, costs_per_kwh, operation_capacity in
                zip(seeds,
                    uniform(1, 10, num_suppliers),
                    uniform(1, 10, num_suppliers),
                    uniform(0.1, 1.0, num_suppliers),
                    uniform(0.25, 1.0, num_suppliers))]

        branch_len=0.25 # the initial branch length as a fraction of spread
        branch_relax=0.8 # the rate at which the branch length relaxes at each generation 
        min_dist=5 #minimum distance from other nodes for a new node to be created (not for consumers)
        max_tries_before_merge =20
        min_dist_branch=2
        max_dist_branch=4
        max_consumer_per_dist=4
        min_consumer_per_dist=2
        consumer_level=2

   

        self.consumers = []
        self.edges = []
        
        #print("Generating distributors...")

        self.distributors = [Distributor(geopy.Point(pos)) for pos in 
                             mean_position + uniform(-spread, spread, (num_suppliers*50, 2))]


        #Remove distributors too close to a powerplant
        self.distributors = [distributor for distributor in self.distributors if min([geopy.distance.vincenty(distributor.position, supplier.position).km for supplier in self.suppliers])>=min_distance_to_powerplant_in_km]


        #Create pairs of all distributors and sort them by distance
        distributor_tuples = sorted(product(self.distributors, self.distributors),key=lambda pair_: geopy.distance.vincenty(pair_[0].position, pair_[1].position).km)

        self.edges.extend(
            (Edge(left_node, right_node, edge_capacity) for left_node, right_node in 
                [x for x in 
                    distributor_tuples if x[0] is not x[1]][0:num_suppliers*8*5]))
        
        
        
        #Create pairs of all distributors with all suppliers and sort them by distance
        distributor_supplier_tuples = sorted(product(self.distributors, self.suppliers), key=lambda pair_: geopy.distance.vincenty(pair_[0].position, pair_[1].position).km)

        self.edges.extend(
            (Edge(left_node, right_node, edge_capacity) for left_node, right_node in
                    distributor_supplier_tuples[0:num_suppliers*4]))
        
        #go through not connected powerplants
        for supplier in self.suppliers:
            if not supplier.edges:
                distance_tuple = sorted(product((supplier,), self.distributors),key=lambda pair_: geopy.distance.vincenty(pair_[0].position, pair_[1].position).km)
                self.edges.append(Edge(*distance_tuple[0],edge_capacity))


        #print("Generate consumers...")
        for distributor in self.distributors:
            number_of_consumers = randint(1,10)
            consumers_ = [Consumer(geopy.Point(pos),1, 1,guaranteed_capacity) for pos, guaranteed_capacity in 
                          zip( multivariate_normal(
                                    mean=[distributor.position.latitude, distributor.position.longitude],
                                    cov=[[spread/1000, 0], [0, spread/1000]],
                                    size=number_of_consumers),
                              uniform(0,10,number_of_consumers))]
            
            consumers_ = [consumer for consumer in consumers_ if min([geopy.distance.vincenty(consumer.position, supplier.position).km for supplier in self.suppliers])>=min_distance_to_powerplant_in_km]


            self.edges.extend((Edge(distributor, consumer, edge_capacity) for consumer in consumers_))
            self.consumers.extend(consumers_)
        
        #print("Finished spatial network layout!")

        #print("Number of suppliers:    ", len(self.suppliers))
        #print("Number of distributors: ", len(self.distributors))
        #print("Number of consumers.    ", len(self.consumers))


        #print("Find neighbourhoods...")
        consumer_array = np.array([[consumer.position.latitude, consumer.position.longitude] for consumer in self.consumers])
        # Normalize data as it is required by some clustering algorithms
        consumer_array = StandardScaler().fit_transform(consumer_array)
        # Run DBSCAN
        db = DBSCAN(eps=0.3, min_samples=5).fit(consumer_array)
        # Get the list of labels, which is the list that contains a label (integer) for each consumer (-1 for noise)
        labels = db.labels_


        ### #print some meta info
        n_clusters_ = len(set(labels)) - (1 if -1 in labels else 0)

        #print('Estimated number of clusters: %d' % n_clusters_)
      #  #print("Silhouette Coefficient: %0.3f"
       #     % metrics.silhouette_score(consumer_array, labels))

        #Give each consumer its neighbourhood id
        for label, consumer in zip(labels, self.consumers):
            consumer.neighbourhood_id = label

        #Make a list of all different labels available
        self.neighbourhood_ids = set(labels)

        #Generate a dictionary with neighbourhood_id -> list of consumers
        #and set for each consumer its neighbourhood
        self.neighbourhoods = {}
        for consumer in self.consumers:
            self.neighbourhoods.setdefault(consumer.neighbourhood_id, []).append(consumer)
            consumer.neighbourhood = self.neighbourhoods[consumer.neighbourhood_id]

        #randomly assign some consumers to be schools, comapnies and shops etc.
        for consumer in self.consumers:
            if uniform() < 0.02:
                consumer.__class__=allNodes.School
                consumer.max_occupants=randint(50,200)
            elif uniform() < 0.05:
                consumer.__class__=allNodes.Company
                consumer.max_occupants=randint(25,200)
            elif uniform() < 0.02:
                consumer.__class__=allNodes.Shop
                consumer.max_occupants=randint(10,200)
            else:
                consumer.__class__=allNodes.Home
                            
                            
                

        self.consumerAgents=[]
        # for each house Generate randomly 1-7 agents
        for consumer in self.consumers:
            if isinstance(consumer,allNodes.Home):
                for i in range(randint(7)):
                    self.consumerAgents.append(agent.consumerAgent(satisfaction = 0.5, cNode = consumer, sNode = None, neighbourhoodSize = 10000000))
                
        #print("Initialising consumerAgents...")
        for a in self.consumerAgents:
            
            shop=a.findFirst(self,allNodes.Shop)
            a.TaskList.append(agentTasks.shopping(shop))
            a.TaskList.append(agentTasks.drinkTea(a.cNode))
            a.TaskList.append(agentTasks.dinner(a.cNode))
            a.TaskList.append(agentTasks.breakfast(a.cNode))
            a.TaskList.append(agentTasks.stayAtHome(a.cNode))
            a.TaskList.append(agentTasks.shower(a.cNode))
            if random.uniform(0,1)>0.2:
                work=a.findFirst(self,allNodes.Company)
                ##print(work.__class__)
                a.TaskList.append(agentTasks.work(work))
            else:
                school=a.findFirst(self,allNodes.School)