/*
 * @(#)XPoint2D.java    2.4.0   13 July 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.util.*;

import java.awt.geom.*;
import java.util.*;
import java.text.ParseException;
import java.beans.PropertyChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;
import java.io.Serializable;

/**
 * <p>Class <code>XPoint2D</code> extends <code>Point2D.Double</code> in
 * order to provide a much shorter name that does not explicitly refer
 * to an inner class and to support <code>Stringable</code>.</p>
 *
 * @author  Richard Rasala
 * @version 2.4.0
 * @since   1.1
 */
public class XPoint2D extends Point2D.Double
    implements Serializable, Stringable, JPTConstants
{
    /** The data array with blank names "", "". */
    public static final String[] BLANK = { "", "" };
    
    /** The data array with names "x", "y". */
    public static final String[] XY = { "x", "y" };
    
    /** The standard error message for fromStringData. */
    public static final String standardMessage =
        "\nXPoint2D Error: Data format must be\n"
        + "[...;...] or\n"
        + "[x=...;y=...]\n"
        + "where ... stands for\n"
        + "the x,y coordinate data\n";
    
    
    /** Helper object for property change API. */
    protected SwingPropertyChangeSupport changeAdapter =
        new SwingPropertyChangeSupport(this);
    
    
    /** Constructs a default <CODE>XPoint2D</CODE>. */
    public XPoint2D() {}
    
    
    /**
     * Constructs an <CODE>XPoint2D</CODE> using the given
     * x,y <CODE>double</CODE> values.
     * 
     * @param x the x-coordinate of the point
     * @param y the y-coordinate of the point
     */
    public XPoint2D(double x, double y) {
        setValue(x, y);
    }
    

    /**
     * <p>Constructs an <CODE>XPoint2D</CODE> using a copy of
     * the data in the given <CODE>Point2D</CODE>.</p>
     * 
     * <p>If the given point is <code>null</code>,
     * then initializes this point to 0,0.</p>
     *
     * @param p the existing point whose data will be copied
     */
    public XPoint2D(Point2D p) {
    	setValue(p);
    }
    
    
    /**
     * Constructs an <CODE>XPoint2D</CODE> using the pair of values
     * in the given array of <code>double</code> which must be of
     * size 2.</p>
     *
     * <p>If the given array is <code>null</code> or not of size 2,
     * then initializes this point to 0,0.</p>
     *
     * @param data the array of size 2 with the x,y data
     */
    public XPoint2D(double[] data) {
    	setValue(data);
    }
    
    
    /**
     * Constructs an <CODE>XPoint2D</CODE> using the pair of values
     * in the given array of <code>float</code> which must be of
     * size 2.</p>
     *
     * <p>If the given array is <code>null</code> or not of size 2,
     * then initializes this point to 0,0.</p>
     *
     * @param data the array of size 2 with the x,y data
     */
    public XPoint2D(float[] data) {
    	setValue(data);
    }
    
    
    /**
     * Constructs an <CODE>XPoint2D</CODE> object from
     * a <CODE>String</CODE> representation of the data state.
     * 
     * @param data <CODE>String</CODE> representation of the data state
     * @throws ParseException if the data is malformed
     */
    public XPoint2D(String data) throws ParseException {
        fromStringData(data);
    }
    
    
    /**
     * <p>Returns a human readable <CODE>String</CODE> representing
     * the data state of this <CODE>XPoint2D</CODE> as an annotated
     * string.</p>
     *
     * <p><code>XPoint2D[x=...;y=...]</code></p>
     *
     * <p>where the dots stand for the x,y coordinate data.</p>
     */
    public String toString() {
        return "XPoint2D[x=" + x + ";y=" + y + "]";
    }
    
    
    /**
     * <p>Returns a human readable <CODE>String</CODE> representing
     * the data state of this <CODE>XPoint2D</CODE> as a simple
     * string.</p>
     *
     * <p><code>[...;...]</code></p>
     *
     * <p>where the dots stand for the x,y coordinate data.</p>
     */
    public String toStringData() {
        return "[" + x + ";" + y + "]";
    }
    
    
    /**
     * <p>Defines the data state for this <CODE>XPoint2D</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, XY)))
            names = XY;
        else
            throw new ParseException(standardMessage, -1);
        
        // parse double values
        
        double[] result = null;
        
        try {
            result = Strings.stringsToDoubles(values);
        }
        catch (ParseException ex) {
            throw Strings.makeAdjustedParseException(ex, "XPoint2D", names); 
        }
        
        setValue(result[0], result[1]);
    }
    
    
    /**
     * <p>Sets this point to the given x,y values.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param x the x-coordinate of the point
     * @param y the y-coordinate of the point
     */
    public void setValue(double x, double y) {
        double oldX = this.x;
        double oldY = this.y;
        
        // if data has changed
        if ((x != oldX) || (y != oldY)) {
            // modify state
            this.x = x;
            this.y = y;
            
            // notify listeners of property change
            changeAdapter.firePropertyChange(VALUE, null, null);
        }
    }
    
    
    /**
     * <p>Sets this point to the value of the given point.</p>
     *
     * <p>If the given point is <code>null</code>, then does nothing.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param p the existing point whose data will be copied
     */
    public void setValue(Point2D p) {
    	if (p != null)
			setValue(p.getX(), p.getY());        
    }
    
    
    /**
     * <p>Sets this point using the pair of values in the given array
     * of <code>double</code> which must be of size 2.</p>
     *
     * <p>If the given array is <code>null</code> or not of size 2,
     * then does nothing.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param data the array of size 2 with the x,y data
     */
    public void setValue(double[] data) {
        if ((data == null) || (data.length != 2))
            return;
        
        setValue(data[0], data[1]);
    }
    
    
    /**
     * <p>Sets this point using the pair of values in the given array
     * of <code>float</code> which must be of size 2.</p>
     *
     * <p>If the given array is <code>null</code> or not of size 2,
     * then does nothing.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param data the array of size 2 with the x,y data
     */
    public void setValue(float[] data) {
        if ((data == null) || (data.length != 2))
            return;
        
        setValue(data[0], data[1]);
    }
    
    
    /**
     * <p>Sets this point to the given x,y values.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * <p>Equivalent to <code>setValue</code>.</p>
     *
     * @param x the x-coordinate
     * @param y the y-coordinate
     */
    public void setLocation(double x, double y) {
        setValue(x, y);
    }
    
    
    /**
     * <p>Sets this point to the value of the given point.</p>
     *
     * <p>If the given point is <code>null</code>, then does nothing.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param p the existing point whose data will be copied
     */
    public void setLocation(Point2D p) {
        setValue(p);        
    }
    
    
    /**
     * <p>Sets this point using the pair of values in the given array
     * of <code>double</code> which must be of size 2.</p>
     *
     * <p>If the given array is <code>null</code> or not of size 2,
     * then does nothing.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param data the array of size 2 with the x,y data
     */
    public void setLocation(double[] data) {
        if ((data == null) || (data.length != 2))
            return;
        
        setValue(data[0], data[1]);
    }
    
    
    /**
     * <p>Sets this point using the pair of values in the given array
     * of <code>float</code> which must be of size 2.</p>
     *
     * <p>If the given array is <code>null</code> or not of size 2,
     * then does nothing.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param data the array of size 2 with the x,y data
     */
    public void setLocation(float[] data) {
        if ((data == null) || (data.length != 2))
            return;
        
        setValue(data[0], data[1]);
    }
    
    
    /**
     * <p>Moves the point by a translation using the data in the point
     * specified by coordinates.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param dx the x-coordinate of the translation
     * @param dy the y-coordinate of the translation
     */
    public void move(double dx, double dy) {
        double x = getX();
        double y = getY();
        
        setValue(x + dx, y + dy);
    }
    
    
    /**
     * <p>Moves the point by a translation using the data in the point.</p>
     *
     * <p>If the given point is <code>null</code>, then does nothing.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param p the translation vector
     */
    public void move(Point2D p) {
        if (p != null)
            move(p.getX(), p.getY());
    }
    
    
    /**
     * <p>Returns the x,y data in an array of <code>double</code>
     * of size 2.</p>
     */
    public double[] toDoubleArray() {
        return new double[] { x, y };
    }
    
    
    /**
     * <p>Returns the x,y data in an array of <code>float</code>
     * of size 2.</p>
     */
    public float[] toFloatArray() {
        return new float[] { (float) x, (float) y };
    }
    
    
    /**
     * Computes the distance of the point to the origin, that is
     * <code>sqrt(x*x+y*y)</code>.
     */
    public double radius() {
        return distance(0, 0);
    }
    
    
    /**
     * Computes the distance squared of the point to the origin,
     * that is, <code>x*x+y*y</code>.
     */
    public double radiusSq() {
        return distanceSq(0, 0);
    }
    
    
    /**
     * Computes the angle in radians from the positive x-axis.
     */
    public double angleInRadians() {
        return angleInRadians(1, 0, x, y);
    }
    
    
    /**
     * Computes the angle in radians from the point p to the point q.
     *
     * @param p point to start  angle measurement
     * @param q point to finish angle measurement
     */
    public static double angleInRadians(Point2D p, Point2D q) {
        if ((p != null) && (q != null))
            return angleInRadians
                (p.getX(), p.getY(), q.getX(), q.getY());
        else
            return 0;
    }
    
    
    /**
     * Computes the angle in radians from the
     * the point (x1,y1) to the point (x2,y2).
     *
     * @param x1 x-coordinate of point to start  angle measurement
     * @param y1 y-coordinate of point to start  angle measurement
     * @param x2 x-coordinate of point to finish angle measurement
     * @param y2 y-coordinate of point to finish angle measurement
     */
    public static double angleInRadians
        (double x1, double y1, double x2, double y2)
    {
        double radians = Math.atan2(y2, x2) - Math.atan2(y1, x1);
        
        if (radians < 0)
            radians += 2 * Math.PI;
        
        return radians;
    }
    
    
    /**
     * Computes the angle in degrees from the positive x-axis.
     */
    public double angleInDegrees() {
        return Math.toDegrees(angleInRadians());
    }
    
    
    /**
     * Computes the angle in degrees from the point p to the point q.
     *
     * @param p point to start  angle measurement
     * @param q point to finish angle measurement
     */
    public static double angleInDegrees(Point2D p, Point2D q) {
        return Math.toDegrees(angleInRadians(p, q));
    }
    
    
    /**
     * Computes the angle in degrees from the
     * the point (x1,y1) to the point (x2,y2).
     *
     * @param x1 x-coordinate of point to start  angle measurement
     * @param y1 y-coordinate of point to start  angle measurement
     * @param x2 x-coordinate of point to finish angle measurement
     * @param y2 y-coordinate of point to finish angle measurement
     */
    public static double angleInDegrees
        (double x1, double y1, double x2, double y2)
    {
        return Math.toDegrees(angleInRadians(x1, y1, x2, y2));
    }
    
    
    /////////////////////////////
    // 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);
    }
    
}
 
