import networkx as nx
import matplotlib.pyplot as plt
from random import randint
from random import uniform
import numpy as np
import matplotlib
import matplotlib.gridspec as gridspec
from matplotlib.ticker import NullFormatter

nullfmt = NullFormatter()  # Keine Labels im Netzwerkplot

#Von https://de.wikibooks.org/wiki/Algorithmensammlung:_Statistik:_Binomialkoeffizient
def binomialkoeffizient(n, k):
    if k == 0: return 1
    if 2*k > n:
        ergebnis = binomialkoeffizient(n, n-k)
    else:
        ergebnis = n - k + 1
        for i in range(2, k+1):  # i in [2; k]
            ergebnis *= (n-k+i)  # Selbstmultiplikation
            ergebnis /= i  # Achtung: Ergebnis ist eine Kommazahl!
    return int(ergebnis)

#Analytische Verteilungsfunktion eines zufaelligen Netzwerkes
def P(n,kmin,kmax,p):
    ergebnis = []
    for k in range(kmin,kmax):
        ergebnis.append(binomialkoeffizient(n-1,k)*p**k*(1-p)**(n-1-k))
    return np.array(ergebnis)

#plot settings
params = {
    'figure.figsize'    : [5, 7.2],
    'text.usetex'       : True,
}
matplotlib.rcParams.update(params) 

#Allgemeine Festlegungen des Netzwerks 
NKn = 40 #Anzahl der Knoten (vertices)
anzedges = 30 #Gesamte Anzahl der im Netzwerk bestehenden Kanten (links, connections)

#Erzeugung einer Liste zum Speichern der gemittelten Verteilungsfunktion
av_deg = []

#Plot-Grid
plt.figure(0)
gs = gridspec.GridSpec(2, 1, height_ratios=[1,2.2], hspace=0.1)
ax1 = plt.subplot(gs[0])
ax2 = plt.subplot(gs[1])

#Anfang der Schleife ueber Nit random networks
Nit = 50
for it in range(Nit):
    #########################################################################################
    #Beginn des eigentlichen Python Programms "Zufaelliges Netzwerk"
    G = nx.Graph()

    #Hinzufuegen der Knoten zum Netzwerk
    G.add_nodes_from(range(NKn))

    #Erzeugung der Kanten des Netzwerkes (zufaellig anzedges-Kanten zwischen den NKn-Knoten erzeugen)
    links = 0
    while links < anzedges:
        KnA = randint(0, NKn-1)
        KnB = randint(0, NKn-1)
        if KnA != KnB and list(G.edges()).count((KnA,KnB)) == 0 and list(G.edges()).count((KnB,KnA)) == 0:
            G.add_edge(KnA,KnB)
            links = links + 1

    #Liste der Knotengrade
    degree_sequence = []
    for n in G.nodes():
        degree_sequence.append(G.degree(n))
    av_deg.extend(degree_sequence)
    maxk = np.max(degree_sequence)
    maxkav = np.max(av_deg)

    #Berechnung der Warscheinlichkeit das Knoten KnA mit KnB verbunden ist
    p = (2*anzedges/float(NKn*(NKn-1)))

    #Erzeugung des Bildes der Verteilungsfunktion der Knotengrade N(k) (analytisch,simulativ)
    ax1.plot(range(int(maxkav+2)), P(NKn,0,int(maxkav+2),p), linewidth = 1, linestyle = '-', c = "black")
    ax1.hist(degree_sequence, bins = range(int(maxk+2)), density = 1, align = "left", histtype = 'bar', color = "blue", alpha = 0.1)
    ax1.hist(av_deg, bins = range(int(maxkav+2)), density = 1, align = "left", histtype = 'bar', color = "black", alpha = 0.7)

    #Achsenbeschriftung
    ax1.set_ylabel(r'$\rm P(k)$')
    ax2.yaxis.set_major_formatter(nullfmt)
    ax2.xaxis.set_major_formatter(nullfmt)
    ax1.set_xlim(0,maxkav+2)
    ax1.set_ylim(0,np.max(np.array(P(NKn,0,int(maxkav+2),p)))+0.2)

    #Erzeugung des Netzwerk-Bildes
    node_size = 150
    node_alpha = 0.3
    node_color = "red"
    edge_thickness = 0.4
    edge_alpha = 0.7
    edge_color = "blue"
    node_text_size = 9
    text_font = "sans-serif"
    graph_pos = nx.shell_layout(G)
    nx.draw_networkx_nodes(G, graph_pos, node_size = node_size, alpha = node_alpha, node_color = node_color)
    nx.draw_networkx_edges(G, graph_pos, width = edge_thickness, alpha = edge_alpha, edge_color = edge_color)
    nx.draw_networkx_labels(G, graph_pos, font_size = node_text_size, font_family = text_font)

    # Plotten der Netzwerkeigenschaften in das Bild
    roundp = "%.4f"%p
    textstr1 = r'$N(k) = N \left( \begin{array}[c]{cc} N -1  \\ \ k \end{array} \right)\, p^k\,(1-p)^{N-1-k} \,\,$'
    textstr2 = r'$N='+str(NKn)+', p='+roundp+', m='+str(anzedges)+'$'
    props = dict(boxstyle='round', facecolor='white', alpha=0.92)
    plt.text(0, -1.44, textstr1, fontsize=11, verticalalignment='top', horizontalalignment='center', bbox=props)
    plt.text(0, -1.32, textstr2, fontsize=12, verticalalignment='bottom', horizontalalignment='center', bbox=props)

    #Speicherung des Bildes als .png Datei
    saveFig = "./img-" + "{:0>3d}".format(it) + ".png"
    plt.savefig(saveFig, dpi=200,bbox_inches="tight",pad_inches=0.05,format="png")
    # Die einzelnen Bilder koennen dann in eine Animation zusammengefuegt werden.
    # z.B. mit folgende Shell-Befehl: ffmpeg -framerate 3 -i './img-%03d.png' network.mp4

    #Loeschen des Bildes
    ax1.clear()
    ax2.clear()

    # Weitere Groessen des Netzwerks koennen in der Shell ausgegeben werden:
#    print nx.shortest_path(G,0,NKn-1)
#    print("radius: %d" % nx.radius(G))
#    print("diameter: %d" % nx.diameter(G))
#    print("eccentricity: %s" % nx.eccentricity(G))
#    print("center: %s" % nx.center(G))
#    print("periphery: %s" % nx.periphery(G))
#    print("density: %s" % nx.density(G))
#    print("neighbors node 0: %s" % G.neighbors(0))
