import pymongo
import network_constants as netconst
from database.MongoRepositoryBase import MongoRepositoryBase
import json

from db.agi.agi_repository import AgiRepository

from db.entities import *
from typing import List


class Repository(MongoRepositoryBase):
    '''This is a repository for MongoDb.'''

    def __init__(self):
        super().__init__(netconst.COMMUNITY_DETECTION_DB_HOSTNAME,
                         netconst.COMMUNITY_DETECTION_DB_PORT,
                         'communityDetectionDb')

        self._location_collection = 'location'
        self._location_cluster_collection = 'location_cluster'
        self._time_cluster_collection = 'time_cluster'
        self._user_cluster_graph_collection = 'user_cluster_graph'
        self._layer_collection = 'layer-new'
        self._layer_nodes_collection = 'layer_nodes-new'
        self._clusterset_collection = 'cluster_set-new'
        self._time_slice_collection = 'time_slice-new'

        self.agi_repo = AgiRepository()

#region Location
    def add_location(self, location: Location):
        super().insert_entry(self._location_collection, location.to_serializable_dict())

    def get_locations(self) -> List[Location]:
        locations = super().get_entries(self._location_collection)
        return [Location(l) for l in locations]

    def get_agi_locations(self) -> List[Location]:
        agi_locations = self.agi_repo.getLocations()
        return [Location(agi_loc) for agi_loc in agi_locations]
#endregion

#region Specific Clusters
    def add_location_cluster(self, cluster: LocationCluster):
        super().insert_entry(self._location_cluster_collection,
                             cluster.to_serializable_dict(for_db=True))

    def get_location_clusters(self) -> List[LocationCluster]:
        clusters = super().get_entries(self._location_cluster_collection)
        return [LocationCluster(location_dict=c, from_db=True) for c in clusters]

    def add_time_cluster(self, cluster: TimeCluster):
        super().insert_entry(self._time_cluster_collection,
                             cluster.to_serializable_dict(for_db=True))

    def get_time_clusters(self) -> List[TimeCluster]:
        clusters = super().get_entries(self._time_cluster_collection)
        return [TimeCluster(time_dict=c, from_db=True) for c in clusters]
#endregion

#region Cluster Graph
    def add_user_cluster_graph(self, user_graph: UserClusterGraph):
        super().insert_entry(self._user_cluster_graph_collection,
                             user_graph.to_serializable_dict(for_db=True))

    def get_user_cluster_graphs(self) -> List[UserClusterGraph]:
        user_graphs = super().get_entries(self._user_cluster_graph_collection)
        return [UserClusterGraph(dict_=u, from_db=True) for u in user_graphs]
#endregion

#region Layers
    def add_layer(self, layer: Layer):
        super().insert_entry(self._layer_collection, layer.to_serializable_dict())

    def get_layers(self) -> List[Layer]:
        entries = super().get_entries(self._layer_collection)
        return [Layer(e) for e in entries]

    def get_layer_names(self) -> List[str]:
        entries = super().get_entries(self._layer_collection, projection={'layer_name': 1})
        return [e['layer_name'] for e in entries]

    def get_layer(self, layer_name) -> Layer:
        entries = super().get_entries(self._layer_collection, selection={'layer_name': layer_name})
        entries = [Layer(e) for e in entries]

        if entries is not None and len(entries) > 0:
            return entries[0]
        else:
            return None

    def add_layer_node(self, node: dict):
        super().insert_entry(self._layer_nodes_collection, node)

    def add_layer_nodes(self, nodes:List[dict]):
        super().insert_many(self._layer_nodes_collection, nodes)

    def get_layer_nodes(self, layer_name: str) -> dict:
        '''Returns all nodes for the layer.'''
        entries = super().get_entries(self._layer_nodes_collection, selection={'layer_name': layer_name}, projection={'_id': 0})
        return [e for e in entries]

#endregion

#region ClusterSet
# TODO cleanup
    def add_clusterset(self, cluster_set: ClusterSet):
        super().insert_entry(self._clusterset_collection, cluster_set.to_serializable_dict())

    def get_clustersets(self) -> List[ClusterSet]:
        '''Returns all clustersets.'''
        entries = super().get_entries(self._clusterset_collection)
        return [ClusterSet(cluster_set_dict=e) for e in entries]

    def get_clusterset_names(self) -> List[str]:
        '''Returns the names of all clustersets.'''
        entries = super().get_entries(self._clusterset_collection, projection={'layer_name': 1})
        return [e['layer_name'] for e in entries]

    def get_clusterset(self, layer_name) -> ClusterSet:
        '''Returns a single clusterset with the given name or None otherwise.'''
        entries = super().get_entries(self._clusterset_collection, selection={'layer_name': layer_name})
        entries = [ClusterSet(cluster_set_dict=e) for e in entries]

        if entries is not None and len(entries) > 0:
            return entries[0]
        else:
            return None
    
    
    def add_clusters(self, clusters: List[Cluster]):
        cluster_dicts = [c.to_serializable_dict(for_db=True) for c in clusters]
        super().insert_many(self._clusterset_collection, cluster_dicts)
        
    def get_clusters_for_layer(self, layer_name: str) -> List[Cluster]:
        entries = super().get_entries(self._clusterset_collection, selection={'layer_name': layer_name}, projection={'_id': 0})
        return [Cluster(cluster_dict=e, from_db=True) for e in entries]
    
#endregion

#region TimeSlice
    def add_time_slice(self, timeslice: TimeSlice):
        super().insert_entry(self._time_slice_collection, timeslice.to_serializable_dict(for_db=True))

    def get_time_slices(self) -> List[TimeSlice]:
        '''Returns all time slices.'''
        entries = super().get_entries(self._time_slice_collection)
        return [TimeSlice(None, None, time_slice_dict=e, from_db=True) for e in entries]

    def get_time_slices_by_name(self, layer_name) -> List[TimeSlice]:
        '''Returns all time slices with the given layer_name.'''
        entries = super().get_entries(self._time_slice_collection, selection={'layer_name': layer_name})
        return [TimeSlice(None, None, time_slice_dict=e, from_db=True) for e in entries]

    def remove_all_time_slices(self):
        super().drop_collection(self._time_slice_collection)
#endregion