import coral
import copy

class MatchData:
    def __init__(self, query_set):
        self.query_set=query_set
        self.elem_property_lists=self.__make_property_lists()

        # Count elements in query_set
        self.query_size = len(query_set)

    def __make_property_lists(self):
        elem_property_lists={}
        for elem in self.query_set:
            Elem_and_ElemPrim_lists=[[],[]]
            elem_property_lists[elem]=Elem_and_ElemPrim_lists
            for prop in elem.__properties__():
                if prop.getName() == "xmi_extension":
                    continue
                if self.__isPrimitive(prop):
                    Elem_and_ElemPrim_lists[1].append(prop)
                else:
                    if len(elem.__getSlotByName__(prop.getName())) == 0:
                        continue

                    Elem_and_ElemPrim_lists[0].append(prop)

        return elem_property_lists
    
    def __isPrimitive(self, prop):
        #coral.Property.
        if prop.isAnonymous():
            return False

        if prop.isAssociation():
            return False
        
        elem=coral.Property.getCorrespondingElement(prop)
        if elem.element_kind.oclIsKindOf( coral.lang.SMD01.DatatypeDefinition ):
            return True

        return False
        
        
    def addIgnored(self, qelem, prop):

        # Isn't there a function for this???
        prop_type=None
        for prop_ed in qelem.__properties__():
            if prop_ed.getName() == prop.name:
                prop_type=prop_ed
                break

        assert prop_type

        property_lists = self.elem_property_lists[qelem]

        for p_list in property_lists:
            if prop_type in p_list:
                p_list.remove(prop_type)
                break

class QueryState:
    def __init__(self, match_data):
        self.q2t={}
        self.results=[]
        self.stack=[]
        self.match_data = match_data

    def primitive_properties(self, elem):
        return self.match_data.elem_property_lists[elem][1]

    def element_properties(self, elem):
        return self.match_data.elem_property_lists[elem][0]

    def push(self, q_elem, t_elem):
        self.stack.append((q_elem, t_elem))

    def pop(self):
        q_elem,t_elem=self.stack[-1]
        self.stack=self.stack[:-1]
        return q_elem,t_elem
            
    def stack_size(self):
        return len(self.stack)

    def clone(self):
        state=copy.copy(self)
        state.q2t=self.q2t.copy()
        state.stack=self.stack[:]
        
        return state
    
    def join(self, state):
        self.q2t=state.q2t
        
    def addPair(self, query_item, target_item):
        assert not self.q2t.has_key(query_item), " Cyclic match :-("+str(query_item)
        assert not target_item in self.q2t.values()
        assert not isinstance(query_item, coral.ElementPrimitive)
        self.q2t[query_item]=target_item

    def isGoal(self):
        if len(self.q2t) == self.match_data.query_size:
            return True
        return False

    def saveResult(self):
        # Doesn't need copying I think, since it is copied in clone
        # self.results.append(self.q2t.copy())
        self.results.append(self.q2t)

    def getResults(self):
        return self.results

    def compatible_type(q_elem, t_elem):
        return q_elem.__type__() == t_elem.__type__()

    compatible_type=staticmethod(compatible_type)
    
    def isFeasiblePair(self,q_elem, t_elem):

        if not self.__class__.compatible_type(q_elem,t_elem):
            return False

        return True

    def visitor(self):
        #print "saving result in State"
        self.saveResult()
        return False


class OneMatchState(QueryState):
    def visitor(self):
        self.saveResult()
        return True

class TwoMatchState(QueryState):
    def visitor(self):
        self.saveResult()
        if len(self.results) > 1:
            return True
        return False

class IsKindOfState(QueryState):
    def compatible_type(q_elem, t_elem):
        return t_elem.oclIsKindOf(q_elem.__type__())

    compatible_type=staticmethod(compatible_type)

# # Returns elements which are not in the query but to which
# # the elements in the query have associations.
# def get_external_elements(query_set):
#     all_parts=get_all_query_parts(query_set)
#     elements=coral.Set()

#     for elem in query_set:
#         select_assoc_prop(elem,all_parts,elements)

#     return elements

# def select_assoc_prop(elem, all_parts, elements):
#     if isinstance(elem,coral.ElementPrimitive):
#         return
#     for prop in elem.__type__().__properties__():
#         if prop.isAnonymous():
#             continue
#         for item in elem.__getSlotByName__(prop.getName()):
#             if prop.isAssociation():
#                 if not item in all_parts:
#                     elements.append(item)
#             else:
#                 select_assoc_prop(item,all_parts, elements)
