########################################################################
#
# File Name:            Action.py
#
# Documentation:        http://docs.4suite.org/4Rdf/Inference/Action.py.html
#
"""

WWW: http://4suite.org/4RDF         e-mail: support@4suite.org

Copyright (c) 1999 Fourthought Inc, USA.   All Rights Reserved.
See  http://4suite.org/COPYRIGHT  for license and copyright information
"""

import types,string

from Ft import Rdf
from Ft.Lib import Set
from Ft.Rdf.Statement import Statement

import Common

class Action:
    def __init__(self, id_):
        self.id = id_
        self.type = Common.ArgumentTypes.ACTION
        return

    def execute(self, infEng, context):
        raise "Must Override"


##Actions to split up a statment
class StatementSubject(Action):
    def __init__(self,item):
        self.item = item
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#subject')

    def execute(self,infEng,context):
        return map(lambda x:x[0],self.item.execute(infEng,context))

    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        rt = iStr + "<ril:subject>\n"
        rt = rt + self.item._4rdf_dump(indent+1)
        rt = rt + iStr + "</ril:subject>\n"

        return rt

class StatementPredicate(Action):
    def __init__(self,item):
        self.item = item
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#predicate')

    def execute(self,infEng,context):
        return map(lambda x:x[1],self.item.execute(infEng,context))

    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        rt = iStr + "<ril:predicate>\n"
        rt = rt + self.item._4rdf_dump(indent+1)
        rt = rt + iStr + "</ril:predicate>\n"

        return rt


class StatementObject(Action):
    def __init__(self,item):
        self.item = item
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#object')

    def execute(self,infEng,context):
        return map(lambda x:x[2],self.item.execute(infEng,context))

    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        rt = iStr + "<ril:object>\n"
        rt = rt + self.item._4rdf_dump(indent+1)
        rt = rt + iStr + "</ril:object>\n"
        return rt



#Path actions
class PathsAction(Action):
    """A Path step is chain of prediactes from the start to the stop
    Given the following statements:
    (A,creator,B)
    (B,name,foo)

    and a start of A, and end end of foo

    The path would be [creastor,name]

    """
    def __init__(self,startSubject,endObject,allowedPredicates):
        self.start = startSubject
        self.end = endObject
        self.predicates = allowedPredicates
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#paths')

    def execute(self,infEng,context):
        results = []

        #FIXME should we keep track of statements visited?
        #FIXME I should probably look up the salesman problems!


        start = self.start.execute(infEng,context)
        end = self.end.execute(infEng,context)
        predicates = self.predicates.execute(infEng,context)


        if len(start) != len(end):
            raise "Start and end must be the same length"
        res = []
        for s,e in map(lambda x,y: (x,y),start,end):
            res.append(self.__recurseFind(infEng,context,s,predicates,e,[]))
        return res
    
    def __recurseFind(self,infEng,context,curSub,predicates,end,traversed):
        results = []
        for pred in predicates:
            if infEng.contains(Statement(curSub,pred,end)):
                #We got one!!!
                results.append(traversed[:] +  [(curSub,pred,end)])
            else:
                #See if we are just another step
                triples = infEng.complete(curSub,pred,'')
                for trip in triples:
                    rt = self.__recurseFind(infEng,context,trip.object,predicates,end,traversed+[(trip.subject,pred,trip.object)])
                    if rt:
                        results = results + rt
            
        return results

    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        iStr2 = "\t"*(indent+1)
        iStr3 = "\t"*(indent+2)
        rt = iStr + "<ril:paths>\n"
        rt = rt + iStr2 + "<ril:start>\n"
        rt = rt + self.start._4rdf_dump(3)
        rt = rt + iStr2 + "</ril:start>\n"
        rt = rt + iStr2 + "<ril:end>\n"
        rt = rt + self.end._4rdf_dump(3)
        rt = rt + iStr2 + "</ril:end>\n"
        rt = rt + iStr2 + "<ril:predicate-list>\n"
        for pred in self.predicates.value:
            rt = rt + iStr3 + "<ril:predicate id='%s'/>\n" % pred
        rt = rt + iStr2 + "</ril:predicate-list>\n"
        rt = rt + iStr + "</ril:paths>\n"
        return rt

class ReversePathsAction(Action):
    """Find all paths given a starting object and an ending subject.  Traverse backwards"""
    def __init__(self,startObject,endSubject,allowedPredicates):
        self.start = startObject
        self.end = endSubject
        self.predicates = allowedPredicates
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#reverse-paths')

    def execute(self,infEng,context):
        results = []

        #FIXME should we keep track of statements visited?
        #FIXME I should probably look up the salesman problems!

        start = self.start.execute(infEng,context)
        end = self.end.execute(infEng,context)
        predicates = self.predicates.execute(infEng,context)


        if len(start) != len(end):
            raise "Start and end must be the same length"
        res = []
        for s,e in map(lambda x,y: (x,y),start,end):
            res.append(self.__recurseFind(infEng,context,s,predicates,e,[]))
        return res
    
    def __recurseFind(self,infEng,context,curObj,predicates,end,traversed):
        results = []
        for pred in predicates:
            if infEng.contains(Statement(end,pred,curObj)):
                #We got one!!!
                results.append(traversed[:] +  [(end,pred,curObj)])
            else:
                #See if we are just another step
                triples = infEng.complete('',pred,curObj)
                for trip in triples:
                    rt = self.__recurseFind(infEng,context,trip.subject,predicates,end,traversed+[(trip.subject,pred,trip.object)])
                    if rt:
                        results = results + rt
            
        return results

    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        iStr2 = "\t"*(indent+1)
        iStr3 = "\t"*(indent+2)
        rt = iStr + "<ril:reverse-paths>\n"
        rt = rt + iStr2 + "<ril:start>\n"
        rt = rt + self.start._4rdf_dump(3)
        rt = rt + iStr2 + "</ril:start>\n"
        rt = rt + iStr2 + "<ril:end>\n"
        rt = rt + self.end._4rdf_dump(3)
        rt = rt + iStr2 + "</ril:end>\n"
        rt = rt + iStr2 + "<ril:predicate-list>\n"
        for pred in self.predicates.value:
            rt = rt + iStr3 + "<ril:predicate id='%s'/>\n" % pred
        rt = rt + iStr2 + "</ril:predicate-list>\n"
        rt = rt + iStr + "</ril:reverse-paths>\n"
        return rt


#Some set functions
class IndexAction(Action):
    def __init__(self,arg,index):
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#unique')
        self.arg = arg
        self.index = int(index)
        return

    def execute(self,infEng,context):
        args = self.arg.execute(infEng,context)
        item = args[self.index]
        if type(item) not in [types.ListType,types.TupleType]:
            item = [item]
        return item
    
    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        rt = iStr + "<ril:index index = '%s'>\n" % self.index
        rt = rt + self.arg._4rdf_dump(indent+1)
        rt = rt + iStr + "</ril:index>\n"
        return rt

class ReverseAction(Action):
    def __init__(self,arg):
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#reverse')
        self.arg = arg
        return

    def execute(self,infEng,context):
        args = self.arg.execute(infEng,context)[:]
        args.reverse()
        return args
    
    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        rt = iStr + "<ril:reverse>\n"
        rt = rt + self.arg._4rdf_dump(indent+1)
        rt = rt + iStr + "</ril:reverse>\n"
        return rt

class SliceAction(Action):
    def __init__(self,arg,start,end):
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#slice')
        self.arg = arg
        self.start = int(start)
        self.end = int(end)
        return

    def execute(self,infEng,context):
        args = self.arg.execute(infEng,context)[:]
        rt = args[self.start:self.end]
        return rt
    
    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        rt = iStr + "<ril:slice start = '%s' end = '%s'>\n" % (self.start,self.end)
        rt = rt + self.arg._4rdf_dump(indent+1)
        rt = rt + iStr + "</ril:slice>\n"
        return rt

class UniqueAction(Action):
    """Make a list unique"""
    def __init__(self,arg):
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#unique')
        self.arg = arg
        return

    def execute(self,infEng,context):
        return Set.Unique(self.arg.execute(infEng,context))

    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        rt = iStr + "<ril:unique>\n"
        rt = rt + self.arg._4rdf_dump(indent+1)
        rt = rt + iStr + "</ril:unique>\n"
        return rt

class IntersectionAction(Action):
    def __init__(self,arg,arg1):
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#intersection')
        self.arg = arg
        self.arg1 = arg1
        return

    def execute(self,infEng,context):
        return Set.Intersection(self.arg.execute(infEng,context),self.arg1.execute(infEng,context))

    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        rt = iStr + "<ril:intersection>\n"
        rt = rt + self.arg._4rdf_dump(indent+1)
        rt = rt + self.arg1._4rdf_dump(indent+1)
        rt = rt + iStr + "</ril:intersection>\n"
        return rt


class UnionAction(Action):
    def __init__(self,arg,arg1):
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#union')
        self.arg = arg
        self.arg1 = arg1
        return

    def execute(self,infEng,context):
        return Set.Union(self.arg.execute(infEng,context),self.arg1.execute(infEng,context))

    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        rt = iStr + "<ril:union>\n"
        rt = rt + self.arg._4rdf_dump(indent+1)
        rt = rt + self.arg1._4rdf_dump(indent+1)
        rt = rt + iStr + "</ril:union>\n"
        return rt
    
class DifferenceAction(Action):
    def __init__(self,arg,arg1):
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#difference')
        self.arg = arg
        self.arg1 = arg1
        return

    def execute(self,infEng,context):
        return Set.Not(self.arg.execute(infEng,context),self.arg1.execute(infEng,context))

    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        rt = iStr + "<ril:difference>\n"
        rt = rt + self.arg._4rdf_dump(indent+1)
        rt = rt + self.arg1._4rdf_dump(indent+1)
        rt = rt + iStr + "</ril:difference>\n"
        return rt
    
###Some other agregate functions
class SumAction(Action):
    def __init__(self,arg):
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#sum')
        self.arg = arg
        return

    def execute(self,infEng,context):
        args = self.arg.execute(infEng,context)
        return [str(reduce(lambda y,x,s=string.atof:y+s(x),args,0))]

    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        rt = iStr + "<ril:sum>\n"
        rt = rt + self.arg._4rdf_dump(indent+1)
        rt = rt + iStr + "</ril:sum>\n"
        return rt

class CountAction(Action):
    def __init__(self,arg):
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#sum')
        self.arg = arg
        return

    def execute(self,infEng,context):
        args = self.arg.execute(infEng,context)
        return [str(len(args))]

    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        rt = iStr + "<ril:count>\n"
        rt = rt + self.arg._4rdf_dump(indent+1)
        rt = rt + iStr + "</ril:count>\n"
        return rt

class AverageAction(Action):
    def __init__(self,arg):
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#ave')
        self.arg = arg
        return

    def execute(self,infEng,context):
        args = self.arg.execute(infEng,context)
        return [str(float(reduce(lambda y,x,s=string.atof:y+s(x),args,0))/len(args))]

    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        rt = iStr + "<ril:average>\n"
        rt = rt + self.arg._4rdf_dump(indent+1)
        rt = rt + iStr + "</ril:average>\n"
        return rt

###A sort action
class SortType:
    NUMBER_SORT = 1
    STRING_SORT = 2
g_keyMap = {'subject':0,
            'predicate':1,
            'object':2,
            }
class SortAction(Action):
    """Sort a list of statements based on the object""" 
    def __init__(self,arg,sortType,key="object"):
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#sort')
        self.arg = arg
        self.sortType = sortType
        self.sortIndex = g_keyMap[string.lower(key)]
        return

    def execute(self,infEng,context):
        args = self.arg.execute(infEng,context)[:]
        args.sort(self.__sortFunc)
        return args

    def __sortFunc(self,left,right):
        left = left[self.sortIndex]
        right = right[self.sortIndex]
        if self.sortType == SortType.NUMBER_SORT:
            left = string.atof(left)
            right = string.atof(right)
        return cmp(left,right)

    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        mode = "TEXT"
        if self.sortType == SortType.NUMBER_SORT:
            mode = 'NUMBER'
        key="subject"
        if self.sortIndex == 1:
            key='predicate'
        elif self.sortIndex == 2:
            key='object'
        rt = iStr + "<ril:sort key='%s' mode='%s'>\n" % (key,mode)
        rt = rt + self.arg._4rdf_dump(indent+1)
        rt = rt + iStr + "</ril:sort>\n"
        return rt



class VariableSetAction(Action):
    def __init__(self,name,val):
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#variable-set')
        self.value = val
        self.name = name
        return

    def execute(self,infEng,context):
        val = self.value.execute(infEng,context)
        if not type(val) in [types.ListType,types.TupleType]:
            val = [val]
        context.variables[self.name] = val

    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        rt = iStr + "<ril:variable-set name='%s'>\n" % self.name
        rt = rt + self.value._4rdf_dump(indent+1)
        rt = rt + iStr + "</ril:variable-set>\n"
        return rt

        
class ExternalParamAction(Action):
    def __init__(self,name,val):
        Action.__init__(self,Rdf.RIL_NAMESPACE + '#param')
        self.defaultValue = val
        self.prefix,self.localName = Common.SplitQName(name)
        return

    def execute(self,infEng,context):
        fullName = self.localName
        if self.prefix:
            uri = context.resolvePrefix(self.prefix)
            fullName = uri + '#' + self.localName

        val = context.external_params.get(fullName)
        if val is None:
            val = self.defaultValue.execute(infEng,context)
        if not type(val) in [types.ListType,types.TupleType]:
            val = [val]
        context.variables[fullName] = val

    def _4rdf_dump(self,indent = 0):
        iStr = "\t"*indent
        rt = iStr + "<ril:param name='%s%s'>\n" % (self.prefix and self.prefix+':' or "",self.localName)
        rt = rt + self.defaultValue._4rdf_dump(indent+1)
        rt = rt + iStr + "</ril:param>\n"
        return rt


        

    

