/*
 * @(#)XCircle.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>XCircle</code> extends <code>XOval</code>
 * but requires that its location be specifed by its center
 * and its size be specified by its radius.  The diameter
 * of the circle is twice the radius and is the value
 * returned by either <code>getWidth</code> or
 * <code>getHeight</code>.</p>
 *
 * <p>The design of this class makes it similar to the class
 * <code>Square</code>.</p>
 *
 * <p>Inherited methods that would permit the caller to make
 * the width and height have different values are overridden
 * to do nothing.</p>
 *
 * @author  Richard Rasala
 * @version 2.4.0
 * @since   2.4.0
 */
public class XCircle extends XOval
    implements Serializable, Stringable, JPTConstants
{
    /** The data array with blank names "", "", "". */
    private static String[] BLANK = { "", "", "" };
    
    /** The data array with names "xc", "yc", "r". */
    private static String[] XYR = { "xc", "yc", "r" };
    
    /** The standard error message for fromStringData. */
    public static final String standardMessage =
        "\nXCircle Error: Data format must be\n"
        + "[...;...;...] or\n"
        + "[xc=...;yc=...;r=...]\n"
        + "where ... stands for\n"
        + "the center data xc,yc\n"
        + "and the radius r\n";
    
    
    /** The radius. */
    private double r;
    
    
    /** Helper object for property change API. */
    protected SwingPropertyChangeSupport changeAdapter =
        new SwingPropertyChangeSupport(this);
    
    /**
     * Constructs a new <code>XCircle</code> initialized to center (0, 0)
     * and radius 0.
     */
    public XCircle() {}
    
    
    /**
     * Constructs and initializes a <code>XCircle</code> from the specified
     * double coordinates.  Calls <code>setXYR</code>.
     *
     * @param x the proposed center x-coordinate
     * @param y the proposed center y-coordinate
     * @param r the proposed radius
     */
    public XCircle(double x, double y, double r) {
        setXYR(x, y, r);
    }
    
    
    /**
     * <p>Constructs a new <code>XCircle</code> initialized to center (0, 0)
     * and with the given radius r.  Calls <code>setXYR</code></p>
     *
     * @param r the proposed radius
     */
    public XCircle(double r) {
        setXYR(0, 0, r);
    }
    
    
    /**
     * <p>Constructs and initializes a <code>XCircle</code> from the specified
     * XCircle object.  Calls <code>setXYR</code>.</p>
     *
     * <p>Initializes to center (0, 0) and radius 0 if the given circle
     * is <code>null</code>.</p>
     *
     * @param circle the XCircle object to copy
     */
    public XCircle(XCircle circle) {
        setCircle(circle);
    }
    
    
    /**
     * <p>Returns a human readable <CODE>String</CODE> representing
     * the data state of this <CODE>XCircle</CODE> as an annotated
     * string.</p>
     *
     * <p><code>XCircle[xc=...;yc=...;r=...]</code></p>
     *
     * <p>where the dots stand for the xc,yc center data
     * and the radius r.</p>
     */
    public String toString() {
        double xc = getCenterX();
        double yc = getCenterY();
        
        return "XCircle[xc=" + xc + ";yc=" + yc + ";r=" + r + "]";
    }
    
    
    /**
     * <p>Returns a human readable <CODE>String</CODE> representing
     * the data state of this <CODE>XCircle</CODE> as a simple
     * string.</p>
     *
     * <p><code>[...;...;...]</code></p>
     *
     * <p>where the dots stand for the xc,yc center data
     * and the radius r.</p>
     */
    public String toStringData() {
        double xc = getCenterX();
        double yc = getCenterY();
        
        return "[" + xc + ";" + yc + ";" + r + "]";
    }
    
    
    /**
     * <p>Defines the data state for this <CODE>XCircle</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 != 3)
            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, XYR)))
            names = XYR;
        else
            throw new ParseException(standardMessage, -1);
        
        // parse double values
        
        double[] result = null;
        
        try {
            result = Strings.stringsToDoubles(values);
        }
        catch (ParseException ex) {
            throw Strings.makeAdjustedParseException(ex, "XCircle", names); 
        }
        
        setXYR(result[0], result[1], result[2]);
    }
    
        
    /**
     * <p>Sets the parameters of the <code>XCircle</code> with error checking.</p>
     *
     * <p>If the radius r is negative, it is replaced by its absolute value.</p>
     *
     * <p>The circle is then set up as an oval with center location xc,yc
     * and common width and height equal to 2*r.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param xc the proposed center x-coordinate
     * @param yc the proposed center y-coordinate
     * @param r the proposed radius
     */
    public void setXYR(double xc, double yc, double r) {
        r = Math.abs(r);
        this.r = r;
        
        double d = 2*r;
        
        super.setXYWH(xc - r, yc - r, d, d);
        
        // notify listeners of property change
        changeAdapter.firePropertyChange(VALUE, null, null);
    }
    
    
    /**
     * <p>Uses the data from the given circle to set the parameters of this circle.</p>
     *
     * <p>Does nothing if the given circle is <code>null</code>.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param circle the XCircle object to copy
     */
    public void setCircle(XCircle circle) {
        if (circle == null)
            return;
        
        double xc = circle.getCenterX();
        double yc = circle.getCenterY();
        double r = circle.getRadius();
        
        setXYR(xc, yc, r);
    }
    
    
    /** Returns the radius. */
    public double getRadius() { return r; }
    
    
    /**
     * <p>Overrides the inherited method to do nothing since a circle cannot have a
     * distinct width and height.</p>
     *
     * <p>The fact that this method does nothing implies that the following methods
     * inherited from <code>XOval</code> and other base classes also do nothing.</p>
     *
     * <ul>
     *  <li><code>setXYRS</code></li>
     *  <li><code>setX1Y1X2Y2</code></li>
     *  <li><code>setFrame</code></li>
     *  <li><code>setFrameFromCenter</code></li>
     *  <li><code>setFrameFromDiagonal</code></li>
     * </ul>
     */
    public void setXYWH(double x, double y, double w, double h) {}
    

    /**
     * <p>Moves the circle 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) {
        double xc = getCenterX();
        double yc = getCenterY();
        double r = getRadius();
        
        setXYR(x + dx, y + dy, r);
    }
    
    
    /**
     * <p>Moves the circle 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());
    }
    
    
    /////////////////////////////
    // 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);
    }
    
}
