import java.util.StringTokenizer;

/**
 * Closed or open or half-closed intervals of real numbers.
 *
 * @author Jim Glenn
 * @version 0.1 1/24/2003
 */

public class Interval
{
    /**
     * Constructs the interval [0.0, 0.0].
     */

    public Interval()
    {
	start = 0.0;
	end = 0.0;
	startClosed = true;
	endClosed = true;
    }

    /**
     * Constructs a closed interval with the given endpoints.
     *
     * @param a the left (low) endpoint
     * @param b the right (high) endpoint
     */

    public Interval(double a, double b)
    {
	start = a;
	end = b;
	startClosed = true;
	endClosed = true;
    }

    /**
     * Constructs a closed interval with the given endpoints.  The
     * <CODE>boolean</CODE> parameters determine if the endpoints
     * are included or not.
     *
     * @param a the left (low) endpoint
     * @param b the right (high) endpoint
     * @param includeA true iff the intverval is closed on the left
     * @param includeB true iff the interval is closed on the right
     */

    public Interval(double a, double b, boolean includesA, boolean includesB)
    {
	start = a;
	end = b;
	startClosed = includesA;
	endClosed = includesB;
	if (start == end)
	    endClosed = startClosed;
    }

    /**
     * Determines if this interval is empty.
     *
     * @return true iff this interval is empty
     */

    public boolean isEmpty()
    {
	return (start == end && !startClosed);
    }

    /**
     * Returns the closed version of this interval.
     *
     * @return the closed version of this interval
     */

    public Interval close()
    {
	Interval result = new Interval(start, end);

	return result;
    }

    /**
     * Returns the intersection of this interval and the given interval.
     *
     * @param i the interval to intersect with this one
     * @return the intersection of that interval and this one
     */

    public Interval intersect(Interval i)
    {
	// compute the left endpoint

	double left = Math.max(i.start, start);
	boolean leftClosed = ((left != i.start || i.startClosed)
			      && (left != start || startClosed));

	// compute the right endpoint

	double right = Math.min(i.end, end);
	boolean rightClosed = ((right != i.end || i.endClosed)
			       && (right != end || endClosed));

	// make result

	Interval result;

	if (right < left)
	    result = new Interval(0.0, 0.0, false, false); // empty intersect
	else
	    result = new Interval(left, right, leftClosed, rightClosed);

	return result;
    }

    /**
     * Determines if this interval includes the given point.
     *
     * @param x the point to test for inclusion
     * @return true iff that point is in this interval
     */

    public boolean contains(double x)
    {
	return ((x > start && x < end)
		|| (x == start && startClosed)
		|| (x == end && endClosed));
    }

    /**
     * Determines if this interval is a superset of the given one.
     *
     * @param i the interval to test
     * @return true iff that interval is a subset of this one
     */

    public boolean contains(Interval i)
    {
	return ((i.start > start
		 || (i.start == start && (startClosed || !i.startClosed)))
		&& (i.end < end
		    || (i.end == end && (endClosed || !i.endClosed))));
    }

    /**
     * Determines if this interval overlaps the given interval.
     *
     * @param i the interval to test for overlap with this one
     * @return true iff that interval overlaps this one
     */

    public boolean intersects(Interval i)
    {
	return !intersect(i).isEmpty();
    }

    /**
     * Returns the interval encoded in the given string.  The interval
     * should be in the form "[a,b]" where a and b are legal
     * representations of a <CODE>double</CODE> and '[' and ']' can
     * also be '(' or ')'.
     *
     * @param s the string to parse
     * @return the interval encoded in that string
     */

    public static Interval parseInterval(String s)
    {
	// Determine endpoints based on square brackets or parentheses

	boolean leftClosed = s.charAt(0) == '[';
	boolean rightClosed = s.charAt(s.length() - 1) == ']';
       
	// strip off first and last characters and get doubles from the result

	StringTokenizer tok = new StringTokenizer(s.substring(1, s.length() - 1), ",");
	double left = (new Double(tok.nextToken())).doubleValue();
	double right = (new Double(tok.nextToken())).doubleValue();

	if (left == right)
	    rightClosed = leftClosed;

	Interval result = new Interval(left, right, leftClosed, rightClosed);

	return result;
    }

    /**
     * Returns a printable representation of this interval.
     *
     * @param returns a printable representation of this interval
     */

    public String toString()
    {
	StringBuffer result = new StringBuffer();

	if (startClosed)
	    result.append('[');
	else
	    result.append('(');
	
	result.append(start).append(',').append(end);

	if (endClosed)
	    result.append(']');
	else
	    result.append(')');

	return result.toString();
    }

    /**
     * An endpoint of this interval.
     */

    private double start, end;

    /**
     * A flag that are set if the corresponding endpoint is closed.
     */

    private boolean startClosed, endClosed;
}
