########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Rdf/Parsers/Versa/DataTypes.py,v 1.16 2005/02/26 01:18:34 jkloth Exp $
"""
Data type checking, conversion and ordering for Versa

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

from Ft.Lib import boolean, number
from Ft.Rdf import Model, OBJECT_TYPE_RESOURCE, OBJECT_TYPE_UNKNOWN


class Resource(unicode):
    def __init__(self, s):
        if not isinstance(s, unicode):
            s = unicode(str(s), 'utf-8')
        return unicode.__init__(self, s)


#FIXME: Implement more efficiently
class Set:
    def __init__(self, l=None):
        self._d = {}
        if l is not None:
            for i in l:
                self._d[i] = None
        return
    def __repr__(self): return repr(self._d.keys())
    #FIXME: should be updated to match comparison rules
    def __hash__(self): return id(self)
    def __cmp__(self, other):
        #Only try to compare with another set.  Note, probably would work, but s technically wrong to compare just the key lists
        return cmp(self._d, other._d)
    def __len__(self): return len(self._d.keys())
    #__get/set/delitem__ &  __get/set/delslice__ are shaky ground without 2.2 iterators
    def __add__(self, other):
        #other set only
        newdict = self._d.update(other._d)
        newset = Set()
        newset._d = newdict
        return newset

    __radd__ = __add__
    def append(self, item): self._d[item] = None
    def remove(self, item): del self._d[item]

IsList = lambda x: isinstance(x, list)
IsString = lambda x: isinstance(x, str) or isinstance(x, unicode)
IsNumber = lambda x: isinstance(x, int) or isinstance(x, long) or isinstance(x, float)
IsResource = lambda x: isinstance(x, Resource)
IsSet = lambda x: isinstance(x, Set)
IsBoolean = lambda x: x is boolean.true or x is boolean.false


def ToString(obj):
    """
    Convert to string
    1.  If it is a string, then return
    2.  If it is a number then convert to a string
    3.  If it is a boolean, then convert to true or false
    4.  If a list or set, the coerce the first item.  If empty ''
    """
    #print "ToString", (obj,)
    if IsString(obj): return obj
    if IsNumber(obj): return unicode(str(obj), 'utf-8')
    #FIXME: doesn't match spec
    if IsResource(obj): return unicode(str(obj), 'utf-8')
    if IsList(obj):
        if obj:
            return ToString(obj[0])
        else:
            return u''
    if IsSet(obj): return ToString(ToList(obj))
    if IsBoolean(obj): return obj and u"true" or u"false"
    from Ft.Rdf.Parsers.Versa import RuntimeException
    if obj is None:
        raise RuntimeException(RuntimeException.NO_CONTEXT)
    raise Exception("Internal Error");


def ToNumber(obj):
    """Convert to number"""
    #print "ToNumber", (obj,)
    if IsNumber(obj): return obj
    if IsString(obj):
        try:
            return float(obj)
        except:
            return number.nan
    #FIXME: doesn't match spec
    if IsResource(obj): return number.nan
    #if IsResource(obj): return ToNumber(ToString(obj))
    if IsList(obj):
        if obj:
            return ToNumber(obj[0])
        else:
            return 0.0
    if IsSet(obj): return ToNumber(ToList(obj))
    if IsBoolean(obj): return obj and 1 or 0
    raise Exception("Internal Error");


def ToBoolean(obj):
    """Convert to boolean"""
    #print "ToBoolean", (obj,)
    if IsBoolean(obj): return obj
    if IsNumber(obj): return obj != 0
    if IsList(obj):
        return len(obj) and boolean.true or boolean.false
    if IsList(obj):
        return len(obj._d) and boolean.true or boolean.false
    return IsBoolean(IsNumber(obj))


def ToList(obj):
    """Convert to list"""
    #print "ToList", (obj,)
    if IsList(obj): return obj
    if IsSet(obj): return obj._d.keys()
    if IsString(obj) or IsNumber(obj) or IsBoolean(obj) or IsResource(obj): return [obj]
    raise Exception("Internal Error");


def ToSet(obj):
    """Convert to set"""
    #print "ToSet", (obj,)
    if IsSet(obj): return obj
    return Set(ToList(obj))


def ToResource(obj):
    """Convert to resource"""
    #print "ToResource", (obj,)
    if IsResource(obj): return obj
    return Resource(ToString(obj))


def CmpString(a, b):
    return cmp(ToString(a), ToString(b))


def CmpNumber(a, b):
    return cmp(ToNumber(a), ToNumber(b))


def CmpBoolean(a, b):
    return cmp(ToBoolean(a), ToBoolean(b))


CmpResource = CmpString


def CmpList(a, b):
    return cmp(ToList(a), ToList(b))


def CmpSet(a, b):
    return cmp(ToSet(a), ToSet(b))


def Cmp(a, b):
    if IsNumber(a): return CmpNumber(a, ToNumber(b))
    if IsString(a): return CmpString(a, ToString(b))
    if IsResource(a): return CmpResource(a, ToResource(b))
    if IsList(a): return CmpList(a, ToList(b))
    if IsSet(a): return CmpSet(a, ToSet(b))
    if IsBoolean(a): return CmpBoolean(a, ToBoolean(b))
    raise Exception("Internal Error");


def SyncType(testObject, obj):
    if IsNumber(testObject): return ToNumber(obj)
    if IsString(testObject): return ToString(obj)
    if IsResource(testObject): return ToResource(obj)
    if IsList(testObject): return ToList(obj)
    if IsSet(testObject): return ToSet(obj)
    if IsBoolean(testObject): return ToBoolean(obj)
    raise Exception("Internal Error");



class StringFunction:
    """
    Create a new string
    """
    def __init__(self, args):
        self.arg = args[0]
        return

    def evaluate(self, con):
        return ToString(self.arg.evaluate(con))


class NumberFunction:
    """
    Create a new number
    """
    def __init__(self, args):
        self.arg = args[0]
        return

    def evaluate(self, con):
        return ToNumber(self.arg.evaluate(con))


