import json
from typing import List, Dict
from datetime import date, datetime


class Cluster:
    '''
    A cluster for an arbitrary layer containing some nodes.

    :param cluster_label: The label of the cluster unique for the layer
    :param nodes: The individual nodes of the cluster
    '''

    def __init__(self, cluster_label: int = None, nodes: List = None,
                 cluster_dict: Dict = None, from_db=False):
        self.cluster_label = cluster_label
        self.nodes = nodes

        if cluster_dict is not None:
            self.from_serializable_dict(cluster_dict, from_db)

    def to_serializable_dict(self, for_db=False) -> Dict:
        return {
            "cluster_label": self.cluster_label,
            "nodes": json.dumps(self.nodes) if for_db else self.nodes
        }

    def from_serializable_dict(self, cluster_dict: Dict, from_db=False):
        self.cluster_label = cluster_dict["cluster_label"]
        self.nodes = json.loads(cluster_dict["nodes"]) \
            if from_db else cluster_dict["nodes"]

    def __repr__(self):
        return json.dumps(self.to_serializable_dict())

    def __str__(self):
        return f"Cluster({self.__repr__()})"


class LocationCluster(Cluster):
    def __init__(self, cluster_label: int = None, nodes: List = None,
                 location_dict: Dict = None, from_db=False):
        super().__init__(cluster_label, nodes)
        self.id = f'{self.cluster_label}'

        if location_dict is not None:
            self.from_serializable_dict(location_dict, from_db)

    def to_serializable_dict(self, for_db=False) -> Dict:
        return {
            "id": self.id,
            "cluster_label": self.cluster_label,
            "nodes": json.dumps(self.nodes) if for_db else self.nodes
        }

    def from_serializable_dict(self, location_dict: Dict, from_db=False):
        self.id = location_dict["id"]
        self.cluster_label = location_dict["cluster_label"]
        self.nodes = json.loads(location_dict["nodes"]) \
            if from_db else location_dict["nodes"]

    def __repr__(self):
        return json.dumps(self.to_serializable_dict())

    def __str__(self):
        return f"LocationCluster({self.__repr__()})"


class TimeCluster(Cluster):
    def __init__(self, date: date = None, hour: int = None, cluster_label: int = None, nodes: List = None,
                 time_dict: Dict = None, from_db=False):
        super().__init__(cluster_label, nodes)
        self.date = date
        self.hour = hour
        self.id = f'{self.date}-{self.hour}-{self.cluster_label}'

        if time_dict is not None:
            self.from_serializable_dict(time_dict, from_db)

    def to_serializable_dict(self, for_db=False) -> Dict:
        return {
            "id": self.id,
            "date": str(self.date),
            "hour": self.hour,
            "cluster_label": self.cluster_label,
            "nodes": json.dumps(self.nodes) if for_db else self.nodes
        }

    def from_serializable_dict(self, time_dict: Dict, from_db=False):
        self.id = time_dict["id"]
        self.date = datetime.strptime(time_dict["date"], '%Y-%m-%d').date()
        self.hour = time_dict["hour"]
        self.cluster_label = time_dict["cluster_label"]
        self.nodes = json.loads(time_dict["nodes"]) \
            if from_db else time_dict["nodes"]

    def __repr__(self):
        return json.dumps(self.to_serializable_dict(True))

    def __str__(self):
        return f"TimeCluster({self.__repr__()})"
