from typing import Dict, List


class LayerAdapter:
    '''
    represents the mapping from an internal layer with a set of attributes and what
    attributes from the dataset correspond to each one
    '''

    def __init__(self, name: str, use_case: str, properties: Dict[str, str], cluster_properties: List[str]):
        '''
        Creates a new instance of LayerAdapter

         @params:
            name                - Required : unique identifier for the layer
            properties          - Required : maps InternalPropertyName->DatasetPropertyName
            cluster_properties  - Required : subset of the keys of properties, marks properties to cluster with
            use_case            - Required : identifier for the use-case this layer belongs to
        '''
        self.name = name
        self.properties = properties
        self.use_case = use_case

        for prop in cluster_properties:
            if prop not in properties.keys():
                raise ValueError(f"{prop} is no property in the layer!")

        self.cluster_properties = cluster_properties
    
    def add_mapping(self, internal: str, external: str):
        '''
        Add a new mapping to the layer. This mapping consists of an internal representation.

        @params:
            internal    - Required: string identifier used internally, f.e. "startTime"
            external    - Required: string identifier the column has in the external dataset f.e. "arrivalTimeOfCustomer"
        '''
        self.properties[internal] = external
    
    def delete_mapping(self, internal: str):
        '''
        Removes a mapping from the layer. Raises a ValueError if the mapping identified by the internal representation
        does not exist.

        @params:
            internal    - Required: string identifier used internally, f.e. "startTime"
        '''
        if internal not in self.properties.keys():
            raise ValueError(f"Attribute {internal} is not an internal attribute!")

        if internal in self.cluster_properties:
                    self.delete_cluster_mapping(internal)

        del self.properties[internal]

    def add_cluster_mapping(self, attribute:str):
        '''
        Adds an attribute of the internal representations to the set of attributes to cluster after. The set of cluster
        attribute is always a subset of self.properties.keys().

        @params:
            attribute   - Required: string identifier used internally, f.e. "startTime"
        '''
        if attribute not in self.properties.keys():
            raise ValueError(f"Attribute {attribute} is not an internal attribute!")
        
        if attribute not in self.cluster_properties:
            self.cluster_properties.append(attribute)
    
    def delete_cluster_mapping(self, attribute:str):
        '''
        Removes an attribute from the set of cluster-attributes. Raises a ValueError if either the attribute does not exist
        as an internal representation or if the attribute exists, but was not added as a cluster-attribute yet

        @params:
            attribute   - Required: string identifier used internally, f.e. "startTime"
        '''
        if attribute not in self.properties.keys():
            raise ValueError(f"Attribute {attribute} is not an internal attribute!")
        
        self.cluster_properties.remove(attribute)

    def to_serializable_dict(self) -> Dict:
        return {
            "name": self.name,
            "properties": self.properties,
            "cluster_properties": self.cluster_properties,
            "use_case": self.use_case
        }

    @staticmethod
    def from_serializable_dict(user_dict: Dict):
        '''
        creates a layer object from a dictionary. has to have the following keys:
            - name
            - properties
            - cluster_properties
            - use_case
        '''
        return LayerAdapter(
            user_dict["name"],
            user_dict["use_case"],
            user_dict["properties"],
            user_dict["cluster_properties"]
        )
