/*
 * @(#)XLine2D.java    2.4.0   24 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.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>XLine2D</code> extends <code>Line2D.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   2.4.0
 */
public final class XLine2D extends Line2D.Double
    implements Serializable, Stringable, JPTConstants
{
    /** The data array with blank names "", "", "", "". */
    private static String[] BLANK = { "", "", "", "" };
    
    /** The data array with names "x1", "y1", "x2", "y2". */
    private static String[] X1Y1X2Y2 = { "x1", "y1", "x2", "y2" };
    
    /** The standard error message for fromStringData. */
    public static final String standardMessage =
        "\nXLine2D Error: Data format must be\n"
        + "[...;...;...;...] or\n"
        + "[x1=...;y1=...;x2=...;y2=...]\n"
        + "where ... stands for\n"
        + "the endpoint coordinate data\n"
        + "x1,y1 and x2,y2\n";
    
    
    /** Helper object for property change API. */
    protected SwingPropertyChangeSupport changeAdapter =
        new SwingPropertyChangeSupport(this);
    
    
    /** Constructs a default <CODE>XLine2D</CODE>. */
    public XLine2D() {}
    
    
    /**
     * Constructs an <CODE>XLine2D</CODE> using the given
     * <CODE>Line2D</CODE> value.
     * 
     * @param line the line to be copied
     */
    public XLine2D(Line2D line) {
    	setValue(line);
    }
    
    
    /**
     * Constructs an <CODE>XLine2D</CODE> using the given
     * <CODE>Point2D</CODE> values.
     * 
     * @param p1 point 1
     * @param p2 point 2
     */
    public XLine2D(Point2D p1, Point2D p2) {
        setValue(p1, p2);
    }
    

    /**
     * Constructs an <CODE>XLine2D</CODE> using the given
     * <CODE>double</CODE> values.
     * 
     * @param x1 the x-coordinate of point 1
     * @param y1 the y-coordinate of point 1
     * @param x2 the x-coordinate of point 2
     * @param y2 the y-coordinate of point 2
     */
    public XLine2D(double x1, double y1, double x2, double y2) {
        setValue(x1, y1, x2, y2);
    }
    

    /**
     * Constructs an <CODE>XLine2D</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 XLine2D(String data) throws ParseException {
        fromStringData(data);
    }
    
    
    /**
     * <p>Returns a human readable <CODE>String</CODE> representing
     * the data state of this <CODE>XLine2D</CODE> as an annotated
     * string.</p>
     *
     * <p><code>XLine2D[x1=...;y1=...;x2=...;y2=...]</code></p>
     *
     * <p>where the dots stand for the x1,y1,x2,y2 coordinate data.</p>
     */
    public String toString() {
        return "XLine2D[x1=" + x1 + ";y1=" + y1 + ";x2=" + x2 + ";y2=" + y2 + "]";
    }
    
    
    /**
     * <p>Returns a human readable <CODE>String</CODE> representing
     * the data state of this <CODE>XLine2D</CODE> as a simple
     * string.</p>
     *
     * <p><code>[...;...;...;...]</code></p>
     *
     * <p>where the dots stand for the x1,y1,x2,y2 coordinate data.</p>
     */
    public String toStringData() {
        return "[" + x1 + ";" + y1 + ";" + x2 + ";" + y2 + "]";
    }
    
    
    /**
     * <p>Defines the data state for this <CODE>XLine2D</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 != 4)
            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, X1Y1X2Y2)))
            names = X1Y1X2Y2;
        else
            throw new ParseException(standardMessage, -1);
        
        // parse double values
        
        double[] result = null;
        
        try {
            result = Strings.stringsToDoubles(values);
        }
        catch (ParseException ex) {
            throw Strings.makeAdjustedParseException(ex, "XLine2D", names); 
        }
        
        setValue(result[0], result[1], result[2], result[3]);
    }
    
        
    /**
     * <p>Sets this line to the given value.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param line the line to copy
     */
    public void setValue(Line2D line) {
    	if (line != null)
			setValue(line.getX1(), line.getY1(), line.getX2(), line.getY2());        
    }
    
    
    /**
     * <p>Sets this line to the given values.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param p1 point 1
     * @param p2 point 2
     */
    public void setValue(Point2D p1, Point2D p2) {
        if ((p1 != null) && (p2 != null))
            setValue(p1.getX(), p1.getY(), p2.getX(), p2.getY());
    }
    
    
    /**
     * <p>Sets this line to the given values.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param x1 the x-coordinate of point 1
     * @param y1 the y-coordinate of point 1
     * @param x2 the x-coordinate of point 2
     * @param y2 the y-coordinate of point 2
     */
    public void setValue(double x1, double y1, double x2, double y2) {
        double oldX1 = this.x1;
        double oldY1 = this.y1;
        double oldX2 = this.x2;
        double oldY2 = this.y2;
        
        // if data has changed
        if ((x1 != oldX1) || (y1 != oldY1) || (x2 != oldX2) || (y2 != oldY2)) {
            // modify state
            this.x1 = x1;
            this.y1 = y1;
            this.x2 = x2;
            this.y2 = y2;
        
            // notify listeners of property change
            changeAdapter.firePropertyChange(VALUE, null, null);
        }
    }
    
    
    /**
     * <p>Sets this line to the given value.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param line the line to copy
     */
    public void setLine(Line2D line) {
    	setValue(line);        
    }
    
    
    /**
     * <p>Sets this line to the given values.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param p1 point 1
     * @param p2 point 2
     */
    public void setLine(Point2D p1, Point2D p2) {
        setValue(p1, p2);
    }
    
    
    /**
     * <p>Sets this line to the given values.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param x1 the x-coordinate of point 1
     * @param y1 the y-coordinate of point 1
     * @param x2 the x-coordinate of point 2
     * @param y2 the y-coordinate of point 2
     */
    public void setLine(double x1, double y1, double x2, double y2) {
        setValue(x1, y1, x2, y2);
    }
    
    
    /**
     * <p>Moves the line by a translation using the data in the point
     * specified by coordinates.</p>
     *
     * @param dx the x-coordinate of the translation
     * @param dy the y-coordinate of the translation
     */
    public void move(double dx, double dy) {
        setValue(x1 + dx, y1 + dy, x2 + dx, y2 +dy);
    }
    
    
    /**
     * <p>Moves the line by a translation using the data in the point.</p>
     *
     * @param p the translation vector
     */
    public void move(Point2D p) {
        if (p == null)
            return;
        
        move(p.getX(), p.getY());
    }
    
    
    /** Computes the length of the line, that is, the distance from x1,y1 to x2,y2. */
    public double length() {
        return Point2D.distance(x1, y1, x2, y2);
    }
    
    
    /**
     * Computes the length squared of the line, that is, the distance squared from
     * x1,y1 to x2,y2.
     */
    public double lengthSq() {
        return Point2D.distanceSq(x1, y1, x2, y2);
    }
    
    
    /** Computes the angle in radians of the line from the positive x-axis. */
    public double angleInRadians() {
        return XPoint2D.angleInRadians(x1, y1, x2, y2);
    }
    
    
    /** Computes the angle in degrees of the line from the positive x-axis. */
    public double angleInDegrees() {
        return XPoint2D.angleInDegrees(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);
    }
    
}
 
