import sys
import os
import warnings

import numpy as np
import pandas as pd

from scipy.stats import spearmanr
from collections import Counter

from netanalytics.io import to_ORCA
from netanalytics.utils import _normalize_degree_distribution


def graphlet_degree_vectors(nodes_list, edges_list, graphlet_size=5):
    if not (graphlet_size == 4 or graphlet_size == 5):
        raise ValueError("The maximum graphlet size must be either 4 or 5.")

    to_ORCA(nodes_list, edges_list, '_orca.txt')

    cmd = './../_orca/orca_mac '+ str(graphlet_size)  + ' _orca.txt _temp.ndump2'
    os.system(cmd)
    res = []
    with open('_temp.ndump2', 'rb') as _from:
        i = 0
        for line in _from:
            res.append(str(line).split("'")[-2].split('\\')[0].split(' '))
            i +=1
    os.remove("_orca.txt")
    os.remove("_temp.ndump2")
    df = pd.DataFrame(res, index=nodes_list)
    return df


#def graphlet_frequency_count(A):


#def relative_graphlet_frequency_distance():


def _graphlet_degree_distribution(GDV):
    _max = np.max(np.max(GDV))
    degrees = []
    dicts = []
    for orbit in range(GDV.shape[1]):
        dicts.append(Counter(GDV.iloc[orbit,:]))
        degrees.append(list(dicts[-1].keys()))
   # print(dicts)
    total_degrees = set()
    for dg in degrees:
        total_degrees = total_degrees.union(set(dg))
    total_degrees = np.array(sorted(list(total_degrees)))
    Ns = []
    for orbit in range(GDV.shape[1]):
        aux = np.array([dicts[orbit].get(d, 0) for d in total_degrees])
        Ns.append(_normalize_degree_distribution(total_degrees.astype(int), aux))
    res = pd.DataFrame(np.array(Ns).T, index=total_degrees)
    res.sort_index(inplace=True)
    return res


def GDD(nodes_list, edges_list):
    """
    Graphlet Degree Distribution.

    It computes the Graphlet Degree Vector and then computes the distributions
    for all the 73 graphlets orbits.
    """
    counts = graphlet_degree_vectors(nodes_list, edges_list, 5)
    return _graphlet_degree_distribution(counts)


def _graphlet_distribution_distance(GDD_1, GDD_2):
    if GDD_1 is None or GDD_2 is None:
        warnings.warn("Empty graphlet degree vector")
        return np.nan
    if (GDD_1.shape[1] != 73 and GDD_2.shape[1] != 73 and
       GDD_1.shape[1] != 15 and GDD_2.shape[1] !=15):
        raise ValueError("The number of orbits must be either 73 pr 15, "
                         "found %d for the first graph and %d for the second"
                         %(GDD_1.shape[1], GDD_2.shape[1]))

    indices = list(set(GDD_1.index.values).union(set(GDD_2.index.values)))
    distance = np.zeros(GDD_1.shape[1])
    v1 = np.zeros(len(indices))
    v2 = v1.copy()
    for orbit in range(GDD_1.shape[1]):
        for i, ix in enumerate(indices):
            try:
                v1[i] = GDD_1.loc[ix].iloc[orbit]
            except KeyError:
                v1[i] = 0
            try:
                v2[i] = GDD_2.loc[ix].iloc[orbit]
            except KeyError:
                v2[i] = 0
        distance[orbit] = np.sqrt(np.sum(np.square(v1-v2)))
    return distance


def GDD_distance(GDV1, GDV2):
    GDD_1 = _graphlet_degree_distribution(GDV1)
    GDD_2 = _graphlet_degree_distribution(GDV2)
    return _graphlet_distribution_distance(GDD_1, GDD_2)


def GDD_agreement(GDV1, GDV2):
    """
    Graphlet Degree Distribution Agreement.

    This measures uses the Graphlet Degree Distribution to compare two networks.
    """
    distances = GDD_distance(GDV1, GDV2)
    agreements = 1 - distances
    arithmetic = np.mean(agreements)
    geometric = np.product(agreements)
    return arithmetic, geometric


def graphlet_correlation_matrix(GDV):
    if GDV.shape[1] == 73:
        GDV = GDV.values.astype(int)
        GDV = np.vstack((GDV, np.ones((1,GDV.shape[1]))))
        spearm_corr = spearmanr(GDV)
        GCM_73 = spearm_corr[0]
        to_consider = [0,1,2,4,5,6,7,8,9,10,11]
        GCM_11 = GCM_73[to_consider, ]
        GCM_11 = GCM_11[:,to_consider]
        return GCM_73, GCM_11
    else:
        GDV = GDV.values.astype(int)
        GDV = np.vstack((GDV, np.ones((1,GDV.shape[1]))))
        spearm_corr = spearmanr(GDV)
        GCM_11 = spearm_corr[0]
        to_consider = [0,1,2,4,5,6,7,8,9,10,11]
        GCM_11 = GCM_11[to_consider, ]
        GCM_11 = GCM_11[:,to_consider]
        return None, GCM_11



def _graphlet_correlation_distance(GCM1, GCM2):
    _sum = 0
    if GCM1 is None or GCM2 is None:
        warnings.warn("Empty correlation matrix")
        return 0
    if GCM1.shape != GCM2.shape:
        raise ValueError("bla bla bla")
    for i in range(GCM1.shape[0]):
        for j in range(i, GCM1.shape[0]):
            _sum += (GCM1[i,j] - GCM2[i,j])**2
    return np.sqrt(_sum)


def GCD(GDV1, GDV2):
    GCM1_73, GCM1_11 =  graphlet_correlation_matrix(GDV1)
    GCM2_73, GCM2_11 =  graphlet_correlation_matrix(GDV2)
    return (_graphlet_correlation_distance(GCM1_73, GCM2_73),
            _graphlet_correlation_distance(GCM1_11, GCM2_11))

import sys
import os
import warnings

import numpy as np
import pandas as pd

from scipy.stats import spearmanr
from collections import Counter

from netanalytics.io import to_ORCA
from netanalytics.utils import _normalize_degree_distribution


def graphlet_degree_vectors(nodes_list, edges_list, graphlet_size=5):
    if not (graphlet_size == 4 or graphlet_size == 5):
        raise ValueError("The maximum graphlet size must be either 4 or 5.")

    to_ORCA(nodes_list, edges_list, '_orca.txt')

    cmd = './../_orca/orca_mac '+ str(graphlet_size)  + ' _orca.txt _temp.ndump2'
    os.system(cmd)
    res = []
    with open('_temp.ndump2', 'rb') as _from:
        i = 0
        for line in _from:
            res.append(str(line).split("'")[-2].split('\\')[0].split(' '))
            i +=1
    os.remove("_orca.txt")
    os.remove("_temp.ndump2")
    df = pd.DataFrame(res, index=nodes_list)
    return df


#def graphlet_frequency_count(A):


#def relative_graphlet_frequency_distance():


def _graphlet_degree_distribution(GDV):
    _max = np.max(np.max(GDV))
    degrees = []
    dicts = []
    for orbit in range(GDV.shape[1]):
        dicts.append(Counter(GDV.iloc[orbit,:]))
        degrees.append(list(dicts[-1].keys()))
   # print(dicts)
    total_degrees = set()
    for dg in degrees:
        total_degrees = total_degrees.union(set(dg))
    total_degrees = np.array(sorted(list(total_degrees)))
    Ns = []
    for orbit in range(GDV.shape[1]):
        aux = np.array([dicts[orbit].get(d, 0) for d in total_degrees])
        Ns.append(_normalize_degree_distribution(total_degrees.astype(int), aux))
    res = pd.DataFrame(np.array(Ns).T, index=total_degrees)
    res.sort_index(inplace=True)
    return res


def GDD(nodes_list, edges_list):
    """
    Graphlet Degree Distribution.

    It computes the Graphlet Degree Vector and then computes the distributions
    for all the 73 graphlets orbits.
    """
    counts = graphlet_degree_vectors(nodes_list, edges_list, 5)
    return _graphlet_degree_distribution(counts)


def _graphlet_distribution_distance(GDD_1, GDD_2):
    if GDD_1 is None or GDD_2 is None:
        warnings.warn("Empty graphlet degree vector")
        return np.nan
    if (GDD_1.shape[1] != 73 and GDD_2.shape[1] != 73 and
       GDD_1.shape[1] != 15 and GDD_2.shape[1] !=15):
        raise ValueError("The number of orbits must be either 73 pr 15, "
                         "found %d for the first graph and %d for the second"
                         %(GDD_1.shape[1], GDD_2.shape[1]))

    indices = list(set(GDD_1.index.values).union(set(GDD_2.index.values)))
    distance = np.zeros(GDD_1.shape[1])
    v1 = np.zeros(len(indices))
    v2 = v1.copy()
    for orbit in range(GDD_1.shape[1]):
        for i, ix in enumerate(indices):
            try:
                v1[i] = GDD_1.loc[ix].iloc[orbit]
            except KeyError:
                v1[i] = 0
            try:
                v2[i] = GDD_2.loc[ix].iloc[orbit]
            except KeyError:
                v2[i] = 0
        distance[orbit] = np.sqrt(np.sum(np.square(v1-v2)))
    return distance


def GDD_distance(GDV1, GDV2):
    GDD_1 = _graphlet_degree_distribution(GDV1)
    GDD_2 = _graphlet_degree_distribution(GDV2)
    return _graphlet_distribution_distance(GDD_1, GDD_2)


def GDD_agreement(GDV1, GDV2):
    """
    Graphlet Degree Distribution Agreement.

    This measures uses the Graphlet Degree Distribution to compare two networks.
    """
    distances = GDD_distance(GDV1, GDV2)
    agreements = 1 - distances
    arithmetic = np.mean(agreements)
    geometric = np.product(agreements)
    return arithmetic, geometric


def graphlet_correlation_matrix(GDV):
    if GDV.shape[1] == 73:
        GDV = GDV.values.astype(int)
        GDV = np.vstack((GDV, np.ones((1,GDV.shape[1]))))
        spearm_corr = spearmanr(GDV)
        GCM_73 = spearm_corr[0]
        to_consider = [0,1,2,4,5,6,7,8,9,10,11]
        GCM_11 = GCM_73[to_consider, ]
        GCM_11 = GCM_11[:,to_consider]
        return GCM_73, GCM_11
    else:
        GDV = GDV.values.astype(int)
        GDV = np.vstack((GDV, np.ones((1,GDV.shape[1]))))
        spearm_corr = spearmanr(GDV)
        GCM_11 = spearm_corr[0]
        to_consider = [0,1,2,4,5,6,7,8,9,10,11]
        GCM_11 = GCM_11[to_consider, ]
        GCM_11 = GCM_11[:,to_consider]
        return None, GCM_11



def _graphlet_correlation_distance(GCM1, GCM2):
    _sum = 0
    if GCM1 is None or GCM2 is None:
        warnings.warn("Empty correlation matrix")
        return 0
    if GCM1.shape != GCM2.shape:
        raise ValueError("bla bla bla")
    for i in range(GCM1.shape[0]):
        for j in range(i, GCM1.shape[0]):
            _sum += (GCM1[i,j] - GCM2[i,j])**2
    return np.sqrt(_sum)


def GCD(GDV1, GDV2):
    GCM1_73, GCM1_11 =  graphlet_correlation_matrix(GDV1)
    GCM2_73, GCM2_11 =  graphlet_correlation_matrix(GDV2)
    return (_graphlet_correlation_distance(GCM1_73, GCM2_73),
            _graphlet_correlation_distance(GCM1_11, GCM2_11))

#def graphlet_frequency_count(A): #def relative_graphlet_frequency_distance(): # print(dicts) Graphlet Degree Distribution.      It computes the Graphlet Degree Vector and then computes the distributions     for all the 73 graphlets orbits. Graphlet Degree Distribution Agreement.      This measures uses the Graphlet Degree Distribution to compare two networks.