from typing import List, Dict, Any


class ClusterResult:
    '''
    Represents a single cluster from clustering.
    
    :param nodes: The nodes contained in the cluster
    :param label: A human readable label describing the cluster
    '''
    def __init__(self, nodes: List[Dict], label: str = "n.a."):
        self.nodes = nodes
        self.label = label


class ClusterResult1D(ClusterResult):
    def __init__(self, nodes, lower_bound, upper_bound):
        super().__init__(nodes, f"{lower_bound} -- {upper_bound}")


class ClusterResult2D(ClusterResult):
    def __init__(self, nodes, center):
        super().__init__(nodes, center)


class ClusterResultConverter:
    def __init__(self):
        pass

    def _convert_feature_to_float(self, feature_value) -> float:
        return float(feature_value if feature_value is not "" else 0)

    def convert_to_cluster_results(self, cluster_groups:Dict[Any, List[Dict]], features:List[str]) -> Dict[Any, ClusterResult]:
        if len(features) == 1:
            return self._convert_to_cluster_results_1d(cluster_groups, features[0])
        elif len(features) == 2:
            return self._convert_to_cluster_results_2d(cluster_groups, features)
        else:
            return self._convert_to_cluster_results(cluster_groups, features)

    def _convert_to_cluster_results(self, cluster_groups:Dict[Any, List[Dict]], features:List[str]) -> Dict[Any, ClusterResult]:
        '''Returns the clustering results as they are, converted to a list of ClusterResults.'''
        new_results = {}
        for key in cluster_groups:
            nodes_in_cluster: List[Dict] = cluster_groups[key]
            new_results[key] = ClusterResult(nodes_in_cluster)
        return new_results

    def _convert_to_cluster_results_1d(self, cluster_groups:Dict[Any, List[Dict]], feature:str) -> Dict[Any, ClusterResult1D]:
        '''Returns the clustering results with an added label for the 1d lower and upper bound.'''
        new_results = {}
        for key in cluster_groups:
            nodes_in_cluster: List[Dict] = cluster_groups[key]

            # choose the first node's value as min and max
            min_ = self._convert_feature_to_float(nodes_in_cluster[0][feature])
            max_ = self._convert_feature_to_float(nodes_in_cluster[0][feature])

            for node in nodes_in_cluster:
                float_feature_value = self._convert_feature_to_float(node[feature])
                if min_ > float_feature_value:
                    min_ = float_feature_value
                if max_ < float_feature_value:
                    max_ = float_feature_value

            new_results[key] = ClusterResult1D(nodes_in_cluster, min_, max_)
        
        return new_results
        
    def _convert_to_cluster_results_2d(self, cluster_groups:Dict[Any, List[Dict]], features:List[str]) -> Dict[Any, ClusterResult2D]:
        '''Returns the clustering results with an added label for the 2d center.'''
        new_results = {}
        for key in cluster_groups:
            nodes_in_cluster: List[Dict] = cluster_groups[key]

            x = [self._convert_feature_to_float(node[features[0]]) for node in nodes_in_cluster]
            y = [self._convert_feature_to_float(node[features[1]]) for node in nodes_in_cluster]
            centroid = (sum(x) / len(nodes_in_cluster), sum(y) / len(nodes_in_cluster))
            
            new_results[key] = ClusterResult2D(nodes_in_cluster, str(centroid))
        
        return new_results
