from mumle.services.bottom.V0 import Bottom
from uuid import UUID

class CDAPI:
    def __init__(self, state, m: UUID):
        self.state = state
        self.bottom = Bottom(state)
        self.m = m
        self.mm = UUID(state.read_value(state.read_dict(state.read_root(), "SCD")))

        # pre-compute some things

        # element -> name
        self.type_model_names = {
            self.bottom.read_outgoing_elements(self.m, e)[0]
                : e for e in self.bottom.read_keys(self.m)
        }


        inh_type, = self.bottom.read_outgoing_elements(self.mm, "Inheritance")
        inh_links = []
        for tm_element, tm_name in self.type_model_names.items():
            types = self.bottom.read_outgoing_elements(tm_element, "Morphism")
            if inh_type in types:
                inh_links.append(tm_element)

        # for each inheritance link we add the parent and child to the sub types map
        # name -> name
        self.direct_sub_types = { type_name: set() for type_name in self.bottom.read_keys(self.m) } # empty initially
        self.direct_super_types = { type_name: set() for type_name in self.bottom.read_keys(self.m) } # empty initially
        for link in inh_links:
            tm_source = self.bottom.read_edge_source(link)
            tm_target = self.bottom.read_edge_target(link)
            parent_name = self.type_model_names[tm_target]
            child_name = self.type_model_names[tm_source]
            self.direct_sub_types[parent_name].add(child_name)
            self.direct_super_types[child_name].add(parent_name)

        def get_transitive_sub_types(type_name: str):
            # includes the type itself - reason: if we want to get all the instances of some type and its subtypes, we don't have to consider the type itself as an extra case
            return [type_name, *(sub_type for child_name in self.direct_sub_types.get(type_name, set()) for sub_type in get_transitive_sub_types(child_name) )]

        def get_transitive_super_types(type_name: str):
            # includes the type itself - reason: if we want to check if something is an instance of a type, we check if its type or one of its super types is equal to the type we're looking for, without having to consider the instance's type itself as an extra case
            return [type_name, *(super_type for parent_name in self.direct_super_types.get(type_name, set()) for super_type in get_transitive_super_types(parent_name))]

        # The transitive sub-/super-types of every type includes the type itself.
        # This is because, often we want to know if an instance's type is 'compatible' (substitutible) with a specified type.
        self.transitive_sub_types = { type_name: set(get_transitive_sub_types(type_name)) for type_name in self.direct_sub_types }
        self.transitive_super_types = { type_name: set(get_transitive_super_types(type_name)) for type_name in self.direct_super_types }

    def get(self, type_name: str):
        return self.bottom.read_outgoing_elements(self.m, type_name)[0]

    def is_direct_subtype(self, super_type_name: str, sub_type_name: str):
        return sub_type_name in self.direct_sub_types[super_type_name]

    def is_direct_supertype(self, sub_type_name: str, super_type_name: str):
        return super_type_name in self.direct_super_types[sub_type_name]

    # Note: according to this function, every class is a subtype of itself
    def is_subtype(self, super_type_name: str, sub_type_name: str):
        return sub_type_name in self.transitive_sub_types[super_type_name]

    # Note: according to this function, every class is a supertype of itself
    def is_supertype(self, sub_type_name: str, super_type_name: str):
        return super_type_name in self.transitive_super_types[sub_type_name]

    # # The edge connecting an object to the value of a slot must be named `{object_name}_{attr_name}`
    def get_attr_link_name(self, class_name, attr_name):
        attr_type_link = self.find_attribute_type(class_name, attr_name)
        if attr_type_link != None:
            return self.type_model_names[attr_type_link]

    # Attributes are inherited, so when we instantiate an attribute of a class, the AttributeLink may contain the name of the superclass
    def find_attribute_type(self, class_name: str, attr_name: str):
        assoc_name = f"{class_name}_{attr_name}"
        type_edges = self.bottom.read_outgoing_elements(self.m, assoc_name)
        if len(type_edges) == 1:
            return type_edges[0]
        else:
            for supertype in self.direct_super_types[class_name]:
                result = self.find_attribute_type(supertype, attr_name)
                if result != None:
                    return result

    def get_type(self, type_name: str):
        return next(k for k, v in self.type_model_names.items() if v == type_name)
