from rdflib import RDF

from rdflib import URIRef, Literal, BNode
from rdflib.util import first, uniq, more_than
from rdflib.Collection import Collection
from rdflib.syntax.serializers import Serializer
from rdflib.syntax.serializers.XMLWriter import XMLWriter

XMLLANG = "http://www.w3.org/XML/1998/namespacelang"


# TODO:
def fix(val):
    "strip off _: from nodeIDs... as they are not valid NCNames"
    if val.startswith("_:"):
        return val[2:]
    else:
        return val


class PrettyXMLSerializer(Serializer):

    def __init__(self, store, max_depth=3):
        super(PrettyXMLSerializer, self).__init__(store)
        self.forceRDFAbout=set()

    def serialize(self, stream, base=None, encoding=None, **args):
        self.__serialized = {}
        store = self.store
        self.base = base
        self.max_depth = args.get("max_depth", 3)
        assert self.max_depth>0, "max_depth must be greater than 0"

        self.nm = nm = store.namespace_manager
        self.writer = writer = XMLWriter(stream, nm, encoding)

        namespaces = {}
        possible = uniq(store.predicates()) + uniq(store.objects(None, RDF.type))
        for predicate in possible:
            prefix, namespace, local = nm.compute_qname(predicate)
            namespaces[prefix] = namespace
        namespaces["rdf"] = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
        writer.push(RDF.RDF)
        writer.namespaces(namespaces.iteritems())

        # Write out subjects that can not be inline
        for subject in store.subjects():
            if (None, None, subject) in store:
                if (subject, None, subject) in store:
                    self.subject(subject, 1)
            else:
                self.subject(subject, 1)

        # write out anything that has not yet been reached
        for subject in store.subjects():
            self.subject(subject, 1)

        writer.pop(RDF.RDF)

        # Set to None so that the memory can get garbage collected.
        self.__serialized = None


    def subject(self, subject, depth=1):
        store = self.store
        writer = self.writer
        if not subject in self.__serialized:
            self.__serialized[subject] = 1
            type = first(store.objects(subject, RDF.type))
            try:
                self.nm.qname(type)
            except:
                type = None
            element = type or RDF.Description
            writer.push(element)
            if isinstance(subject, BNode):
                def subj_as_obj_more_than(ceil):
                    return more_than(store.triples((None, None, subject)), ceil)
                if (depth == 1 and subj_as_obj_more_than(0)
                        ) or subj_as_obj_more_than(1):
                    writer.attribute(RDF.nodeID, fix(subject))
            else:
                writer.attribute(RDF.about, self.relativize(subject))
            if (subject, None, None) in store:
                for predicate, object in store.predicate_objects(subject):
                    if not (predicate==RDF.type and object==type):
                        self.predicate(predicate, object, depth+1)
            writer.pop(element)
        elif subject in self.forceRDFAbout:
            writer.push(RDF.Description)
            writer.attribute(RDF.about, self.relativize(subject))
            writer.pop(RDF.Description)
            self.forceRDFAbout.remove(subject)

    def predicate(self, predicate, object, depth=1):
        writer = self.writer
        store = self.store
        writer.push(predicate)
        if isinstance(object, Literal):
            attributes = ""
            if object.language:
                writer.attribute(XMLLANG, object.language)
            if object.datatype:
                writer.attribute(RDF.datatype, object.datatype)
            writer.text(object)
        elif object in self.__serialized or not (object, None, None) in store:
            if isinstance(object, BNode):
                if more_than(store.triples((None, None, object)), 0):
                    writer.attribute(RDF.nodeID, fix(object))
            else:
                writer.attribute(RDF.resource, self.relativize(object))
        else:
            items = []
            for item in store.items(object): # add a strict option to items?
                if isinstance(item, Literal):
                    items = None # can not serialize list with literal values in them with rdf/xml
                else:
                    items.append(item)

            if first(store.objects(object, RDF.first)): # may not have type RDF.List
                collection = object
                self.__serialized[object] = 1
                # TODO: warn that any assertions on object other than
                # RDF.first and RDF.rest are ignored... including RDF.List
                writer.attribute(RDF.parseType, "Collection")
                col=Collection(store,object)
                for item in col:
                    self.forceRDFAbout.add(item)
                    self.subject(item)
                    self.__serialized[item] = 1
            else:
                if depth<=self.max_depth:
                    self.subject(object, depth+1)
                elif isinstance(object, BNode):
                    writer.attribute(RDF.nodeID, fix(object))
                else:
                    writer.attribute(RDF.resource, self.relativize(object))
        writer.pop(predicate)

