#
# Copyright John Reid 2009
#



"""
Code to handle intervals.
"""



class Interval(object):
    """
    Represents an interval.
    Defined as half-open interval [start,end), which includes the start position but not the end.
    Start and end do not have to be numeric types.
    """


    def __init__(self, start, end):
        "Construct, start must be <= end."
        if start > end:
            raise ValueError('Start (%s) must not be greater than end (%s)' % (start, end))
        self._start = start
        self._end = end


    start = property(fget=lambda self: self._start, doc="The interval's start")
    end = property(fget=lambda self: self._end, doc="The interval's end")


    def __str__(self):
        "As string."
        return '[%s,%s)' % (self.start, self.end)


    def __repr__(self):
        "String representation."
        return '[%s,%s)' % (self.start, self.end)


    def __cmp__(self, other):
        "Compare."
        if None == other:
            return 1
        start_cmp = cmp(self.start, other.start)
        if 0 != start_cmp:
            return start_cmp
        else:
            return cmp(self.end, other.end)


    def __hash__(self):
        "Hash."
        return hash(self.start) ^ hash(self.end)


    def intersection(self, other):
        "Intersection. @return: An empty intersection if there is none."
        if self > other:
            other, self = self, other
        if self.end <= other.start:
            return Interval(self.start, self.start)
        return Interval(other.start, self.end)


    def hull(self, other):
        "@return: Interval containing both self and other."
        if self > other:
            other, self = self, other
        return Interval(self.start, other.end)


    def overlap(self, other):
        "@return: True iff self intersects other."
        if self > other:
            other, self = self, other
        return self.end > other.start


    def __contains__(self, item):
        "@return: True iff item in self."
        return self.start <= item and item < self.end


    def zero_in(self):
        "@return: True iff 0 in self."
        return self.start <= 0 and 0 < self.end


    def subset(self, other):
        "@return: True iff self is subset of other."
        return self.start >= other.start and self.end <= other.end


    def proper_subset(self, other):
        "@return: True iff self is proper subset of other."
        return self.start > other.start and self.end < other.end


    def empty(self):
        "@return: True iff self is empty."
        return self.start == self.end


    def singleton(self):
        "@return: True iff self.end - self.start == 1."
        return self.end - self.start == 1


    def separation(self, other):
        "@return: The distance between self and other."
        if self.overlap(other):
            return 0
        if self > other:
            return self.start - other.end
        else:
            return self.end - other.start


    def as_slice(self):
        "@return: A slice object that the interval indexes."
        return slice(self.start, self.end)
