"""
RIF Core parser for FuXi.

Parses RIF Core XML (and RIF In RDF) syntaxes into FuXi (converts the former into the latter).
Supports Frames and atoms with only two positional arguments.  Follows import trail
"""

import urllib2, warnings
from cStringIO import StringIO
from amara.lib import iri,inputsource
from amara.xslt  import transform
from rdflib.Graph import Graph
from rdflib import Namespace, RDF, Variable, URIRef
from rdflib.util import first
from rdflib.Collection import Collection
from FuXi.Rete.RuleStore import N3Builtin, SetupRuleStore
from FuXi.Horn.PositiveConditions import *
from FuXi.Horn.HornRules import Rule, Clause

class SmartRedirectHandler(urllib2.HTTPRedirectHandler):
    def http_error_301(self, req, fp, code, msg, headers):
        result = urllib2.HTTPRedirectHandler.http_error_301(
            self, req, fp, code, msg, headers)
        result.status = code
        return result

    def http_error_302(self, req, fp, code, msg, headers):
        result = urllib2.HTTPRedirectHandler.http_error_302(
            self, req, fp, code, msg, headers)
        result.status = code
        return result

RIF_NS = Namespace('http://www.w3.org/2007/rif#')
XSD_NS = Namespace('http://www.w3.org/2001/XMLSchema#')
ENT    = Namespace("http://www.w3.org/ns/entailment/")

mimetypes = {
    'application/rdf+xml' : 'xml',
    'text/n3'             : 'n3',
    'text/turtle'         : 'turtle',
}

TRANSFORM_URI = iri.absolutize('rif-core-rdf.xsl',iri.os_path_to_uri(__file__))

IMPORT_PARTS=\
"""
SELECT DISTINCT ?location ?profile {
    []    a             rif:Import;
          rif:location  ?location;
          rif:profile   ?profile .
}"""

IMPLIES_PARTS=\
"""
SELECT DISTINCT ?impl ?body ?bodyType ?head ?headType {
    ?impl a             rif:Implies;
          rif:if        ?body;
          rif:then      ?head .
    ?body a             ?bodyType .
    ?head a             ?headType .
}
"""

MEMBER_PARTS =\
"""
SELECT DISTINCT ?member ?class ?instance {
    ?member     a             rif:Member;
                rif:class     ?class;
                rif:instance  ?instance
}"""

EXISTS_PARTS =\
"""
SELECT DISTINCT ?exists ?formula ?formulaType ?vars {
    ?exists     a             rif:Exists;
                rif:vars      ?vars;
                rif:formula   ?formula .
    ?formula    a             ?formulaType
}
"""

RULE_PARTS =\
"""
SELECT DISTINCT ?rule ?vars ?impl {
    ?rule a             rif:Forall;
          rif:formula   ?impl;
          rif:vars      ?vars
}
"""

FRAME_PARTS =\
"""
SELECT ?frame ?object ?slots {
    ?frame  a           rif:Frame;
            rif:object  ?object;
            rif:slots   ?slots
}
"""

EXTERNAL_PARTS=\
"""
SELECT ?external ?args ?op {
    ?external   a           rif:External;
                rif:content [ a rif:Atom; rif:args ?args; rif:op ?op ]
}
"""

ATOM_PARTS=\
"""
SELECT ?atom ?args ?op {
    ?atom   a        rif:Atom;
            rif:args ?args;
            rif:op ?op
}
"""

rif_namespaces = { u'rif':RIF_NS }

class RIFCoreParser(object):
    def __init__(self,
                 location=None,
                 graph=None,
                 debug=False,
                 nsBindings = None,
                 owlEmbeddings = False):
        self.owlEmbeddings = owlEmbeddings
        self.nsBindings = nsBindings if nsBindings else {}
        self.location = location
        self.rules = {}
        self.debug = debug
        if graph:
            assert location is None,"Must supply one of graph or location"
            self.graph = graph
            if debug:
                print "RIF in RDF graph was provided"
        else:
            assert graph is None,"Must supply one of graph or location"
            if debug:
                print "RIF document URL provided ", location
            if self.location.find('http:')+1:
                req = urllib2.Request(self.location)

                ##From: http://www.diveintopython.org/http_web_services/redirects.html
                #points an 'opener' to the address to 'sniff' out final Location header
                opener = urllib2.build_opener(SmartRedirectHandler())
                f = opener.open(req)
                self.content = f.read()
            else:
                try:
                    self.content = urllib2.urlopen(self.location).read()
                except ValueError:
                    self.content = urllib2.urlopen(iri.os_path_to_uri(self.location)).read()
#                self.content = open(self.location).read()
            try:
                rdfContent = transform(self.content,inputsource(TRANSFORM_URI))
                self.graph = Graph().parse(StringIO(rdfContent))
                if debug:
                    print "Extracted rules from RIF XML format"
            except ValueError:
                try:
                    self.graph = Graph().parse(StringIO(self.content),format='xml')
                except:
                    self.graph = Graph().parse(StringIO(self.content),format='n3')
                if debug:
                    print "Extracted rules from RIF in RDF document"
        self.nsBindings.update(dict(self.graph.namespaces()))

    def handleImport(self):
        additionalRules = set()
        additionalFacts = set()
        for location,profile in self.graph.query(IMPORT_PARTS,
                                                 initNs=rif_namespaces):
            graph = []
            if profile == ENT.RDF:
                graph = Graph().parse(location)
                additionalFacts.update(graph)
                if self.debug:
                    print "Importing RDF referenced from RIF document"
            if profile == ENT['OWL-Direct'] and self.owlEmbeddings:
                rule_store, rule_graph, network = SetupRuleStore(makeNetwork=True)
                graph = Graph().parse(location)
                additionalFacts.update(graph)
                additionalRules.update(network.setupDescriptionLogicProgramming(
                    graph,
                    addPDSemantics=False,
                    constructNetwork=False))
                if self.debug:
                    print "Embedded %s rules from %s (imported OWL 2 RL)"%(
                        len(additionalRules),
                        location
                    )
            print "Added %s RDF statements from RDF Graph"%(len(graph))
        return additionalFacts,additionalRules

    def getRuleset(self):
        """
        >>> parser = RIFCoreParser('http://www.w3.org/2005/rules/test/repository/tc/Frames/Frames-premise.rif')
        >>> for rule in parser.getRuleset(): print rule
        Forall ?Customer ( ns1:discount(?Customer 10) :- ns1:status(?Customer "gold"^^<http://www.w3.org/2001/XMLSchema#string>) )
        Forall ?Customer ( ns1:discount(?Customer 5) :- ns1:status(?Customer "silver"^^<http://www.w3.org/2001/XMLSchema#string>) )
        >>> parser = RIFCoreParser('http://www.w3.org/2005/rules/test/repository/tc/Guards_and_subtypes/Guards_and_subtypes-premise.rif')
        >>> for rule in parser.getRuleset(): print rule
        """
        self.members = dict((_member,(_cls,_inst))
            for _member,_cls,_inst in self.graph.query(MEMBER_PARTS,initNs=rif_namespaces)
        )

        self.exists = dict((exists,(formula, formulaType, vars))
            for exists, formula, formulaType, vars in
                self.graph.query(EXISTS_PARTS,initNs=rif_namespaces)
        )

        self.implications = dict([(impl,(body,bodyType,head,headType))
                                    for impl,body,bodyType,head,headType in
                                        self.graph.query(IMPLIES_PARTS,initNs=rif_namespaces)])
        self.rules = dict([ (rule,(vars,impl))
                                for rule,vars,impl
                                    in self.graph.query(RULE_PARTS,
                                                        initNs=rif_namespaces)])
        self.frames = dict([ (frame,(obj,slots))
            for frame,obj,slots in self.graph.query(FRAME_PARTS,
                                                    initNs=rif_namespaces)])

        self.atoms = dict([ (atom,(args,op))
                                for atom, args, op in self.graph.query(
                                        ATOM_PARTS,
                                        initNs=rif_namespaces) ])

        self.externals = dict([(external,(args,op))
                               for external,args,op in self.graph.query(
                                    EXTERNAL_PARTS,
                                    initNs=rif_namespaces) ])
        rt          = set()
        groundFacts = set()
        for sentenceCollection in self.graph.objects(predicate=RIF_NS.sentences):
            col = Collection(self.graph,sentenceCollection)
            for sentence in col:
                if RIF_NS.Implies in self.graph.objects(sentence,RDF.type):
                    rt.add(self.extractImp(sentence))
                elif RIF_NS.Forall in self.graph.objects(sentence,RDF.type):
                    rt.add(self.extractRule(sentence))
                elif RIF_NS.Frame in self.graph.objects(sentence,RDF.type):
                    frames = self.extractFrame(sentence)
                    for term in frames:
                        if term.isGround():
                            triple = term.toRDFTuple()
                            if triple not in groundFacts:
                                groundFacts.add(triple)
        additionalFacts,additionalRules=self.handleImport()
        if additionalRules:
            rt.update(additionalRules)
        if additionalFacts:
            groundFacts.update(additionalFacts)
        return list(rt),list(groundFacts)

    def extractImp(self,impl):
        body,bodyType,head,headType = self.implications[impl]
        head = first(self.extractPredication(head,headType))
        if bodyType == RIF_NS.And:
            raise
        else:
            body = self.extractPredication(body,bodyType)

        body = And([first(body)]) if len(body) == 1 else And(body)
        return Rule(Clause(body,head),declare=[],nsMapping=self.nsBindings)

    def extractRule(self,rule):
        vars,impl = self.rules[rule]
        body,bodyType,head,headType = self.implications[impl]
        allVars = map(self.extractTerm,Collection(self.graph,vars))
        head = first(self.extractPredication(head,headType))
        if bodyType == RIF_NS.And:
            body = map(
                   lambda i: first(self.extractPredication(
                       i,
                       first(self.graph.objects(i,RDF.type)))
                   ),
                   Collection(self.graph,first(self.graph.objects(body,RIF_NS.formulas)))
            )

        else:
            body = self.extractPredication(body,bodyType)
        if isinstance(body,list):
            body = And([first(body)]) if len(body) == 1 else And(body)
        nsMapping = {}
        nsMapping.update(self.nsBindings)
        return Rule(
            Clause(body,head),
            declare=allVars,
            nsMapping=nsMapping
        )

    def extractExists(self,exists_resource):
        formula, formulaType, vars = self.exists[exists_resource]
        allVars = map(self.extractTerm,Collection(self.graph,vars))

        if formulaType == RIF_NS.And:
            formula = And(map(
                lambda i: first(self.extractPredication(
                    i,
                    first(self.graph.objects(i,RDF.type)))
                ),
                Collection(self.graph,first(self.graph.objects(formula,RIF_NS.formulas)))
            ))

        else:
            formula = self.extractPredication(formula,formulaType)
        return Exists(formula,allVars)

    def extractPredication(self,predication,predType):
        if predType == RIF_NS.Frame:
            return self.extractFrame(predication)
        elif predType == RIF_NS.Atom:
            return [self.extractAtom(predication)]
        elif predType == RIF_NS.Exists:
            return [self.extractExists(predication)]
        elif predType == RIF_NS.Member:
            _cls,_inst = self.members[predication]
            return [Uniterm(
                RDF.type,
                [self.extractTerm(_inst),
                 self.extractTerm(_cls)],
                newNss=self.nsBindings)]
        elif predType == RIF_NS.External:
#            N3Builtin(self,uri,func,argument,result)
            args,op = self.externals[predication]
            args    = map(self.extractTerm,Collection(self.graph,args))
            op      = self.extractTerm(op)
            return [ExternalFunction(Uniterm(op,args))]
        else:
            raise SyntaxError("Unsupported RIF in RDF type: %s"%predType.n3())

    def extractAtom(self,atom):
        args,op = self.atoms[atom]
        op      = self.extractTerm(op)
        args    = map(self.extractTerm,Collection(self.graph,args))
        if len(args) > 2:
            raise NotImplementedError(
                "FuXi RIF Core parsing only supports subset involving binary/unary Atoms"
            )
        return Uniterm(op,args,newNss=self.nsBindings)

    def extractFrame(self,frame):
        obj,slots = self.frames[frame]
        rt=[]
        for slot in Collection(self.graph,slots):
            k = self.extractTerm(first(self.graph.objects(slot,RIF_NS.slotkey)))
            v = self.extractTerm(first(self.graph.objects(slot,RIF_NS.slotvalue)))
            rt.append(
                Uniterm(k,[self.extractTerm(obj),v],newNss=self.nsBindings)
            )
        return rt

    def extractTerm(self,term):
        if (term,RDF.type,RIF_NS.Var) in self.graph:
            return Variable(first(self.graph.objects(term,RIF_NS.varname)))
        elif (term,RIF_NS.constIRI,None) in self.graph:
            iriLit = first(self.graph.objects(term,RIF_NS.constIRI))
            assert iriLit.datatype == XSD_NS.anyURI
            return URIRef(iriLit)
        else:
            return first(self.graph.objects(term,RIF_NS.value))

def test():
    import doctest
    doctest.testmod()

if __name__ == '__main__':
#    test()
    parser = RIFCoreParser('http://www.w3.org/2005/rules/test/repository/tc/Guards_and_subtypes/Guards_and_subtypes-premise.rif')
    for rule in parser.getRuleset():
        print rule


