"""
$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/dulcinea/lib/range_value.py $
$Id: range_value.py 31353 2008-11-25 13:44:05Z dbinger $

Provides the RangeValue class.
"""
from dulcinea.base import Copyable
from dulcinea.numeric import near_cmp
from qp.lib.spec import either, Specified, number_classes

class RangeValue (Specified, Copyable):
    """
    A numeric range.
    """
    lo_is = either(*number_classes)
    hi_is = either(*number_classes)

    def __init__(self, lo=0, hi=0):
        self.lo = lo
        self.hi = hi
        if type(self.lo) is not type(self.hi):
            raise TypeError("inconsistent types: %s and %s" %
                (self.lo, self.hi))
        if type(self.lo) not in number_classes:
            raise TypeError("range endpoint must be a number")
        if self.lo > self.hi:
            raise ValueError("backwards range: %s > %s" % (self.lo, self.hi))

    def __str__ (self):
        return "%s .. %s" % (self.lo, self.hi)

    def __hash__(self):
        return hash((self.lo, self.hi))

    def get_min(self):
        return self.lo

    def get_max(self):
        return self.hi

    def get_tuple(self):
        return (self.lo, self.hi)

    def get_type(self):
        return type(self.lo)

    def format(self):
        if self.lo == self.hi:
            return "%g" % self.lo
        else:
            return "%g .. %g" % (self.lo, self.hi)


    # A lot of arithmetic operations aren't supported since it's not clear what
    # they should mean.  Arithmetic operations involving two range values are
    # not supported at all.

    def __add__(self, other):
        if isinstance(other, number_classes):
            return self.__class__(self.lo + other, self.hi + other)
        else:
            return NotImplemented

    __radd__ = __add__

    def __sub__(self, other):
        if isinstance(other, number_classes):
            return self.__class__(self.lo - other, self.hi - other)
        else:
            return NotImplemented

    def __rsub__(self, other):
        if isinstance(other, number_classes):
            return self.__class__(other - self.hi, other - self.lo)
        else:
            return NotImplemented

    def __mul__(self, other):
        if isinstance(other, number_classes):
            return self.__class__(self.lo * other, self.hi * other)
        else:
            return NotImplemented

    __rmul__ = __mul__

    def __div__(self, other):
        if isinstance(other, number_classes):
            return self.__class__(self.lo / other, self.hi / other)
        else:
            return NotImplemented
    __truediv__ = __div__

    def __rdiv__(self, other):
        return NotImplemented
    __rtruediv__ = __div__

    def to_float(self):
        return self.__class__(float(self.lo),
                              float(self.hi))

    def __eq__(self, other):
        if isinstance(other, RangeValue):
            if isinstance(self.lo, float):
                return (near_cmp(self.lo, other.lo) == 0 and
                        near_cmp(self.hi, other.hi) == 0)
            else:
                return self.lo == other.lo and self.hi == other.hi
        else:
            return False

    def __ne__(self, other):
        return not self == other

    def __lt__(self, other):
        """
        Comparison is not well-defined for intervals, but this is
        provided anyway so that sorting does not fail.
        """
        if isinstance(other, RangeValue):
            return self.get_min() < other.get_min()
        else:
            return True # arbitrary
            
    def __le__(self, other):
        return self == other or self < other
            
    def __gt__(self, other):
        """
        Comparison is not well-defined for intervals, but this is
        provided anyway so that sorting does not fail.
        """
        if isinstance(other, RangeValue):
            return self.get_min() > other.get_min()
        else:
            return True # arbitrary

    def __ge__(self, other):
        return self == other or self > other               

    def in_range(self, other):
        """Return true if 'other' is inside the range, ie. >= the low end
        and <= the high end.
        """
        if isinstance (other, RangeValue):
            return (near_cmp(self.lo, other.lo) <= 0 and
                    near_cmp(self.hi, other.hi) >= 0)
        elif isinstance(other, number_classes):
            return (near_cmp(self.lo, other) <= 0 and
                    near_cmp(self.hi, other) >= 0)
        else:
            return False






