########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Rdf/Statement.py,v 1.20 2005/03/05 06:15:52 mbrown Exp $
"""
An RDF statement

Copyright 2005 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""

from Ft.Lib.Uri import MatchesUriSyntax
from Ft.Rdf import RDF_MS_BASE, OBJECT_TYPE_LITERAL, OBJECT_TYPE_RESOURCE, OBJECT_TYPE_UNKNOWN
from Ft.Rdf import RdfException
from Ft.Rdf.Resource import Resource


# See this article for ideas on more efficient reification:
# http://lists.w3.org/Archives/Public/www-rdf-interest/2000Nov/0450.html

class Statement(Resource):
    """Represents an RDF triple or statement"""

    def __init__(self, subject, predicate, object_, statementUri='',
                 scope='', objectType=OBJECT_TYPE_UNKNOWN):
        """
        Initializer for an RDF statement instance.
        statementUri is the URI of this statement.
        scope is usually the URI of the serialized source of this statement.
        """
        Resource.__init__(self, statementUri)
        if not isinstance(subject, (str, unicode)):
            raise TypeError("Subject not of type string: %s" % str(subject))
        if not isinstance(predicate, (str, unicode)):
            raise TypeError("Predicate not of type string: %s" % str(predicate))
        if not isinstance(object_, (str, unicode)):
            raise TypeError("Object not of type string: %s" % str(object_))
        if statementUri is not None and not isinstance(statementUri, (str, unicode)):
            raise TypeError("Statement URI not of type string: %s" % str(statementUri))
        if scope is not None and not isinstance(scope, (str, unicode)):
            raise TypeError("Scope not of type string: %s" % str(scope))

        self.subject = subject
        self.predicate = predicate
        self.object = object_
        self.scope = scope
        self.objectType = objectType
        self.uri = statementUri is None and '' or statementUri
        return

    def __cmp__(self, other):
        if not isinstance(other, Statement):
            raise TypeError("Object being compared must be a Statement, not a %s" % type(other))
        # per Uche on 4suite list, 2005-02-28:
        # By default, don't compare scope or statementUri.
        # Compare objectType if it is known.
        if self.objectType != OBJECT_TYPE_UNKNOWN and other.objectType != OBJECT_TYPE_UNKNOWN:
            return cmp((self.subject, self.predicate, self.object, self.objectType),
                       (other.subject, other.predicate, other.object, other.objectType))
        else:
            return cmp((self.subject, self.predicate, self.object),
                       (other.subject, other.predicate, other.object))


    # always define when defining __cmp__ or obj will be unhashable
    def __hash__(self):
        return id(self)

    def reify(self, model, uri=None):
        """
        Prepare the statement in the model so that it can be the subject of
        other statements.  See the RDF spec for details of how statements are expanded for
        reification.  Note that the original statement is not removed by reification.
        The statement's uri is used as the subject of the added statements, or a URI
        is generated if the statement's uri is an empty string.
        """
        if uri is None:
            if self.uri:
                uri = self.uri
            else:
                uri = model.generateUri()
                self.uri = uri
        model.add(Statement(uri, RDF_MS_BASE+'type', RDF_MS_BASE+'Statement',
                            scope=uri))
        model.add(Statement(uri, RDF_MS_BASE+'subject', self.subject,
                            scope=uri))
        model.add(Statement(uri, RDF_MS_BASE+'predicate', self.predicate,
                            scope=uri))
        model.add(Statement(uri, RDF_MS_BASE+'object', self.object,
                            scope=uri))
        return uri


    def asTuple(self, encoding=None):
        """
        Returns the statement data as a tuple in this order:
        (subject, object, predicate, scope, statementUri, objectType)

        If an encoding argument is given, all values are coerced to that encoding.
        Otherwise, they are left as-is. If the encoding is the special value
        'unicode', then the values are coerced to unicode types.
        """
        if encoding is None:
            return (self.subject, self.predicate, self.object, self.uri, self.scope,
                    str(self.objectType))
        elif encoding == 'unicode':
            return tuple([unicode(s) for s in self.asTuple(encoding=None)])
        else:
            return tuple([s.encode(encoding) for s in self.asTuple(encoding=None)])


    def __repr__(self):
        if self.scope:
            return '<RDF Statement at %s: [%s, Scope: %s]>' % (id(self), str(self), self.scope)
        else:
            return '<RDF Statement at %s: [%s]>' % (id(self), str(self))


    def __str__(self):
        if self.objectType == OBJECT_TYPE_LITERAL:
            obj = repr(self.object)
        elif self.objectType == OBJECT_TYPE_RESOURCE:
            obj = self.object
        elif MatchesUriSyntax(self.object):
            obj = self.object
        else:
            obj = repr(self.object)
        return 'Subject: %s, Predicate: %s, Object: %s' % (
            self.subject, self.predicate, obj)
