#This file contains the methods for caclulating the similarity between clusters
import math

from db.entities.connected_node import NodeC
from db.entities.connected_cluster import ClusterC
from db.entities.connected_layer import LayerC
from typing import Dict

def minMaxFunction(iIndex,jIndex,clusterList) -> Dict[str,int]:

    ''' minMax Metric for calculating similarity between 2 clusters.
        Clusters must be from the same layer, and will be compared to clusters from different layers (cluster_layer attribute)

    :param string iIndex: The index of the first Cluster in the "clusterList" 
    :param string jIndex: The index of the second Cluster in the "clusterList"  
    :param List[Cluster] clusterList: A list of clusters to which the 2 clusters will be compared to


    :returns: Dictionary with layername as KEY, and the computed similarity value between the 2 clusters in regard to the layer as the VALUE of the Dict.
    :rtype: Dict{str,int}
    '''

    
    iCluster= clusterList[iIndex]
    jCluster= clusterList[jIndex]

    outputDict = dict()

    #calculate th
    for curCluster in clusterList:      #jCluster.cluster_layer == iCluster.cluster_layer, so i only compare to one

        iClusterTuple = (iCluster.cluster_label,iCluster.cluster_layer)
        jClusterTuple = (jCluster.cluster_label,jCluster.cluster_layer)
        #iClusterKey = frozenset(iClusterTuple)
        #jClusterKey = frozenset(jClusterTuple)

        curLayer = curCluster.cluster_layer
        if(( curLayer != iCluster.cluster_layer) 
        and ( curCluster.cluster_connClustDict.__contains__(iClusterTuple)) 
        and ( curCluster.cluster_connClustDict.__contains__(jClusterTuple))):

            # min part
            curMin = min(curCluster.cluster_connClustDict[iClusterTuple],curCluster.cluster_connClustDict[jClusterTuple])
            if(outputDict.__contains__(curLayer) == False):
                outputDict[curLayer]= curMin
            else: # max part
                if(outputDict[curLayer]<curMin):
                    outputDict[curLayer] = curMin


    return outputDict


def calcEuclideanDist(iIndex,jIndex,clusterList) -> Dict[str,float]:
    ''' Euclidean Distance Metric for calculating similarity between 2 clusters.
        Clusters must be from the same layer, and will be compared to clusters from different layers (cluster_layer attribute)

    :param string iIndex: The index of the first Cluster in the "clusterList" 
    :param string jIndex: The index of the second Cluster in the "clusterList"  
    :param List[Cluster] clusterList: A list of clusters to which the 2 clusters will be compared to


    :returns: Dictionary with layername as KEY, and the computed similarity value between the 2 clusters in regard to the layer as the VALUE of the Dict.
    :rtype: Dict{str,float}
    '''



    iCluster= clusterList[iIndex]
    jCluster= clusterList[jIndex]

    outputDict = dict()

    #calculate the distance  //paralelizable
    for curCluster in clusterList:      #jCluster.cluster_layer == iCluster.cluster_layer, so i only compare to one

        iClusterTuple = (iCluster.cluster_label,iCluster.cluster_layer)
        jClusterTuple = (jCluster.cluster_label,jCluster.cluster_layer)
        #iClusterKey = frozenset(iClusterTuple)
        #jClusterKey = frozenset(jClusterTuple)
        
        curLayer = curCluster.cluster_layer

        #considering only clusters from other layers for distance calc
        if( curLayer != iCluster.cluster_layer):
            ###### if in a layer both cluster don't have a connection --> distance of 0. Identical in regard to that layer. correct or false?
            iVal = 0
            jVal = 0
            connectedClusters = False
            if(curCluster.cluster_connClustDict.__contains__(iClusterTuple)):
                iVal = curCluster.cluster_connClustDict[iClusterTuple]
                connectedClusters = True
            if(curCluster.cluster_connClustDict.__contains__(jClusterTuple)):
                jVal = curCluster.cluster_connClustDict[jClusterTuple]
                connectedClusters = True

            
            if (connectedClusters == False):
                #clusters aren't connected => assign the max int value if there are no prior elements in list
                if(outputDict.__contains__(curLayer) == False):
                    outputDict[curLayer]= 2147483647 #notConnected to that particular layer at all
            else:
                #clusters ARE connected => add the squares part of the euclid distance to the value of the similarity
                if(outputDict.__contains__(curLayer) == False):
                    #first element
                    outputDict[curLayer]= (iVal - jVal)**2
                else: 
                    #further elements
                    outputDict[curLayer]+= (iVal - jVal)**2

    for layer in outputDict:
        outputDict[layer] = math.sqrt(outputDict[layer])


    return outputDict


#                                      frozenset(tuple)                dict
#                               [(iClusterLabel,jClusterLabel), (layer,similarity)]
#def calculateSimilarity(inputLayerDict) -> Dict[frozenset((str,str)),Dict[str,int]]:
def calculateSimilarity(inputLayerDict):


    ''' Calculates the similarity between clusters contained in the "inputLayerDict". Similarity is calculated for each combination of 2 clusters from the SAME layer.

    :param Dict{layername: LayerC} inputLayerDict: Contains the associated Layer and Clusters objects. The dictonary KEY is layername, the Value is a LayerC Object. The LayerC object has an attribute cluster_Dict which stores the clusters in the LayerC.


    :returns: Dict{tuple(cluster_label1, cluster_label2) : Dict{layername, similarityValue}}. Returns a Dictionary with a tuple of 2 clusters as KEY, and a Dictionary with the computed similarity of the clusters in regard to each layer as VALUE
    :rtype: Dict{(string,string): Dict{str:float}}
    '''     

    print("Entered calculateSimilarity")
    similarityDict = dict() #the key is a frozenset(Tuple) (clusterLabel1,clusterLabel2)
    
    clusterList = list()
    

    for curLayerC in inputLayerDict.values():        
        for curCluster in curLayerC.cluster_Dict.values():
            clusterList.append(curCluster)
    #print(" Nr. of clusters: "+str(len(clusterList)))
    
    #go thru every combination of 2 clusters and calculate the similarity between them in regard to each layer
    i=0
    while( i < len(clusterList) ):
        iCluster = clusterList[i]
        j=i+1
        while ( j<len(clusterList)):
            jCluster = clusterList[j]
            if (iCluster.cluster_layer == jCluster.cluster_layer): #calculate similarity only from the same layer
                tuplekey = (clusterList[i].cluster_label,clusterList[j].cluster_label,iCluster.cluster_layer)
                
                #### EUCLIDEAN DISTANCE /minMax
                similarityDict[tuplekey]=calcEuclideanDist(i,j,clusterList)

                #print("#### similarityDict  i:"+str(i)+" j:"+str(j))
                #print("#### "+str(similarityDict))
            else:
                j = len(clusterList)

            j+=1
        i+=1


    print("Finished calculateSimilarity")
    return similarityDict