# cquery.py Implements matching for queries specified using CQUERY10
#
# Functions:
#             csearch_elem
#             csearch

import coral
from query import *


#def cquery_search_elem_isKindOf(query, elem):
def csearch_elem(query, target_elem):
    if not isinstance(query, coral.Element):
        print "csearch_elem takes an Element as its first parameter not",type(query)
        return []
    if not isinstance(target_elem, coral.Element):
        print "csearch_elem takes an Element as its second parameter"
        return []



    State=coral.util.search.state.IsKindOfState

    query_set=coral.Set()
    for q_elem in query.queryelement:
        query_set.append(q_elem.abstractElement)

    allow_permutations=False
    results=Results(allow_permutations)

    match_data = MatchData(query_set)
    for qelem in query.queryelement:
        for meta_prop in qelem.ignore:
            match_data.addIgnored(qelem.abstractElement, meta_prop)

    
    for q_elem in query_set:
        state=State(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:
                for result in rs:
                    results.append(result)

    return results.getList()



# query is of type CQUERY10::Query and target can be of any
# type inherited from SMD01::AnyAbstractElement. The target
# is normally a root element such as a UML14::Model.

#def cquery_search_isKindOf(query, target, allow_permutations=False):
def csearch(cquery, target, allow_permutations=False):

    State=coral.util.search.state.IsKindOfState

    query_start=cquery.queryelement[0].abstractElement
    query_set=coral.Set()

    for qelem in cquery.queryelement:
        if qelem.isActive:
            query_start=qelem.abstractElement
            break

    for qelem in cquery.queryelement:
        query_set.append(qelem.abstractElement)

    target_starts=target.__getAllParts__().select(lambda p: State.compatible_type( query_start, p))

    # initialize the result collection
    results=Results(allow_permutations)

    match_data = MatchData(query_set)
    for qelem in cquery.queryelement:
        for meta_prop in qelem.ignore:
            match_data.addIgnored(qelem.abstractElement, meta_prop)

    originalState=State(match_data)

    for target_start in target_starts:
        state=originalState.clone()
        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()



#
# Old implementation that modified the query graph before searching.
#


def csearch_elemx(query, elem):
    if not isinstance(query, coral.Element):
        print "csearch_elem takes an Element as its first parameter not",type(query)
        return []
    if not isinstance(elem, coral.Element):
        print "csearch_elem takes an Element as its second parameter"
        return []

    # Method 1: Prune query graph and use search as is
    query_set=get_pruned_query_set(query)
    return  match_query_elem(query_set, elem)

    # Method 2: Populate the skip list used in state with elements from ignored slots
    #           Don't use search but implement the same functionallity here..
    # Not implemented
    
    

def csearchx(query, target, allow_permutations=False):
    # Uses search_isKindOf
    if not isinstance(query, coral.Element):
        print "csearch takes an Element as its first parameter not",type(query)
        return []
    if not isinstance(target, coral.Element):
        print "csearch takes an Element as its second parameter"
        return []

    # Method 1: Prune query graph and use search as is
    query_set=get_pruned_query_set(query)
    
    first_active_elem=None
    for qelem in query.queryelement:
        if qelem.isActive:
            first_active_elem=qelem.abstractElement
            break

    return search_isKindOf(query_set, target, first_active_elem, allow_permutations)



def get_pruned_query_set(query):#Tested
    # TODO: check that pruning doesn't split the query graph
    #       Do this by checking if the slot contains query elements for instance.

    # TODO: Maybe model copy the query since pruning affects the original query elements

    query_set=coral.Set()
    # TODO: What if we want containedElements?
    for query_element in query.queryelement:
        pruned_element=prune_ignore_properties(query_element)
        query_set.append(pruned_element)
        
    return query_set

def prune_ignore_properties(query_element):#Tested
    element=query_element.abstractElement
    for prop in query_element.ignore:
        slot=element.__getSlotByName__(str(prop.name))
        slot_elements=slot[:]
        for slot_element in slot_elements:
            slot.remove(slot_element)
        assert len(slot)==0
    return element
