# User-visible side of the spigot Python package. Uses Python operator
# overloading to build a wrapper around the C++ spigot class that
# makes it look more or less like a Python numeric type.

import spigot.internal
import struct
import types

def mkintspig(x):
    ret = spigot.internal.Spigot()
    if isinstance(x, Spigot):
        ret.clone(x)
    elif type(x) == types.FloatType:
        bitpat = struct.unpack(">Q", struct.pack(">d",1))[0]
        if 0x7ff & ~(bitpat >> 52) == 0:
            raise ValueError("cannot construct spigot from infinity or NaN")
        ret.parse("ieee:%x" % bitpat)
    elif type(x) == types.IntType or type(x) == types.LongType:
        ret.parse("%d" % x)
    else:
        raise TypeError("cannot convert %s to spigot" % repr(type(x)))
    return ret

class Spigot(object):
    def __init__(self, x, scope=None):
        if scope != None or type(x) == types.StringType:
            self.sp = spigot.internal.Spigot()
            self.sp.parse(x, scope)
        else:
            self.sp = mkintspig(x)

    def __add__(self, s2):
        if not isinstance(s2, Spigot):
            s2 =  Spigot(s2)
        return Spigot("x+y", {"x":self.sp, "y":s2.sp})

    def __radd__(self, s2):
        if not isinstance(s2, Spigot):
            s2 =  Spigot(s2)
        return Spigot("y+x", {"x":self.sp, "y":s2.sp})

    def __sub__(self, s2):
        if not isinstance(s2, Spigot):
            s2 =  Spigot(s2)
        return Spigot("x-y", {"x":self.sp, "y":s2.sp})

    def __rsub__(self, s2):
        if not isinstance(s2, Spigot):
            s2 =  Spigot(s2)
        return Spigot("y-x", {"x":self.sp, "y":s2.sp})

    def __mul__(self, s2):
        if not isinstance(s2, Spigot):
            s2 =  Spigot(s2)
        return Spigot("x*y", {"x":self.sp, "y":s2.sp})

    def __rmul__(self, s2):
        if not isinstance(s2, Spigot):
            s2 =  Spigot(s2)
        return Spigot("y*x", {"x":self.sp, "y":s2.sp})

    def __div__(self, s2):
        if not isinstance(s2, Spigot):
            s2 =  Spigot(s2)
        return Spigot("x/y", {"x":self.sp, "y":s2.sp})

    def __rdiv__(self, s2):
        if not isinstance(s2, Spigot):
            s2 =  Spigot(s2)
        return Spigot("y/x", {"x":self.sp, "y":s2.sp})

    def __pow__(self, s2):
        if not isinstance(s2, Spigot):
            s2 =  Spigot(s2)
        return Spigot("x^y", {"x":self.sp, "y":s2.sp})

    def __rpow__(self, s2):
        if not isinstance(s2, Spigot):
            s2 =  Spigot(s2)
        return Spigot("y^x", {"x":self.sp, "y":s2.sp})

    def __neg__(self):
        return Spigot("-x", {"x":self.sp})

    def __pos__(self):
        return self

    def __abs__(self):
        return Spigot("abs(x)", {"x":self.sp})

    def __long__(self):
        sp = spigot.internal.Spigot()
        sp.clone(self.sp)
        intpart = sp.cfracterm()
        if intpart < 0:
            next = sp.cfracterm()
            if next != None:
                intpart = intpart + 1
        return intpart

    def __int__(self):
        return int(self.__long__())

    def __float__(self):
        sp = spigot.internal.Spigot()
        sp.clone(self.sp)
        sp.ieee(64, 0, 2) # FIXME: symbolic rounding mode constant
        out = ""
        while 1:
            s = sp.readfmt()
            if s == "": break
            out = out + s
        return struct.unpack(">d", struct.pack(">Q", long(out, base=16)))[0]

    def __cmp__(self, s2): 
        if not isinstance(s2, Spigot):
            s2 =  Spigot(s2)
        sp = spigot.internal.Spigot()
        sp.parse("x-y", {"x":self.sp, "y":s2.sp})
        return sp.sign()

    def __nonzero__(self):
        sp = spigot.internal.Spigot()
        sp.clone(self.sp)
        return sp.sign() != 0

    def continued_fraction(self):
        "Generator which returns a number's continued fraction term by term."
        sp = spigot.internal.Spigot()
        sp.clone(self.sp)
        while True:
            term = sp.cfracterm()
            if term is None:
                return
            yield term

    def convergents(self):
        """Generator which returns a number's continued fraction convergents
as successive (numerator, denominator) 2-tuples."""
        n0, n1, d0, d1 = 0, 1, 1, 0
        for term in self.continued_fraction():
            n0, n1 = n1, term*n1+n0
            d0, d1 = d1, term*d1+d0
            yield (n1, d1)
