INFINITY = float('inf')
NEGATIVE_INFINITY = -INFINITY


class IntervalSet:

    def __init__(self, intervals, disjoint=False):
        self.intervals = intervals

        if not disjoint:
            self.intervals = union_overlapping(self.intervals)

        self.size = sum(i.size for i in self.intervals)

    def __repr__(self):
        return repr(self.intervals)

    def __iter__(self):
        return iter(self.intervals)

    def __len__(self):
        return len(self.intervals)

    def __getitem__(self, i):
        return self.intervals[i]

    def __nonzero__(self):
        return self.size != 0

    def __sub__(self, other):
        return self.intersect( other.complement() )

    def complement(self):
        complementary = []
        cursor = NEGATIVE_INFINITY

        for interval in self.intervals:
            if cursor < interval.start:
                complementary.append( Interval(cursor, interval.start) )
                cursor = interval.end

        if cursor < INFINITY:
            complementary.append( Interval(cursor, INFINITY) )

        return IntervalSet(complementary, disjoint=True)

    def intersect(self, other): #XXX The last major bottleneck. Factorial-time hell.
        # Then again, this function is entirely unused...
        if (not self) or (not other):
            return IntervalSet([])

        #earliest = max(self.intervals[0].start, other.intervals[0].start)
        #latest = min(self.intervals[-1].end, other.intervals[-1].end)

        #mine = [i for i in self.intervals if i.start >= earliest and i.end <= latest]
        #theirs = [i for i in other.intervals if i.start >= earliest and i.end <= latest]

        intersections = [x for x in (i.intersect(j)
                                     for i in self.intervals
                                     for j in other.intervals)
                         if x]

        return IntervalSet(intersections, disjoint=True)

    def intersect_interval(self, interval):
        intersections = [x for x in (i.intersect(interval)
                                     for i in self.intervals)
                         if x]
        return IntervalSet(intersections, disjoint=True)

    def union(self, other):
        return IntervalSet( sorted(self.intervals + other.intervals) )


class Interval:

    def __init__(self, start, end):
        if end - start < 0:
            raise ValueError("Invalid interval start=%s end=%s" % (start, end))

        self.start = start
        self.end = end
        self.tuple = (start, end)
        self.size = self.end - self.start

    def __eq__(self, other):
        return self.tuple == other.tuple

    def __ne__(self, other):
        return self.tuple != other.tuple

    def __hash__(self):
        return hash( self.tuple )

    def __lt__(self, other):
        return self.start < self.start

    def __le__(self, other):
        return self.start <= self.start

    def __gt__(self, other):
        return self.start > self.start

    def __ge__(self, other):
        return self.start >= self.start

    def __cmp__(self, other):
        return (self.start > other.start) - (self.start < other.start)

    def __len__(self):
        raise TypeError("len() doesn't support infinite values, use the 'size' attribute instead")

    def __nonzero__(self):  # Python 2
        return self.size != 0

    def __bool__(self):  # Python 3
        return self.size != 0

    def __repr__(self):
        return '<Interval: %s>' % str(self.tuple)

    def intersect(self, other):
        start = max(self.start, other.start)
        end = min(self.end, other.end)

        if end > start:
            return Interval(start, end)

    def overlaps(self, other):
        earlier = self if self.start <= other.start else other
        later = self if earlier is other else other
        return earlier.end >= later.start

    def union(self, other):
        if not self.overlaps(other):
            raise TypeError("Union of disjoint intervals is not an interval")

        start = min(self.start, other.start)
        end = max(self.end, other.end)
        return Interval(start, end)


def union_overlapping(intervals):
    """Union any overlapping intervals in the given set."""
    disjoint_intervals = []

    for interval in intervals:
        if disjoint_intervals and disjoint_intervals[-1].overlaps(interval):
            disjoint_intervals[-1] = disjoint_intervals[-1].union(interval)
        else:
            disjoint_intervals.append(interval)

    return disjoint_intervals
