#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

#Only used for testing
from routes.connClusters import get_conn_clusters
from routes.similarity import get_similarity
from routes.connRun import get_connected_run


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: LayerC}. Returns a Dict with the data gathered from the DB
    
    :rtype: Dict{string: LayerC}
    '''    

    print("Entered dataInput")
    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: LayerC}. Returns a Dict with the data gathered from the DB
    
    :rtype: Dict{string: LayerC}
    '''    

    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, 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("UniqueID"))
            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)
                        #check for duplicates #TODO
                        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


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

    return outputDict

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

def getConnectedRunDataFromMongo():
    result = get_connected_run()
    return result

def convertRetrievedClustersFromMongo(inputArray):
    
    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