########################################################################
#
# File Name:            Model.py
#
# Documentation:        http://docs.4suite.org/4Rdf/Model.py.html
#
"""
The model container for RDF meta-data: represents a directed graph
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
"""

from Ft.Rdf import Resource
from Ft.Rdf import Statement
from Ft.Lib import Uuid
from Ft.Rdf import Container
from Ft.Rdf import RDF_MS_BASE, RDF_SCHEMA_BASE, RdfException
import types

_SequenceTypes = [types.ListType, types.TupleType]

# Bit flags for pattern matches
NORMAL = 0x00
IGNORE_CASE = 0x01
REGEX = 0x02

try:
    import codecs, types
    STRING_TYPES = [types.StringType, types.UnicodeType]
    def CharCode(text):
        if type(text) is not types.UnicodeType:
            return unicode(text, "utf-8")
        else:
            return text
except ImportError:
    STRING_TYPES = [types.StringType]
    CharCode = lambda x: x


class Model(Resource.Resource):

    def __init__(self, driver, schemaHandler=None):
        Resource.Resource.__init__(self, '')
        self._driver = driver
        self._schemaHandler = schemaHandler
        if schemaHandler:
            schemaHandler.initializeModel(self)
        return

    def addContainer(self, container, sourceUri=None):
        # FIXME - update to new interface 3/30/01
        container = self._mapContainer(container)
        uri = container.uri
        if not uri:
            #We need to generate one
            uri = self.generateUri()
            
        ctype = container.className
        stmts = [(uri, RDF_MS_BASE + 'type', RDF_MS_BASE+ctype, uri, 
                  sourceUri or '')]
        idx = 1
        for item in container:
            stmts.append((uri, RDF_MS_BASE + '_%d' % idx, item, "", uri))
            idx = idx + 1

        self._driver.add(stmts)

        return uri

    def extractContainer(self, uri, sourceUri=None):
        #Get type of the bag
        rt = self.complete(uri, RDF_MS_BASE+'type', None)

        if not len(rt):
            return None

        oCtype = rt[0].object
        ctype = rt[0].object[len(RDF_MS_BASE):]

        #Get all of the statements about the bag
        contents = self.complete(uri, RDF_MS_BASE+'_.*', None, sourceUri=uri,
                                 predicateFlags=REGEX)

        #Make sure there is only on copy of each _
        newContents = []
        found = []
        for c in contents:
            if c.predicate not in found:
                newContents.append(c)
                found.append(c.predicate)
        contents = newContents

        if ctype == 'Seq':
            # Remove the leading underscore also
            strip_len = len(RDF_MS_BASE) + 1
            contents.sort(lambda left, right, strip=strip_len:
                          cmp(int(left.predicate[strip:]),
                              int(right.predicate[strip:]))
                          )

        contents = map(lambda x:x.object,contents)

        cont = None
        if ctype == 'Bag':
            cont = Container.Bag(uri, contents)
        elif ctype == 'Seq':
            cont = Container.Sequence(uri, contents)
        elif ctype == 'Alt':
            cont = Container.Alternative(uri, contents)
        else:
            raise RdfException(RdfException.INVALID_CONTAINER_TYPE,(oCtype,))
        return self._unmapContainer(cont)

    def add(self, statements, checkSchema=1):
        if type(statements) not in _SequenceTypes:
            statements = (statements,)
        if self._schemaHandler and checkSchema:
            for stmt in statements:
                self._schemaHandler.processNewStatement(self, stmt)
        self._driver.add(self._unmapStatements(statements))

    def remove(self, statements):
        if type(statements) not in _SequenceTypes:
            statements = (statements,)
        self._driver.remove(self._unmapStatements(statements))
        return

    def removePattern(self, subject, predicate, object,
                      statementUri=None, sourceUri=None, **flags):
        """
        See complete() for a description of the flags keyword argument.
        """
        self._validateFlags(flags)
        self._driver.removePattern(subject, predicate, object,
                                   statementUri, sourceUri, flags)
        return

    def contains(self, statement):
        s = self._unmapStatements([statement])[0]
        return self._driver.contains(s[0], s[1], s[2], s[3], s[4], {})

    def containsPattern(self, subject, predicate, object,
                        statementUri=None, sourceUri=None, **flags):
        """
        See complete() for a description of the flags keyword argument.
        """
        self._validateFlags(flags)
        return self._driver.contains(subject, predicate, object,
                                     statementUri, sourceUri, flags)

    def statements(self, sourceUri=None):
        return self.complete(None, None, None, None, sourceUri)

    def size(self, sourceUri=None):
        return self._driver.size(sourceUri)

    def complete(self, subject, predicate, object,
                 statementUri=None, sourceUri=None, **flags):
        """
        Any combination of s, p and o can be None, and any None slot is
        treated as a wildcard that matches any value in the model.  A list
        is returned comprising all the statement objects that match the
        arguments.

        flags keyword arguments map to the listed arguments with 'Flags'
        append to the name.  Where the value is any combination of
        IGNORE_CASE and REGEX.
        """
        self._validateFlags(flags)
        statements = self._driver.complete(subject, predicate, object,
                                           statementUri, sourceUri, flags)
        return self._mapStatements(statements)

    def exclude(self, subject, predicate, object,
                statementUri=None, sourceUri=None, **flags):
        """
        See complete() for a description of the flags keyword argument.
        """
        self._validateFlags(flags)
        statements = self._driver.exclude(subject, predicate, object,
                                          statementUri, sourceUri, flags)
        return self._mapStatements(statements)

    def generateUri(self):
        return 'urn:uuid:'+Uuid.UuidAsString(Uuid.GenerateUuid())

    def bind(self, object, name, sourceUri=None):
        self._driver.bind(object, name, sourceUri)

    def unbind(self, name, sourceUri=None):
        self._driver.unbind(name, sourceUri)

    def lookup(self, name, sourceUri=None):
        return self._driver.lookup(name, sourceUri)

    def has_key(self, name, sourceUri=None):
        return self._driver.has_key(name, sourceUri)

    def keys(self, sourceUri=None):
        return self._driver.keys(sourceUri)

    def compileRil(self, expression):
        from Ft.Rdf.Parsers.Ril import CompiledRilExpression
        return CompiledRilExpression.CompiledRilExpression(expression)

    def _dump(self, sourceUri=None):
        for statement in self.statements():
            print statement

    def _mapStatements(self, statements):
        return map(lambda x:
                   Statement.Statement(x[0], x[1], x[2], x[3], x[4]),
                   statements)

    def _unmapStatements(self, statements):
        return map(lambda x:
                   (CharCode(x.subject), CharCode(x.predicate),
                    CharCode(x.object), CharCode(x.uri),
                    CharCode(x.sourceUri)),
                   statements)

    def _mapContainer(self, container):
        return container

    def _unmapContainer(self, container):
        return container


    def _validateFlags(self,flags):
        allowedFlags = ['subjectFlags',
                        'predicateFlags',
                        'objectFlags',
                        'sourceUriFlags',
                        'statementUriFlags']

        for k in flags.keys():
            if k not in allowedFlags:
                raise RdfException(RdfException.INVALID_FLAG,(k,))

