import coral
from state import * #QueryState, IsKindOfState, get_external_elements#,count_elements_in_query

class Results:
    def __init__(self, allow_permutations=False):
        self.results=[]
        self.sorted_results=[]
        
        if allow_permutations:
            self.append=self.append_perm
        else:
            self.append=self.append_unique
        
    def append_unique(self, result):
        target_list=result.values()
        target_list.sort()
        if target_list in self.sorted_results:
            return 0

        self.sorted_results.append(target_list)
        self.results.append(result)
        return 1
    
    def append_perm(self, result):
        self.results.append(result)
        
    def getList(self):
        return self.results

def match_query_elem(query_set, target_elem, State_T=IsKindOfState):
    #print "query.match_query_elem"
    # See if the target element matches any of the query elements

    results=Results(False)
    match_data = MatchData(query_set)

    for q_elem in match_data.query_set:
        state=State_T(match_data)
        if state.isFeasiblePair(q_elem, target_elem):
            state.addPair(q_elem, target_elem)
            match(q_elem, target_elem, state)
            rs=state.getResults()
            if rs:
                #print "--> Match",q_elem,target_elem
                
                for result in rs:
                    #print "~~> Match my result",result
                    results.append(result)

                    #print "**> Match, # of results",len(results.getList())
    return results.getList()

def match_elem(query_elem, target_elem, query_set, State_T=OneMatchState):
    # NOTE: match_elem used to match elements which were part of a query element but not in the query_set
    # NOTE: however, this is not the case anymore so make sure to include all elements in query_set

    if query_elem.__type__() != target_elem.__type__():
        return False

    match_data = MatchData(query_set)

    state=State_T(match_data)

    if match(query_elem, target_elem, state):
        return state.getResults()
    else:
        return []

def search_isKindOf(query_set, target_model, query_start=None, allow_permutations=False, State=IsKindOfState):
    #print "query.search_isKindOf"
    if not issubclass(State,IsKindOfState):
        assert 0
        
    return search(query_set, target_model, query_start, allow_permutations, State)

def search(query_set, target_model, query_start=None, allow_permutations=False, State=QueryState):
    # NOTE: search used to match elements which were part of a query element but not in the query_set
    # NOTE: however, this is not the case anymore so make sure to include all elements in query_set
    
    if not query_set or not target_model: return []

    # if the caller didn't specify the start point then an arbitrary one is chosen
    if not query_start:
        query_start=query_set[0]
    else:
        assert query_start in query_set

    # find all elements in the target that might match to the query element.
    target_starts=target_model.__getAllParts__().select(lambda p: State.compatible_type( query_start, p))

#     if not allow_permutations:# and len(target_starts) > 100: # Not useful for small graphs since the thing takes time it-self
#         # Calculate the symmetry of the query
#         # If the query matches it-self only one time then we don't
#         # need to filter permutations
#         matches=0
#         for start in query_set:
#             matches += len(search(query_set, start, query_set[0], True, TwoMatchState))
#             if matches>0:
#                 break
#         if matches == 0:
#             allow_permutation=True

    # initialize the result collection
    results = Results(allow_permutations)

    match_data = MatchData(query_set)

    # match every possible target element to the query start element.
    for target_start in target_starts:

        state=State(match_data)

        if state.isFeasiblePair(query_start, target_start):
            state.addPair(query_start, target_start)
            match(query_start,target_start, state)
            
            for res in state.getResults():
                results.append(res)

    return results.getList()

def match(q_elem, t_elem, state, match_primitive = True):
    if isinstance(q_elem,coral.ElementPrimitive):
        assert 0

    # Start by matching only the slots which contain ElementPrimitives.
    if match_primitive:
        #print "match primitive in ",q_elem
        for primitive_prop in state.primitive_properties(q_elem):
            propName=primitive_prop.getName()
            q_slot=q_elem.__getSlotByName__(propName)
            t_slot=t_elem.__getSlotByName__(propName)
            
            for q_item,t_item in map(None,q_slot,t_slot):
                if not q_item == t_item:
                    #print q_item
                    if (len(str(q_item)) and str(q_item)[0] != "*"): # Remove this line when wildcard is not used anymore
                        return False

    state1=state.clone()
    state1.push(q_elem, t_elem) # return later

    # Next up are the slots which contain Elements.
    nothing_new=True
    for prop in state.element_properties(q_elem):

        #if state.isIgnored(q_elem, prop):
        #    continue

        propName=prop.getName()
        q_slot=q_elem.__getSlotByName__(propName)
        t_slot=t_elem.__getSlotByName__(propName)

        allMatched=True
        for q_item in q_slot:
            #if state1.do_not_follow(q_item):
            #    continue

            if not q_item in state.match_data.query_set:
                continue
            
            if state1.q2t.has_key(q_item): # Has it been matched already?
                if state1.q2t[q_item] in t_slot: # Does the t_elem have the same edge
                    continue
                else:
                    return False
                
            nothing_new=False # if we find an association that has not been matched we can not know if this isGoal
                
            allMatched=False
            for t_item in t_slot:
                if t_item in state1.q2t.values(): # This belongs to someone already
                    continue

                if state1.isFeasiblePair(q_item,t_item): # Can we add it to the set?
                    state2=state1.clone()
                    state2.addPair(q_item,t_item) 
                        
                    if match(q_item,t_item,state2):
                        allMatched=True
                        break

            if allMatched:
                continue

            return False




    if nothing_new:                    # every edge has been matched previously
        if state.stack_size()==0:      # we are in the element that we started from
            if state.isGoal():         # did we find mappings for all query elements?
                return state.visitor() # return false to get more matches
        else:
            a,b=state.pop()
            return match(a,b,state,False)

    # It doesn't get this far if the visitor always returns false
    # Only if the visitor is satisfied with one result it will return True and get to this point
    return True
