/*
 * @(#)XInterval.java    2.4.0   9 May 2005
 *
 * Copyright 2005
 * 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;

import edu.neu.ccs.*;
import edu.neu.ccs.util.*;

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.</p>
 *
 * <p>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.4.0
 * @since   1.0
 */
public class XInterval
    implements Serializable, Stringable, JPTConstants
{
    /** The data array with blank names "", "". */
    private static String[] BLANK = { "", "" };
    
    /** The data array with names "min", "max". */
    private static String[] MINMAX = { "min", "max" };
    
    /** The data array with names "max", "min". */
    private static String[] MAXMIN = { "max", "min" };
    
    /** The standard error message for fromStringData. */
    public static final String standardMessage =
        "\nXInterval Error: Data format must be\n"
        + "[...;...] or\n"
        + "[min=...;max=...] or\n"
        + "[max=...;min=...]\n"
        + "where ... stands for\n"
        + "the minimum and maximum of the interval\n";
    
    
    /** Lower endpoint of interval. */
    protected double minimum = 0.0;
    
    /** Upper endpoint of interval. */
    protected double maximum = 0.0;
    
    
    /** Helper object for property change API. */
    protected SwingPropertyChangeSupport changeAdapter =
        new SwingPropertyChangeSupport(this);
    
    
    /** Constructs an interval with the endpoints set to 0,0. */
    public XInterval() {};
    
    
    /**
     * Constructs an <CODE>XInterval</CODE> using the given
     * <CODE>XInterval</CODE>.
     * 
     * @param interval the interval to be copied
     */
    public XInterval(XInterval interval) {
    	setInterval(interval);
    }
    
    
    /**
     * <p>Constructs an interval with the given endpoints.</p>
     *
     * <p>The order of the endpoints does not matter since the
     * endpoints will be ordered internally.</p>
     *
     * @param endpointA one of the two endpoints
     * @param endpointB the other of the two endpoints
     */
    public XInterval(double endpointA, double endpointB) {
        setEndpoints(endpointA, endpointB);
    }
    
    
    /**
     * Constructs an interval from the encoded <CODE>String</CODE> data.
     *
     * @param data the encoded String data
     */
    public XInterval(String data) throws ParseException {
        fromStringData(data);
    }
    
    
    /**
     * <p>Returns a human readable <CODE>String</CODE> representing
     * the data state of this <CODE>XInterval</CODE> as an annotated
     * string.</p>
     *
     * <p><code>XInterval[min=...;max=...]</code></p>
     *
     * <p>where ... stands for the minimum and maximum of the interval.</p>
     */
    public String toString() {
        return "XInterval[min=" + minimum + ";max=" + maximum + "]";
    }
    
    
    /**
     * <p>Returns a human readable <CODE>String</CODE> representing
     * the data state of this <CODE>XInterval</CODE> as a simple
     * string.</p>
     *
     * <p><code>[...;...]</code></p>
     *
     * <p>where ... stands for the minimum and maximum of the interval.</p>
     */
    public String toStringData() {
        return "[" + minimum + ";" + maximum + "]";
    }
    
    
    /**
     * <p>Defines the data state for this <CODE>XInterval</CODE> object 
     * from a <CODE>String</CODE> representation of the data state.</p>
     * 
     * <p>Fires property change VALUE.</p>
     *
     * @param data <CODE>String</CODE> representation of the data state
     * @throws ParseException if the data is malformed
     */
    public void fromStringData(String data)
        throws ParseException
    {
        if (data == null)
            throw new ParseException(standardMessage, -1);

        String[] strings = Strings.decode(data);
            
        if (strings == null)
            throw new ParseException(standardMessage, -1);
        
        if (strings.length != 2)
            throw new ParseException(standardMessage, -1);
        
        String[] names  = Strings.getNames (strings);
        String[] values = Strings.getValues(strings);
        
        // check for valid names before parsing double values
        // to permit specific error messages
        
        if ((Arrays.equals(names, BLANK)) || (Arrays.equals(names, MINMAX)))
            names = MINMAX;
        else
        if (Arrays.equals(names, MAXMIN))
            names = MAXMIN;
        else
            throw new ParseException(standardMessage, -1);
        
        // parse double values
        
        double[] result = null;
        
        try {
            result = Strings.stringsToDoubles(values);
        }
        catch (ParseException ex) {
            throw Strings.makeAdjustedParseException(ex, "XInterval", names); 
        }
        
        setEndpoints(result[0], result[1]);
    }
    
    
    /**
     * <P>Sets the endpoints for the interval to the given endpoints.</P>
     *
     * <p>The order of the endpoints does not matter since the
     * endpoints will be ordered internally.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param endpointA one of the two endpoints
     * @param endpointB the other of the two endpoints
     */
    public void setEndpoints(double endpointA, double endpointB) {
        double oldMin  = getMinimum();
        double oldMax  = getMaximum();

        // set minimum and maximum
        if (endpointA <= endpointB) {
            minimum = endpointA;
            maximum = endpointB;
        }
        else {
            minimum = endpointB;
            maximum = endpointA;
        }
        
        // if anything has changed
        if ((minimum != oldMin) || (maximum != oldMax)) {
        
            // notify listeners of property change
            changeAdapter.firePropertyChange(VALUE, null, null);
        }
    }
    
    
    /**
     * <p>Sets the endpoints for this interval to the endpoints of
     * the given interval.</p>
     *
     * <p>Does nothing if the given interval is <CODE>null</CODE>.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param interval the interval to be copied
     */
    public void setInterval(XInterval interval) {
        if (interval == null)
            return;
            
        setEndpoints(interval.minimum, interval.maximum);
    }
    
    
    /** Returns the lower endpoint of the interval. */
    public double getMinimum() { 
        return minimum; 
    }
    
    
    /** Returns the upper endpoint of the interval. */
    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 maximum - minimum;
    }
    
    
    /**
     * Returns the midpoint of the interval.
     */
    public double getMidpoint() { 
        return minimum + (maximum - minimum) / 2; 
    }
    
    
    /////////////////////////////
    // 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);
    }
    
}
