#This file contains the methods which add the data (layers,clusters,nodes) 
# from the input JSON to the "layerDict" dicionary for further processing

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
#from db.repository import Repository
import json
import requests
from routes.clustersets import get_by_name

#TEST ONLY
from routes.connClusters import get_conn_clusters
from routes.similarity import get_similarity


def getClusterDataFromSwagger(limitNrCluster,limitNrNodes):

    ''' Calculates the nr of connections/weights between the clusters contained in the "inputLayerDict". Connections are made between clusters from DIFFERENT layers.

    :param int limitNrCluster: Limits Clusters considered. None or <0 values == No limit

    :param int limitNrNodes: Limits Clusters considered. None or <0 values == No limit

    :returns: Dict{layername: Layer}. Returns a Dict with the data gathered from the DB
    
    :rtype: Dict{string: Layer}
    '''    

    print("Entered dataInput")
    # ??? OBSOLETE ???
    # oldBigTestClusters https://drive.google.com/uc?export=download&id=1l4gHBwrG_N4pCL5-MfWJk2szNrF3VnpG  it takes a whie to download
    # smallTestClusters https://drive.google.com/uc?export=download&id=1cMoGtmi-XouSDM9DRl-ddmPkf2Bm7sk7
    # smallTestOnlyLocationClusters https://drive.google.com/uc?export=download&id=1wBT9vi7aS4rE4qOWHEyLEfQ2KbmpBK9e
    # smallTestOnlyPriceClusters https://drive.google.com/uc?export=download&id=1g9pEOOpDMBj6yZOlFj7HfOoMOAFTvPOW
    # smallTestOnlyTimeClusters https://drive.google.com/uc?export=download&id=1XKXQHEC5ubJHmntQBNnzgfpEZl6OXE_B

    listURLs = []    
    #"""
    listURLs.append('http://articonf1.itec.aau.at:30103/api/layers/Price_Layer/clusters')
    listURLs.append('http://articonf1.itec.aau.at:30103/api/layers/FinishedTime_Layer/clusters')    
    listURLs.append('http://articonf1.itec.aau.at:30103/api/layers/Destination_Layer/clusters')
    #listURLs.append('http://articonf1.itec.aau.at:30103/api/layers/StartingPoint_Layer/clusters')
    #listURLs.append('http://articonf1.itec.aau.at:30103/api/layers/Reputation_Layer/clusters')    
    #listURLs.append('http://articonf1.itec.aau.at:30103/api/layers/StartingTime_Layer/clusters')
    #listURLs.append('http://articonf1.itec.aau.at:30103/api/layers/User_Layer/clusters')
    #"""

    #Maximum of these nodes PER Layer will be considered
    if (limitNrCluster == None ) or (limitNrCluster < 0): 
        limitNrCluster = 9223372036854775807  #per Layer (LLONG_MAX == 2^63)
    if (limitNrNodes == None ) or (limitNrNodes < 0): 
        limitNrNodes = 9223372036854775807  #per Layer (LLONG_MAX == 2^63)

    layerDict = dict()
    #imports and translates the data from JSON into usefull format
    #returns layerdiction -> Layer -> clusterDict -> Cluster -> nodesDict -> Nodes    
    for url in listURLs:
        newData = loadJson(url)
        layerDict = populateWithNewNodesSingleLayer(newData[0:limitNrCluster],layerDict,limitNrNodes)


    return layerDict

def loadJson(url) :
    res = requests.get(url, timeout=30)
    jsonData = json.loads(res.content) 
    return jsonData


def getClusterDataFromMongo(layerNameList,limitNrCluster,limitNrNodes):
    ''' Calculates the nr of connections/weights between the clusters contained in the "inputLayerDict". Connections are made between clusters from DIFFERENT layers.

    :param List[string] layerNameList: Name of the layers to pull from the DB

    :param int limitNrCluster: Limits Clusters considered. None or <0 values == No limit

    :param int limitNrNodes: Limits Clusters considered. None or <0 values == No limit

    :returns: Dict{layername: Layer}. Returns a Dict with the data gathered from the DB
    
    :rtype: Dict{string: Layer}
    '''    

    layerDict = dict()
    

    #Maximum of these nodes PER Layer will be considered
    if (limitNrCluster == None ) or (limitNrCluster < 0): 
        limitNrCluster = 9223372036854775807  #per Layer (LLONG_MAX == 2^63)
    if (limitNrNodes == None ) or (limitNrNodes < 0): 
        limitNrNodes = 9223372036854775807  #per Layer (LLONG_MAX == 2^63)

    layerDict = dict()
    #imports and translates the data from JSON into usefull format
    #returns layerdiction -> Layer -> clusterDict -> Cluster -> nodesDict -> Nodes    
    for name in layerNameList:
        newData = get_by_name(name)
        layerDict = populateWithNewNodesSingleLayer(newData[0:limitNrCluster],layerDict,limitNrNodes)

    return layerDict


def populateWithNewNodesSingleLayer(inputData, layerDict) -> Dict[str,LayerC]:
    ''' Gets Layer,Cluster and Node data from a JSON format, and appends it into the "layerDict" dictionary. A single Layer only

    :param inputData: JSON data to be formated.

    :param Dict{string: Layer} layerDict: If it's empty/null a new one will be created. Otherwise contains the associated Layer and Clusters objects to which date is appended. The dictonary KEY is layername, the Value is a Layer Object. The Layer object has an attribute cluster_Dict which stores the clusters in the Layer.

    :returns: Dict{layername: Layer}. Returns the inputLayerDict with the added data from the JSON
    
    :rtype: Dict{string: Layer}
    '''  
    print("Entered populateWithNewNodes")
    if(layerDict == None):
        layerDict = dict()
    print(" Layer: "+inputData[0].get("layer_name"))

    curLayerName = None
    #newClusterDict
    #clusterDict = layerDict.get(curCluster.get("layer_name"),dict())
    for curCluster in inputData:
        if(curCluster.get("layer_name")!= curLayerName):
            clusterDict = layerDict.get(curCluster.get("layer_name"),dict())
            curLayerName = curCluster.get("layer_name")
            
        oldCluster = clusterDict.get(curCluster.get("cluster_label"),None)
        if oldCluster is None: #means this is a new cluster
            cluster_containedNodesDict = dict()
        else: #means this is an already existing cluster
            cluster_containedNodesDict = oldCluster.cluster_containedNodesDict

        for curNode in curCluster.get("nodes"):
            #totalNodesCount+=1
            newNode = NodeC(
                curCluster.get("cluster_label"),
                curLayerName,
                curNode.get("Finished_time"),
                curNode.get("Latitude_Destination"),
                curNode.get("Longitude_Destination"),
                curNode.get("TravelID"),
                curNode.get("TravelPrice"),
                curNode.get("UniqueID"),
                curNode.get("UserID"))
            if(newNode != None):
                if(newNode.uniqueID!= None and newNode.cluster_label!= None and newNode.node_layer!= None):
                    auxtuple = (newNode.uniqueID,newNode.cluster_label)
                    key = frozenset(auxtuple)
                    cluster_containedNodesDict[key]= newNode #overwrite if already there

        #finished node

        if oldCluster is None:
            #    def __init__(self,cluster_label,cluster_layer,cluster_containedNodesDict,cluster_connNodesDict, cluster_connectionsNr,cluster_connClustDict):
            newCluster = ClusterC(
                curCluster.get("cluster_label"),
                curLayerName,
                None,
                cluster_containedNodesDict,
                dict(),         #will populate the dict fields later later
                dict()) #may not be empty anymore) 

            clusterDict[newCluster.cluster_label] = newCluster
        else:
            oldCluster.cluster_containedNodesDict = cluster_containedNodesDict
            clusterDict[curCluster.get("cluster_label")] = oldCluster               
        #finished cluster
        newLayer = LayerC(curLayerName,clusterDict)
        layerDict[curLayerName]= newLayer

    ##########TESTEAAAZAA
    return layerDict

def populateWithNewNodesSingleLayer(inputData, layerDict, limitNrNodes) -> Dict[str,LayerC]:
    ''' Gets Layer,Cluster and Node data from a JSON format, and appends it into the "layerDict" dictionary.

    :param inputData: JSON data to be formated.

    :param Dict{string: Layer} layerDict: If it's empty/null a new one will be created. Otherwise contains the associated Layer and Clusters objects to which JSON data is appended. The dictonary KEY is layername, the Value is a Layer Object. The Layer object has an attribute cluster_Dict which stores the clusters in the Layer.

    :param int limitNrNodes: How many maximum nodes PER layer will be considered.

    :returns: Dict{layername: Layer}. Returns the inputLayerDict with the added data from the JSON

    :rtype: Dict{string: Layer}
    '''  

    print("Entered populateWithNewNodes")
    if(layerDict == None):
        layerDict = dict()
    print(" Layer: "+inputData[0].get("layer_name"))
    
    curLayerName = None
    #newClusterDict
    #clusterDict = layerDict.get(curCluster.get("layer_name"),dict())
    for curCluster in inputData:
        if(curCluster.get("layer_name")!= curLayerName):
            clusterDict = layerDict.get(curCluster.get("layer_name"),dict())
            curLayerName = curCluster.get("layer_name")
            
        oldCluster = clusterDict.get(curCluster.get("cluster_label"),None)
        if oldCluster is None: #means this is a new cluster
            cluster_containedNodesDict = dict()
        else: #means this is an already existing cluster
            cluster_containedNodesDict = oldCluster.cluster_containedNodesDict

        
        for curNode in curCluster.get("nodes"):
            #totalNodesCount+=1
            newNode = NodeC(
                curCluster.get("cluster_label"),
                curLayerName,
                curNode.get("Finished_time"),
                curNode.get("Latitude_Destination"),
                curNode.get("Longitude_Destination"),
                curNode.get("TravelID"),
                curNode.get("TravelPrice"),
                curNode.get("UniqueID"),
                curNode.get("UserID"))
            if(newNode != None):
                if(newNode.uniqueID!= None and newNode.cluster_label!= None and newNode.node_layer!= None):
                    if( limitNrNodes>0):
                        auxtuple = (newNode.uniqueID,newNode.cluster_label)
                        key = frozenset(auxtuple)
                        cluster_containedNodesDict[key]= newNode #overwrite if already there
                        limitNrNodes-=1

        #finished node

        if oldCluster is None:
            #    def __init__(self,cluster_label,cluster_layer,cluster_containedNodesDict,cluster_connNodesDict, cluster_connectionsNr,cluster_connClustDict):
            newCluster = ClusterC(
                curCluster.get("cluster_label"),
                curLayerName,
                None,
                cluster_containedNodesDict,
                dict(),
                dict()) #may not be empty anymore) 

            clusterDict[newCluster.cluster_label] = newCluster
        else:
            oldCluster.cluster_containedNodesDict = cluster_containedNodesDict
            clusterDict[curCluster.get("cluster_label")] = oldCluster               
        #finished cluster
        newLayer = LayerC(curLayerName,clusterDict)
        layerDict[curLayerName]= newLayer

    return layerDict

#deprecated
def populateWithNewNodesAllLayers(inputData,layerDict) -> Dict[str,LayerC]:
    ''' Gets Layer,Cluster and Node data from a JSON format, and appends it into the "layerDict" dictionary.

    :param inputData: JSON data to be formated.

    :param Dict{string: Layer} layerDict: If it's empty/null a new one will be created. Otherwise contains the associated Layer and Clusters objects to which date is appended. The dictonary KEY is layername, the Value is a Layer Object. The Layer object has an attribute cluster_Dict which stores the clusters in the Layer.

    :returns: Dict{layername: Layer}. Returns the inputLayerDict with the added data from the JSON

    :rtype: Dict{string: Layer}
    '''  
    
    print("Entered populateWithNewNodes")
    if(layerDict == None):
        layerDict = dict()

    for curLayer in inputData:
        
        clusterDict = layerDict.get(curLayer.get("layer_name"),dict()) #gets the Old dict or an empty if none is found i.e for a new layer
        curLayerName = curLayer.get("layer_name")
        for curCluster in curLayer.get("clusters"):         
            oldCluster = clusterDict.get(curCluster.get("cluster_label"),None)


            if oldCluster is None: #means this is a new cluster
                cluster_containedNodesDict = dict()
            else: #means this is an already existing cluster
                cluster_containedNodesDict = oldCluster.cluster_containedNodesDict
            

            #SORT NODES?  
            for curNode in curCluster.get("nodes"):
                if(curNode != None):
                    if(curNode.uniqueID != None):
                        newNode = NodeC(
                            curCluster.get("cluster_label"),
                            curLayerName,
                            curNode.get("Finished_time"),
                            curNode.get("Latitude_Destination"),
                            curNode.get("Longitude_Destination"),
                            curNode.get("TravelID"),
                            curNode.get("TravelPrice"),
                            curNode.get("UniqueID"),
                            curNode.get("UserID"))

                        auxtuple = (newNode.uniqueID,newNode.cluster_label)
                        key = frozenset(auxtuple)
                        cluster_containedNodesDict[key]= newNode #overwrite if already there

            
            #finished node

            if oldCluster is None:
                newCluster = ClusterC(
                    curCluster.get("cluster_label"),
                    curLayer.get("layer_name"),
                    None,
                    cluster_containedNodesDict,
                    dict(),
                    dict()) #may not be empty anymore) 

                clusterDict[newCluster.cluster_label] = newCluster
            else:
                #only cluster_containedNodesDict should change
                oldCluster.cluster_containedNodesDict = cluster_containedNodesDict
                #cluster_connNr and clusterConnDict should stay the same
                clusterDict[curCluster.get("cluster_label")] = oldCluster               
        #finished cluster
        newLayer = LayerC(curLayer.get("layer_name"),clusterDict)
        layerDict[curLayer.get("layer_name")]= newLayer
    #finished layer
    print("Finished populateWithNewNodes")
    return layerDict



def getConnClusterDataFromMongo():
    mongoArray = get_conn_clusters()
    outputDict = convertRetrievedClustersFromMongo(mongoArray)

    return outputDict

def getSimilarityDataFromMOngo():
    result = get_similarity()
    return result

def convertRetrievedClustersFromMongo(inputArray):
    ####TODO#### Not tested thoroughly

    LayerDict = {}
    for entry in inputArray:
        if not(entry['cluster_layer'] in LayerDict):
            LayerDict[entry['cluster_layer']] = []

        cl = ClusterC(
            entry['cluster_label'],
            entry['cluster_layer'],
            entry['cluster_runId'],
            entry['cluster_containedNodesDict'],
            entry['cluster_connNodesDict'],
            entry['cluster_connClustDict'])
        LayerDict[entry['cluster_layer']].append(cl)        

    return LayerDict