/*
 * @(#)Interval.java    2.2  17 September 2002
 *
 * Copyright 2004
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.gui;

import edu.neu.ccs.*;
import edu.neu.ccs.codec.*;
import java.awt.geom.*;
import java.text.ParseException;
import java.util.*;
import java.beans.*;
import java.io.Serializable;
import javax.swing.event.SwingPropertyChangeSupport;

/**
 * <P>Encapsulates an interval of double precision numbers.
 *
 * The endpoints of the interval may be supplied in any order
 * and will be ordered automatically.</P>
 *
 * @author  Richard Rasala
 * @author  Jeff Raab
 * @version 2.2
 * @since   1.0
 */
public class Interval
    implements Cloneable, Serializable, Stringable
{
    /** Bound property name for the minimum property. */
    protected static final String MINIMUM = "minimum";

    /** Bound property name for the maximum property. */
    protected static final String MAXIMUM = "maximum";

    /** Bound property name for the midpoint property. */
    protected static final String MIDPOINT = "midpoint";

    /** Bound property name for the size property. */
    protected static final String SIZE = "SIZE";

    /** Lower endpoint of interval. */
    protected double minimum = 0.0;
    
    /** Upper endpoint of interval. */
    protected double maximum = 0.0;
    
    /** 
     * Interval size, equal to the difference between
     * the maximum and minimum endpoints. 
     */
    protected double size = 0.0;
    
    /** The midpoint of the interval. */
    protected double midpoint = 0.0;
    
    /** Helper object for property change API. */
    protected SwingPropertyChangeSupport changeAdapter =
        new SwingPropertyChangeSupport(this);
    
    //////////////////
    // Constructors //
    //////////////////
    
    /**
     * Constructs an interval with all data set to 0.0.
     *
     * @see #Interval(double, double)
     * @see #Interval(String)
     */
    public Interval() {};
    
    /**
     * Constructs an interval with the given endpoints.
     *
     * @param endpointA one of the two endpoints
     * @param endpointB the other of the two endpoints
     * @see #Interval()
     * @see #Interval(String)
     * @see #setEndpoints(double, double)
     */
    public Interval(double endpointA, double endpointB) {
        setEndpoints(endpointA, endpointB);
    }
    
    /**
     * Constructs an interval from encoded <CODE>String</CODE> data.
     *
     * @param data the encoded String data
     * @see #Interval()
     * @see #Interval(double, double)
     * @see #toStringData()
     * @see #fromStringData(String)
     * @see #toString()
     */
    public Interval(String data) throws ParseException {
        fromStringData(data);
    }
    
    ////////////////
    // Stringable //
    ////////////////
    
    /**
     * Returns a <CODE>String</CODE> representation of the data state
     * for this <CODE>Interval</CODE> object.
     *
     * @see #Interval(String)
     * @see #fromStringData(String)
     * @see #toString()
     */
    public String toStringData() {
        return toString();
    }
    
    /**
     * Extracts the data state for this <CODE>Interval</CODE> object 
     * from a <CODE>String</CODE> representation of the data state.
     * 
     * @param data <CODE>String</CODE> representation of the desired value
     * @throws ParseException if the data is malformed
     * @see #Interval(String)
     * @see #toStringData()
     * @see #toString()
     */
    public void fromStringData(String data) throws ParseException {
        if (data == null)
            throw new ParseException("Input was null.", -1);

        String[] pair = XPoint2D.decodeIntoPair(data);
        
        double lower = XDouble.parseDouble(pair[0]);
        double upper = XDouble.parseDouble(pair[1]);
        
        setEndpoints(lower, upper);
    }
    
    /**
     * Returns a human readable <CODE>String</CODE> representing the
     * data state of this <CODE>Interval</CODE> as: (minimum;maximum).
     *
     * @see #Interval(String)
     * @see #toStringData()
     * @see #fromStringData(String)
     */
    public String toString() {
        return "[" + minimum + ";" + maximum + "]";
    }

    ////////////////
    // Public API //
    ////////////////
    
    /**
     * <P>Sets the endpoints for the interval
     * to the given endpoints.</P>
     *
     * <P>Calling this method may result in the firing
     * of one or more <CODE>PropertyChangeEvent</CODE>s.
     *
     * These events will be fired for property changes
     * in the following order:
     * <CODE>MINIMUM</CODE>, <CODE>MAXIMUM</CODE>, 
     * <CODE>MIDPOINT</CODE>, <CODE>SIZE</CODE>.</P>
     *
     * @param endpointA one of the two endpoints
     * @param endpointB the other of the two endpoints
     * @see #setInterval(Interval)
     */
    public void setEndpoints(double endpointA, double endpointB) {
        double oldMin  = getMinimum(),
               oldMax  = getMaximum(),
               oldMid  = getMidpoint(),
               oldSize = getSize();

        // set minimum and maximum
        if (endpointA <= endpointB) {
            minimum = endpointA;
            maximum = endpointB;
        }
        else {
            minimum = endpointB;
            maximum = endpointA;
        }
        
        // calculate size and midpoint
        size = maximum - minimum;
        midpoint = (minimum + maximum) / 2.0;
        
        // if the minimum has changed
        if (getMinimum() != oldMin) {
        
            // notify listeners of property change
            changeAdapter.firePropertyChange(
                MINIMUM, 
                new Double(oldMin), 
                new Double(getMinimum()));
        }
        
        // if the maximum has changed
        if (getMaximum() != oldMax) {
        
            // notify listeners of property change
            changeAdapter.firePropertyChange(
                MAXIMUM, 
                new Double(oldMax), 
                new Double(getMaximum()));
        }
        
        // if the minimum has changed
        if (getMidpoint() != oldMid) {
        
            // notify listeners of property change
            changeAdapter.firePropertyChange(
                MIDPOINT, 
                new Double(oldMid), 
                new Double(getMidpoint()));
        }
        
        // if the minimum has changed
        if (getSize() != oldSize) {
        
            // notify listeners of property change
            changeAdapter.firePropertyChange(
                SIZE, 
                new Double(oldSize), 
                new Double(getSize()));
        }
    }
    
    /**
     * Sets the endpoints for this interval
     * to the endpoints of the given interval.
     *
     * If the given interval is <CODE>null</CODE>,
     * this method does nothing.
     *
     * @param other the interval to be copied
     * @see #setEndpoints(double, double)
     */
    public void setInterval(Interval other) {
        if (other == null)
            return;
            
        setEndpoints(other.minimum, other.maximum);
    }
    
    /**
     * Returns the lower endpoint of the interval.
     *
     * @see #getMaximum()
     */
    public double getMinimum() { 
        return minimum; 
    }
    
    /**
     * Returns the upper endpoint of the interval.
     *
     * @see #getMinimum()
     */
    public double getMaximum() {
        return maximum; 
    }

    /**
     * Returns the size of the interval, that is,
     * the difference between the upper and lower endpoints.
     */
    public double getSize() {
        return size;
    }
    
    /**
     * Returns the midpoint of the interval.
     */
    public double getMidpoint() { 
        return midpoint; 
    }
    
    /////////////////////////////
    // Property Change Support //
    /////////////////////////////
    
    /**
     * Registers the given object 
     * to listen for property change events 
     * generated by this object.
     *
     * @param listener the listener to be registered
     */
    public void addPropertyChangeListener(
        PropertyChangeListener listener) 
    {
        changeAdapter.addPropertyChangeListener(listener);
    }

    /**
     * Registers the given object 
     * to listen for property change events
     * generated by this object
     * with the given property name.
     *
     * @param propertyName the name of the desired property 
     * @param listener the listener to be registered
     */
    public void addPropertyChangeListener(
        String propertyName,
        PropertyChangeListener listener) 
    {
        changeAdapter.addPropertyChangeListener(
            propertyName, 
            listener);
    }

    /**
     * Deregisters the given object 
     * from listening for property change events 
     * generated by this object.
     *
     * @param listener the listener to be deregistered
     */
    public void removePropertyChangeListener(
        PropertyChangeListener listener) 
    {
        changeAdapter.removePropertyChangeListener(listener);
    }

    /**
     * Deregisters the given object 
     * from listening for property change events
     * generated by this object
     * with the given property name.
     *
     * @param propertyName the name of the desired property 
     * @param listener the listener to be deregistered
     */
    public void removePropertyChangeListener(
        String propertyName,
        PropertyChangeListener listener) 
    {
        changeAdapter.removePropertyChangeListener(
            propertyName, 
            listener);
    }
}
