#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""Authors: Benjamin Renoust (github.com/renoust)
   Date: 2018/02/13
   Description: Loads a Detangler JSON format graph and compute unweighted entanglement analysis with Py3Plex
"""
import sys
print(sys.version)

import networkx as nx
from scipy.sparse.csgraph import csgraph_from_dense, connected_components
from scipy import spatial
import numpy as np
import itertools
import math

# Build the R and C matrix
def build_occurrence_matrix(network):

    multiedges = network.get_edges()
    layers = []
    edge_list = []
    for e in multiedges:
        (n1,l1),(n2,l2) = e
        if l1 == l2:
            if l1 not in layers:
                layers += [l1]
            edge_list.append([n1,n2,l1])

    edge_list = sorted(edge_list, key=lambda x: [x[0],x[1]])

    nb_layers = len(layers)
    r_matrix = np.zeros((nb_layers,nb_layers)).astype(float)
    
    def count_overlap(overlap):
        prev_layers = []
        for e in overlap:
            layer = e[2]
            layer_index = layers.index(layer)
            r_matrix[layer_index,layer_index] += 1.0
            for l in prev_layers:
                r_matrix[l,layer_index] += 1.0
                r_matrix[layer_index,l] += 1.0

            prev_layers.append(layer_index) 

    current_edge = None
    flat_pairs = 0.0
    overlap = []

    for e in edge_list:
        node_pair = [e[0],e[1]]
        if current_edge != node_pair:
            flat_pairs += 1.0
            current_edge = node_pair            
            count_overlap(overlap)
            overlap = []
        overlap.append(e)
    count_overlap(overlap)
    flat_pairs += 1

    c_matrix = r_matrix.copy()

    for i in range(nb_layers):
        c_matrix[i,i] /= flat_pairs

    for i,j in itertools.combinations(range(nb_layers),2):
        c_matrix[i,j] /= r_matrix[j][j] 
        c_matrix[j,i] /= r_matrix[i][i]

    return c_matrix, layers


# proceeds with block decomposition
def compute_blocks(c_matrix):
    c_sparse = csgraph_from_dense(c_matrix)
    nb_components, labels = connected_components(c_sparse, directed=False, return_labels=True)
    
    v2i = {}
    for i,v in enumerate(labels):
        v2i[v] = v2i.get(v, []) + [i]


    blocks = []
    indices = []
    for v,i in v2i.items():
        indices.append(i)
        blocks.append(c_matrix[np.ix_(i,i)])

    return indices, blocks


# computes entanglement for one block
def compute_entanglement(block_matrix):
    eigenvals, eigenvects = np.linalg.eig(block_matrix)
    max_eigenval = max(eigenvals.real)
    index_first_eigenvect = np.argmax(eigenvals)

    nb_layers = len(block_matrix)
    # normalizes the max eigenval to dimensions
    entanglement_intensity = max_eigenval/nb_layers

    gamma_layers = []
    for i in range(nb_layers):
        gamma_layers.append(abs(eigenvects[i][index_first_eigenvect].real)) #because of approx.

    # computes entanglement homogeneity, cosine distance with the [1...1] vector
    entanglement_homogeneity = 1 - spatial.distance.cosine(gamma_layers, np.ones(nb_layers))
    # normalizes within the top right quadrant (sorts of flatten the [0-1] value distribution)
    normalized_entanglement_homogeneity = 1 - math.acos(entanglement_homogeneity)/(math.pi/2)

    return [entanglement_intensity, entanglement_homogeneity, normalized_entanglement_homogeneity], gamma_layers


def compute_entanglement_analysis(network):

    matrix, layers = build_occurrence_matrix(network)
    indices, blocks = compute_blocks(matrix)


    analysis = []
    for i,b in enumerate(blocks):
        layer_labels = [layers[x] for x in indices[i]]
        [I,H,H_norm],gamma=compute_entanglement(b)
        block_analysis = { 
            'Entanglement intensity':I,
            'Layer entanglement': {layer_labels[x]:gamma[x] for x in range(len(gamma))},
            'Entanglement homogeneity': H,
            'Normalized homogeneity': H_norm
        }
        analysis.append(block_analysis)
    return analysis

if __name__ == '__main__':

    

    analysis = compute_entanglement_analysis(net)

    print ("%d connected components of layers"%len(analysis))
    for i,b in enumerate(analysis):
        print ('--- block %d'%i)
        layer_labels = b['Layer entanglement'].keys()
        print ('Covering layers: %s'%layer_labels)

        print ('Entanglement intensity: %f'%b['Entanglement intensity'])
        print ('Layer entanglement: %s'%b['Layer entanglement'])
        print ('Entanglement homogeneity: %f'%b['Entanglement homogeneity'])
        print ('Normalized homogeneity: %f'%b['Normalized homogeneity'])
