#
#  evaluate.py contains the evaluator implementations for the meta-model derived from Constraint.
#  The meta-model element should have a function that returns the path to the implementation.
#  An Evaluator should be derived from ConstraintEvaluator and implement the same interface.
#  Look at the documentation string to find out what the functions do.
#

import sys
import coral
import coral.util.search.query as Q
import coral.util.search.cquery as CQ
import coral.util.search.util as SU

from code import InteractiveInterpreter
from exceptions import *
import string
L = coral.lang.CONSTRAINT10

class II(InteractiveInterpreter):

    def __init__(self, locals = {}):
        self.fail = False
        self.message = []
        InteractiveInterpreter.__init__(self, locals)
        
    def write(self, data):
        self.message.append(data)
        self.fail = True
        
    def runcode(self, code):
        InteractiveInterpreter.runcode(self, code)

class ConstraintEvaluator:
    def __init__(self, constraintElement):
        self.elem = constraintElement

    def searchApply(self, target):
        """Applies this constraint on all elements in target"""
        raise NotImplemented("Constraint.searchApply should be overridden")
        
    def apply(self, match):
        """Applies this constraint on element match"""
        raise NotImplemented("Constraint.apply should be overridden")

    def appliesTo(self, element):
        """Checks if this constraint applies to element"""
        raise NotImplemented("Constraint.appliesTo should be overridden")
        
    def __str__(self):
        return str(self.elem)
    


class MessageBuffer:
    def __init__(self):
        self.buffer = []

    def write(self, data):
        self.buffer.append(data)

class GraphicalScriptConstraintEvaluator(ConstraintEvaluator):
    def __init__(self, query, script):
        ConstraintEvaluator.__init__(self,None)
        self.code = compile(script, "script", "exec")
        self.query = query

    def searchApply(self, target):
        for match in Q.search_isKindOf(SU.get_all_parts(self.query), target):
            self.locals = {"match":match, "coral":coral, "ret":None}        
            self.apply(match)

            
            #TODO: create an instance of Result with information

    def apply(self, match):
        self.fillLocals(self.locals, match)
        ii = II(self.locals)
        mb = MessageBuffer()
        sys.stdout = mb
        try:
            ii.runcode(self.code)
        finally:
            sys.stdout = sys.__stdout__

#        print string.join(mb.buffer)
        if ii.fail:
            raise Exception(string.join(ii.message))

    def fillLocals(self, locals, match):
        for q_elem in match:
            if hasattr(q_elem, "name") and q_elem.name != "":
                if q_elem.name[0] == "*":
                    self.locals[str(q_elem.name[1:])] = match[q_elem]
                else:
                    self.locals[str(q_elem.name)] = match[q_elem]

class QueryScriptConstraintEvaluator(ConstraintEvaluator):
    def __init__(self, QSConstraint):
        ConstraintEvaluator.__init__(self, QSConstraint)
        self.query = QSConstraint.query
        self.types = coral.Set()
        for qelem in self.query.queryelement:
            if qelem.isActive:
                for part in qelem.abstractElement.__getAllParts__():
                    self.types.append(part.__type__())

        scriptList = []
        scriptList.append("import coral\ndef script():\n")
        for line in str(QSConstraint.script).split('\n'):
            scriptList.append('\t'+line+'\n')
        scriptList.append("ret = script()")
        scriptFunction = string.join(scriptList,"")

        self.code = compile(str(scriptFunction), "QSC script", "exec")
    def appliesTo(self, element):
        return self.types.exists(lambda t: element.oclIsKindOf(t))

    def searchApply(self, target):
        results=[]

        for match in CQ.csearch(self.query, target):
            try:
                result = self.applyWithMatch(match)

                if result:
                    results.append(result)
                    
            except Exception, exce:
                results.append(L.Result(message=str(exce),constraint=self.elem,name=self.elem.name))

        return results

    def apply(self, elem):
        results=[]
        matches=CQ.csearch_elem(self.query, elem)
        for match in matches:
            try:
                retval=self.applyWithMatch(match)
                if retval:
                    results.append(retval)

            except Exception, e:
                result=[L.Result(message=str(e),constraint=self.elem,name=self.elem.name)]
                return result

        return results

    def applyWithMatch(self, match):
        locals = {"match":match, "coral":coral, "ret":None}
        self.fillLocals(locals, match)
        ii = II(locals)
        mb = MessageBuffer()
        sys.stdout = mb
        try:
            ii.runcode(self.code)
        finally:
            sys.stdout = sys.__stdout__
            
        if ii.fail:
            raise Exception(string.join(ii.message))

        if not locals["ret"]:
            result = L.Result(message=self.elem.description+"\n"+string.join(mb.buffer),name=self.elem.name)
            result.constraint=self.elem
            for val in match.values():
                #print val
                result.offending.append(val)
                
            return result

        return None
    
    def fillLocals(self, locals, match):
        for q_elem in self.query.queryelement:
            locals[str(q_elem.name)] = match[q_elem.abstractElement]
        
class PythonConstraintEvaluator(ConstraintEvaluator):
    def __init__(self, PConstraint):
        ConstraintEvaluator.__init__(self, PConstraint)
        scriptList = []
        scriptList.append("import coral\ndef script():\n")
        for line in str(PConstraint.script).split('\n'):
            scriptList.append('\t'+line+'\n')
        scriptList.append("def addOffending(elem):\n")
        scriptList.append("\toffending.append(elem)\n")
        scriptList.append("ret = script()")
        scriptFunction = string.join(scriptList,"")
        self.code = compile(str(scriptFunction), "PC script", "exec")
        
    def searchApply(self, target):
        results=[]
        elem_kind=coral.AbstractElementDefinition_getAbstractElementDefinitionFromSMD(self.elem.kind)
        for match in target.__getAllParts__():

            if match.oclIsKindOf(elem_kind):
                #self.locals = {"match":match, "coral":coral, "ret":None, "self":match}
                try:
                    result=self.apply(match)
                    if result:
                        #assert result.constraint
                        for res in result:
                            results.append(res)
                except Exception, e:
                    return results+[L.Result(message=str(e),constraint=self.elem,name=self.elem.name)]

        return results

    def appliesTo(self, element):
        if not self.elem.kind:
            return False

        elem_kind=coral.AbstractElementDefinition_getAbstractElementDefinitionFromSMD(self.elem.kind)
        if element.oclIsKindOf(elem_kind):
            return True

        return False
        
    def apply(self, match):
        offending=[]
        locals={"offending":offending,"ret":None,"self":match}
        ii = II(locals)
        mb = MessageBuffer()
        sys.stdout = mb
        try:
            ii.runcode(self.code)
        finally:
            sys.stdout = sys.__stdout__
            
        #print string.join(mb.buffer)
        if ii.fail:
            raise Exception(string.join(ii.message))

        if not locals["ret"]:
            result = L.Result(message=self.elem.description+"\n"+string.join(mb.buffer),name=self.elem.name)
            result.offending.append(match)
            #print locals["offending"]
            for off in locals["offending"]:
                result.offending.append(off)

            result.constraint=self.elem
            return [result]

        return []
    
    #print "\x1b[31;01mConstraintError:\x1b[39;49;00m",e
