#!/usr/local/bin/python
import urllib
import os
import re
import sys
import codecs
import tempfile
from xml.dom import Node

from Ft.Rdf import RDF_SCHEMA_BASE, RDF_MS_BASE,OBJECT_TYPE_RESOURCE, OWL_NS
from Ft.Rdf._4rdf import CheckLabel
from Ft.Xml import XPath
from string import *


def ParseNsMappings(nsMappings, nsDict={}, mappingUri=None):
    if type(nsMappings) == type('') or type(nsMappings) == type(u''):
        #parsing from text        
        from Ft.Xml.Domlette import NonvalidatingReader
        mappingsNode = NonvalidatingReader.parseString(nsMappings.encode('utf-8'), mappingUri)
    elif isinstance(nsMappings, node):
        #parsing from node
        mappingsNode = nsMappings
    else:   
        return {}    
    nsDict.update({'fres':'http://xmlns.4suite.org/reserved'})

    mappingsContext = XPath.Context.Context(mappingsNode, processorNss=nsDict)

    _mappingElements = XPath.Compile('/fres:NsMappings/fres:NsMapping')
    _mappingPrefixExpression = XPath.Compile('string(Prefix)')
    _mappingUriExpression = XPath.Compile('string(Uri)')            


    mappings={}
    for nsMapping in _mappingElements.evaluate(mappingsContext):
        if nsMapping.localName == u"NsMapping":
            mappingsContext.node = nsMapping
            prefix=_mappingPrefixExpression.evaluate(mappingsContext)
            uri   =_mappingUriExpression.evaluate(mappingsContext)
            #print "parsed out %s -> %s from user ns mapping"%(prefix,uri)
            mappings[prefix]=uri
        elif nsMapping.localName == u"Merge":
            href = nsMapping.attributes.get((None, u"href"))
            ParseNsMappings(href, nsDict=mappings)
            #Go recursive

    return mappings


RECTANGLE_PATTERN = re.compile(r'rectangle\s+\([\d]+,[\d]+\)\s+\([\d]+,[\d]+\)\s+[^\s]+.*')

class RDFGraphVizEngine:
    def __init__(self, gvisDir, mapName='rdfImageMap',
                 uriFormat='%s', maxArcs=None, outputJpeg=None,useNeato=False):
        """
        """
        self._useNeato  = useNeato
        self.maxArcs = int(self._useNeato and 160 or 300)
        self.mapName = mapName
        self.uriFormat = uriFormat or '%s'
        self.gviz = gvisDir.strip()
        if outputJpeg:
            self.outputJpeg = outputJpeg
            self.outputSvg = outputJpeg[:outputJpeg.find('.')] + '.svg'
            #print self.outputSvg
        else:
            self.outputJpeg = tempfile.mktemp('.jpg')
            self.outputSvg =  tempfile.mktemp('.svg')
            #print self.outputSvg
            
        #self.tmp = tempDir.strip()
        # =  or os.path.join(self.tmp, 'output.jpg')
        dotPath = os.path.join(self.gviz, 'dot')
        winDotPath = os.path.join(self.gviz, 'dot.exe')
        if not os.path.exists(dotPath) and not os.path.exists(winDotPath):
            raise Exception("graphViz is not properly setup or not installed (cannot locate 'dot' executable at %s or %s)"%(str(dotPath),str(winDotPath)))
        
        #print "setting up RDFGraphVizEngine.  Gvis dir is %s, uriFormat is %s,  and tmp dir is at %s"%(self.gviz, self.uriFormat, self.tmp)
        #print "setting up RDFGraphVizEngine.  Gvis dir is %s, uriFormat is %s"%(self.gviz, self.uriFormat)

    def retrieveResourceUris(self, resultNode):
        resoureExpression = Compile('//Resource')
        
    def resourceExists(self, name, model):
        """
        Checks model.complete(model,None,None) to determine if name exists
        """
        try:
            stmts=model.complete(name, None, None)
        except:
            return []
        else:
            return stmts

    def resourceViewer(self, model, resourceUri=None, scoped=0, rotate=0,
                       splitpreds=1, resultTxt=None, nsDict=None,
                       resultsNode=None):
        nsDict = nsDict or {}
        #print nsDict
        singleResource = ''
        color=''     
        if resultTxt or resultsNode:
            color = 'color=red'
            stmts = []

            if resultTxt:
                from Ft.Xml.Domlette import NonvalidatingReader
                # FIXME: Find a better way to detect null results, perhaps from
                #  raw Versa data.  Otherwise we should be passing through
                #  exceptions.
                try:
                    resultsNode = NonvalidatingReader.parseString(
                        resultTxt.encode('utf-8'), 'http://4Suite.org')
                except:
                    return "No resources to view"

            from Ft.Xml import XPath
            resultsContext = XPath.Context.Context(resultsNode)

            _resourceExpr = XPath.Compile('//Resource/text() | //BlankNode/text()')
            evaluated = _resourceExpr.evaluate(resultsContext)
            if len(evaluated) == 1:
                singleResource = evaluated[0].nodeValue
            for res in evaluated:
                res = res.nodeValue      
                if len(res.split('ftss:///')) > 1:
                    res = '/' + res.split('ftss:///')[-1]                            
                #print "adding *all* statements about %s to graph"%(res)
                #print "Adding statements about %s to graph"%(res)                
                
                stmts_in_scope = []
                
                if stmts_in_scope and scoped:
                    #To avoid duplicate statements in cases of rdf:about=""
                    stmts.extend(stmts_in_scope)
                else:
                    stmts.extend(model.complete(res, '', ''))
                    stmts.extend(model.complete('', '', res))
        else:
            if scoped:
                color=''                    
                stmts = model.complete('', '', '', scope=resourceUri) or model.complete('', '', '', scope=resourceUri)
            else:
                stmts=model.complete('', '', '')

        rotate = rotate and 'rotate=90' or 'rotate=180'
        if self._useNeato:
            output = 'digraph G {overlap=scale;splines=true;center=true;orientation=land;resolution=0.96;rankdir=LR;ratio=fill;%s\n'%(rotate)
        else:
            output = 'digraph G {center=true;orientation=land;resolution=0.96;rankdir=LR;ratio=fill;%s\n'%(rotate)

        formatDict={}       
        
        arcs = len(stmts)
        if arcs > self.maxArcs:
            raise "Attempt to process %s arcs. This is beyond the recommended (%s) threshold of  raphviz's capabilities and size constraints"%(arcs,self.maxArcs)
        
        for stmt in stmts:
            kv = [(stmt.predicate, stmt.object, stmt.objectType)]
            currentKv = formatDict.get(stmt.subject, [])
            formatDict[stmt.subject] = currentKv + kv

        index = 0
        unknownIndex = len(formatDict.keys())
        for resName, resourceArcs in formatDict.items():
            if model.isBnodeLabel(resName):
                #See if it has an rdf:type statement associated with it.  Use if so
                type_stmts = model.complete(resName, RDF_MS_BASE+"type", None)
                
                if type_stmts:
                    type_str = splitPredicate(type_stmts[0].object,nsDict)
                    rdfClass=CheckLabel(model,type_str)
                    l = "Anonymous\\n%s"%(rdfClass)
                else:
                    l = CheckLabel(model,resName)
                output = output+'\t%s [label = "%s",URL="%s"]\n'%(index, l, self.uriFormat%(urllib.quote(resName)))
            else:
                output = output+'\t%s [label = "%s",URL="%s" %s]\n'%(index, ResourceName(model, resName,nsDict), self.uriFormat%(urllib.quote(resName)), color)
            for predicate, object, otype in resourceArcs:
                if predicate in [RDF_MS_BASE+"type",RDF_SCHEMA_BASE+'label']:
                    continue
                objUri = object
                if splitpreds:
                    predicate = splitPredicate(predicate, nsDict)
                if object in formatDict.keys():
                    object = formatDict.keys().index(object)
                else:
                    exists = self.resourceExists(object, model)
                    isLiteralbyOntology = model.complete(predicate,RDF_MS_BASE+'type',OWL_NS+'DatatypeProperty')
                    isResource = (otype == OBJECT_TYPE_RESOURCE) 
                    if isResource and not isLiteralbyOntology:
                        resUri = object
                        if exists:
                            output = output + '\t%s [label = "%s",URL="%s" %s]\n'%(unknownIndex, ResourceName(model, objUri,nsDict).replace('"', "'"), self.uriFormat%(urllib.quote(object)), color)    
                        else:
                            output = output + '\t%s [label = "%s"]\n'%(unknownIndex, ResourceName(model, objUri,nsDict).replace('"', "'"))                        
                        
                    else:
                        output = output + '\t%s [label = "%s", shape=box]\n'%(unknownIndex, ResourceName(model, objUri,nsDict).replace('"', "'"))
                    object = unknownIndex
                    unknownIndex += 1

                output = output + '\t%s -> %s [label = "%s"];\n'%(index, object, predicate)
            index = index + 1

        output = output + "}"        

        outputDot = tempfile.mktemp('.dot')
        outputMap = tempfile.mktemp('.map')
        dotExecPath = os.path.join(self.gviz, self._useNeato and 'neato' or 'dot')
        dotfile = codecs.open(outputDot, 'w', 'utf-8')
        dotfile.write(output)
        dotfile.close()

        # dot -Tismap output.dot -o output.map
        args = [os.path.basename(dotExecPath), '-Tismap', outputDot,
                '-o', outputMap]
        os.spawnv(os.P_WAIT, dotExecPath, args)
        
        # dot -Tjpeg output.dot -o output.jpg
        args = [os.path.basename(dotExecPath), '-Tjpeg', outputDot,
                '-o', self.outputJpeg]
        os.spawnv(os.P_WAIT, dotExecPath, args)        
        
        # dot -Tsvg output.dot -o output.svg
        args = [os.path.basename(dotExecPath), '-Tsvg', outputDot,
                '-o', self.outputSvg]
        os.spawnv(os.P_WAIT, dotExecPath, args)
        
        if not(os.path.exists(self.outputSvg)):
            raise Exception("%s wasn't created! arg list used: %s"%(self.svgPath,args))        
            
        from Ft.Xml.Domlette import NonvalidatingReader
        from Ft.Xml import XPath            
        from Ft.Xml.Lib.Print import PrettyPrint
        from cStringIO import StringIO
        stream = StringIO()
        doc = NonvalidatingReader.parseUri('file://'+self.outputSvg)
        xContext=XPath.Context.Context(doc,processorNss={'svg':'http://www.w3.org/2000/svg'})
        w=float(split(XPath.Compile('/svg:svg/@width').evaluate(xContext)[0].nodeValue,'p')[0])
        h=float(split(XPath.Compile('/svg:svg/@height').evaluate(xContext)[0].nodeValue,'p')[0])
        scaleFactor = min(1,2048 / w)
        
        self.svgHeight = h * scaleFactor
        self.svgWidth  = w * scaleFactor
        
        g = XPath.Compile('/svg:svg/svg:g').evaluate(xContext)[0]
        svgRoot = XPath.Compile('/svg:svg').evaluate(xContext)[0]

        #svgRoot.setAttributeNS(None,'width','100%')
        #svgRoot.setAttributeNS(None,'height','100%')
        svgRoot.removeAttributeNS(None,'viewBox')
        
        g.setAttributeNS(None,'transform','scale(%s)'%(scaleFactor))
        PrettyPrint(doc,stream)
        svgfile = codecs.open(self.outputSvg, 'w', 'utf-8')
        svgfile.write(stream.getvalue())
        svgfile.close()

        mapfile = codecs.open(outputMap, 'r', 'utf-8')
        mapfile_content = mapfile.read()
        mapfile.close()
        matches = RECTANGLE_PATTERN.findall(mapfile_content)

        rt = '<MAP name="%s">\n'%(self.mapName)
        for match in matches:
            splitString = match.split(' ')
            corner1x, corner1y = splitString[1][1:-1].split(',')
            corner2x, corner2y = splitString[2][1:-1].split(',')
            resourceUri = splitString[3]
            rt = rt+'<AREA href="%s" shape="rect" coords="%s,%s,%s,%s"/>\n'%(resourceUri, corner1x, corner2y, corner2x, corner1y)
        rt = rt + '</MAP>'         
        return rt
        
def splitPredicate(predicate, nsDict):      
    reverseDict={}
    for key, value in nsDict.items():
        reverseDict[value]=key
    
    splitPred=predicate.split('#')                    
                        
    uri=''
    local=predicate
    if len(splitPred)>1:        
        local = splitPred[-1]
        if len(splitPred)>2:
            uri = '#'.join(splitPred[0:-2])
        else:
            uri = splitPred[0]
        
        uri = uri + '#'
    elif len(predicate.split('/'))>1:
        if predicate[-1] =='/':
            local=predicate
            prefix=''
        else:
            local = predicate.split('/')[-1]
            uri = '/'.join(predicate.split('/')[0:-2]) + '/'
    prefix = ''    
    
    if reverseDict.has_key(uri):
        prefix = reverseDict[uri] + ':'
        
    return prefix + local
        
def ResourceName(model, resourceUri,nsDict):
    #Should also account for rdf:type as well as rdfs:label
    label_stmts = model.complete(resourceUri, RDF_SCHEMA_BASE+"label", None)
    type_stmts = model.complete(resourceUri, RDF_MS_BASE+"type", None)
    if label_stmts:
        return  "<%s>"%label_stmts[0].object        
    elif type_stmts:
        type_str = splitPredicate(type_stmts[0].object,nsDict)
        rdfClass=CheckLabel(model,type_str)
        if rdfClass == type_str:
            rdfClass = splitPredicate(type_str,nsDict).replace('\n','\\n')
        label = splitPredicate(resourceUri,nsDict).replace('\n','\\n')
        return  "%s\\n{%s}"%(label,rdfClass)
    else: return splitPredicate(resourceUri,nsDict).replace('\n','\\n')


if __name__ == '__main__':
    
    from Ft.Rdf import Model
    from Ft.Rdf.Drivers import Memory
    from Ft.Rdf.Serializers.Dom import Serializer
    from Ft.Xml.Domlette import NonvalidatingReader

    if len(sys.argv)<5:
        print "usage: RDFVisualizer.py graphVizBinDir rdfModel output\n"
        print "graphVizBinDir - the 'bin' directory of graphvis"
        print "rdfModel - the file containing the RDF serialization to graph"
        print "output - the name of the file to write the jpeg image to"
        sys.exit(0)

    tmpDir = sys.argv[1]
    gvisBin = sys.argv[2]
    modelFile = sys.argv[3]
    output = sys.argv[4]
    
    print "%s -> %s"%(modelFile, output)
    
    doc = NonvalidatingReader.parseUri(urllib.pathname2url(modelFile))

    d = Memory.DbAdapter('')
    d.begin()    
    model = Model.Model(d)
    szr = Serializer()
    szr.deserialize(model, doc, 'versaExample')    
    gvizAgent=RDFGraphVizEngine(gvisDir=gvisBin, outputJpeg=output)
    gvizAgent.resourceViewer(model, scoped=1)

