/*
 * @(#)PathNode.java    2.4.0   22 September 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.gui;

import edu.neu.ccs.*;
import edu.neu.ccs.util.*;

import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import java.text.ParseException;

/**
 * <p>Class <code>PathNode</code> makes explicit a data structure for
 * the ingredients that may be used to build a Java object of class
 * <code>GeneralPath</code>.</p>
 *
 * <p>In JPT, we introduce the <code>PathList</code> class that combines
 * an explicit sequence of <code>PathNode</code> objects together with a
 * winding rule to provide a flexible alternative to GeneralPath.</p>
 *
 * <p>Class <code>PathNode</code> introduces abbreviations for the
 * somewhat cumbersome names in the Java class <code>PathIterator</code>.
 * The table below shows the corresponding names and also the number of
 * parameters for each type of path segment.</p>
 *
 * <table cellspacing="0" cellpadding="4">
 *   <tr>
 *     <td><b>PathIterator Constant</b></td>
 *     <td><b>PathNode Constant</b></td>
 *     <td><b>Float Parameters</b></td>
 *   </tr>
 *   <tr>
 *     <td><code>SEG_MOVETO</code></td>
 *     <td><code>MOVE</code></td>
 *     <td><i>x1, y1</i></td>
 *   </tr>
 *   <tr>
 *     <td><code>SEG_LINETO</code></td>
 *     <td><code>LINE</code></td>
 *     <td><i>x1, y1</i></td>
 *   </tr>
 *   <tr>
 *     <td><code>SEG_QUADTO</code></td>
 *     <td><code>QUAD</code></td>
 *     <td><i>x1, y1, x2, y2</i></td>
 *   </tr>
 *   <tr>
 *     <td><code>SEG_CUBICTO</code></td>
 *     <td><code>CUBIC</code></td>
 *     <td><i>x1, y1, x2, y2, x3, y3</i></td>
 *   </tr>
 *   <tr>
 *     <td><code>SEG_CLOSE</code></td>
 *     <td><code>CLOSE</code></td>
 *     <td><i>None</i></td>
 *   </tr>
 * </table>
 *
 * <p>By Java convention in the <code>PathIterator</code> documentation, the
 * path segment parameters are referred to as <i>x1, y1, x2, y2, x3, y3</i>
 * although it is clear that only the <code>CUBIC</code> segment requires
 * all 6 parameters.  We will follow these conventions in naming methods and
 * parameters for this class.</p>
 *
 * <p>We will also refer to the pair <i>x1,y1</i> as <i>slot 1</i>,
 * <i>x2,y2</i> as <i>slot 2</i>, and <i>x3,y3</i> as <i>slot 3</i>
 * when we speak of methods that set or query slots.</p>
 */
public class PathNode
    implements Stringable
{
    /** Shorthand constant for PathIterator.SEG_MOVETO. */
    public static final int MOVE  = PathIterator.SEG_MOVETO;
    
    /** Shorthand constant for PathIterator.SEG_LINETO. */
    public static final int LINE  = PathIterator.SEG_LINETO;
    
    /** Shorthand constant for PathIterator.SEG_QUADTO. */
    public static final int QUAD  = PathIterator.SEG_QUADTO;
    
    /** Shorthand constant for PathIterator.SEG_CUBICTO. */
    public static final int CUBIC = PathIterator.SEG_CUBICTO;
    
    /** Shorthand constant for PathIterator.SEG_CLOSE. */
    public static final int CLOSE = PathIterator.SEG_CLOSE;
    
    /** The standard error message for fromStringData. */
    public static final String standardMessage =
        "\nPathNode data format must be one of\n"
        + "MOVE[x1;y1] or\n"
        + "LINE[x1;y1] or\n"
        + "QUAD[x1;y1;x2;y2] or\n"
        + "CUBIC[x1;y1;x2;y2;x3;y3] or\n"
        + "CLOSE[]\n"
        + "where x1,y1,x2,y2,x3,y3 are float values\n";
    
    
    /**
     * The type of the path node; must be one of the constants:
     * MOVE, LINE, QUAD, CUBIC, CLOSE.
     */
    protected int nodetype = MOVE;
    
    /** The x1 parameter. */
    protected float x1 = 0;
    
    /** The y1 parameter. */
    protected float y1 = 0;
    
    /** The x2 parameter. */
    protected float x2 = 0;
    
    /** The y2 parameter. */
    protected float y2 = 0;
    
    /** The x3 parameter. */
    protected float x3= 0;
    
    /** The y3 parameter. */
    protected float y3 = 0;
    
    
    
    /**
     * The default constructor that sets the node type to MOVE
     * and sets all numeric data to 0.
     */
    public PathNode() {}
    
    
    /**
     * <p>The constructor that sets the node type and as much of
     * the numeric data as is in the parameters array.</p>
     *
     * <p>The number of data cells used in the parameters array
     * depends on the node type according to the following rules:</p>
     *
     * <ul>
     *  <li><code>MOVE:  2</code></li>
     *  <li><code>LINE:  2</code></li>
     *  <li><code>QUAD:  4</code></li>
     *  <li><code>CUBIC: 6</code></li>
     *  <li><code>CLOSE: 0</code></li>
     * </ul>
     *
     * <p>Throws <code>IllegalArgumentException</code> if the node
     * type is not one of the constants: MOVE, LINE, QUAD, CUBIC,
     * or CLOSE.</p>
     *
     * <p>If the parameters array is <code>null</code>,
     * the missing data is treated as 0.</p>
     *
     * <p>If the parameters array is non-<code>null</code>,
     * then its length must be large enough to fill the required
     * number of parameters for the current node type or else an
     * <code>IllegalArgumentException</code> is thrown.</p>
     *
     * @param type the node type
     * @param parameters the numeric data for the node
     * @throws IllegalArgumentException
     */
    public PathNode(int type, float[] parameters) {
        setNodeType(type);
        setParameters(parameters);
    }
    
    
    /**
     * <p>The constructor that sets the node type and as much of
     * the numeric data as is in the parameters array.</p>
     *
     * <p>The number of data cells used in the parameters array
     * depends on the node type according to the following rules:</p>
     *
     * <ul>
     *  <li><code>MOVE:  2</code></li>
     *  <li><code>LINE:  2</code></li>
     *  <li><code>QUAD:  4</code></li>
     *  <li><code>CUBIC: 6</code></li>
     *  <li><code>CLOSE: 0</code></li>
     * </ul>
     *
     * <p>Throws <code>IllegalArgumentException</code> if the node
     * type is not one of the constants: MOVE, LINE, QUAD, CUBIC,
     * or CLOSE.</p>
     *
     * <p>If the parameters array is <code>null</code>,
     * the missing data is treated as 0.</p>
     *
     * <p>If the parameters array is non-<code>null</code>,
     * then its length must be large enough to fill the required
     * number of parameters for the current node type or else an
     * <code>IllegalArgumentException</code> is thrown.</p>
     *
     * <p>This is a convenience constructor so that the parameters
     * may be specified by an array of double rather than float.</p>
     *
     * @param type the node type
     * @param parameters the numeric data for the node
     * @throws IllegalArgumentException
     */
    public PathNode(int type, double[] parameters) {
        setNodeType(type);
        setParameters(parameters);
    }
    
    
    /**
     * <p>The constructor that sets the node type and the numeric
     * node data.</p>
     *
     * <p>The number of data items used depends on the node type
     * according to the following rules:</p>
     *
     * <ul>
     *  <li><code>MOVE:  2</code></li>
     *  <li><code>LINE:  2</code></li>
     *  <li><code>QUAD:  4</code></li>
     *  <li><code>CUBIC: 6</code></li>
     *  <li><code>CLOSE: 0</code></li>
     * </ul>
     *
     * <p>Data items that will not be used may be passed as 0.</p>
     *
     * <p>Throws <code>IllegalArgumentException</code> if the node
     * type is not one of the constants: MOVE, LINE, QUAD, CUBIC,
     * or CLOSE.</p>
     *
     * @param type the node type
     * @param x1 parameter 0
     * @param y1 parameter 1
     * @param x2 parameter 2
     * @param y2 parameter 3
     * @param x3 parameter 4
     * @param y3 parameter 5
     * @throws IllegalArgumentException
     */
    public PathNode
        (int type,
         float x1, float y1, float x2, float y2, float x3, float y3)
    {
        setNodeType(type);
        setParameters(x1, y1, x2, y2, x3, y3);
    }
    
    
    /**
     * <p>The constructor that copies the data of an existing
     * path node.</p>
     *
     * <p>If the given path node is <code>null</code> then acts
     * as the default constructor.</p>
     *
     * @param node the path node to copy
     */
    public PathNode(PathNode node) {
        setPathNode(node);
    }
    
    
    /**
     * <p>Constructor that initializes a new node using the data
     * in the <code>String</code> format that is produced by the
     * method <code>toString()</code>.</p>
     *
     * <p>Throws <code>ParseException</code> if the data is not in
     * the correct format or has numeric errors.</p>
     *
     * @param data the <code>String</code> with the state information
     * @throws ParseException
     */
    public PathNode(String data)
        throws ParseException
    {
        fromStringData(data);
    }
    
    
    /**
     * <p>Sets this path node to have a copy of the settings of
     * the given path node.</p>
     *
     * <p>If the given path node is <code>null</code> then does
     * nothing.</p>
     *
     * @param node the path node to copy
     */
    public final void setPathNode(PathNode node) {
        if (node != null) {
            nodetype = node.nodetype;
            
            switch (nodetype) {
                case CUBIC:
                    x3 = node.x3;
                    y3 = node.y3;
                
                case QUAD:
                    x2 = node.x2;
                    y2 = node.y2;
                
                case MOVE:
                case LINE:
                    x1 = node.x1;
                    y1 = node.y1;
                
                case CLOSE:
                default:
                    break;
            }
        }
    }
    
    
    /**
     * <p>Sets the node type of the node.</p>
     *
     * <p>Throws <code>IllegalArgumentException</code> if the node
     * type is not one of the constants: MOVE, LINE, QUAD, CUBIC,
     * or CLOSE.</p>
     *
     * <p>Does nothing if the node type will not be changed.</p>
     *
     * <p>If the node type is changed then it is almost certain
     * that the numeric data in this node is invalid and it is
     * the responsibility of the caller to use one or more of
     * the set methods to provide valid data.</p>
     *
     * @param type the node type
     */
    public void setNodeType(int type) {
        if (type == nodetype)
            return;
        
        switch (type) {
            case MOVE:
            case LINE:
            case QUAD:
            case CUBIC:
            case CLOSE:
                nodetype = type;
                break;
            
            default:
                throw new IllegalArgumentException
                    ("Invalid node type for PathNode: " + type);
        }
    }
    
    
    /**
     *<p>Returns the node type which is one of the constants:
     * MOVE, LINE, QUAD, CUBIC, CLOSE.</p>
     */
    public int getNodeType() {
        return nodetype;
    }
    
    
    /**
     * <p>Sets the internal parameters appropriate for the current
     * node type but does not set parameters that are unused.</p>
     *
     * <p>If the parameters array is <code>null</code>,
     * the missing data is treated as 0.</p>
     *
     * <p>If the parameters array is non-<code>null</code>,
     * then its length must be large enough to fill the required
     * number of parameters for the current node type or else an
     * <code>IllegalArgumentException</code> is thrown.</p>
     *
     * @param parameters the numeric data for the node
     */
    public void setParameters(float[] parameters) {
        if (parameters == null) {
            switch (nodetype) {
                case CUBIC:
                    x3 = 0;
                    y3 = 0;
                
                case QUAD:
                    x2 = 0;
                    y2 = 0;
                
                case MOVE:
                case LINE:
                    x1 = 0;
                    y1 = 0;
                
                case CLOSE:
                default:
                    break;
            }
        }
        else {
            int M = getParameterCount();
            int N = parameters.length;
            
            if (N < M)
                throw new IllegalArgumentException
                    ("Too few arguments in PathNode.setParameters");
            
            switch (nodetype) {
                case CUBIC:
                    x3 = parameters[4];
                    y3 = parameters[5];
                
                case QUAD:
                    x2 = parameters[2];
                    y2 = parameters[3];
                
                case MOVE:
                case LINE:
                    x1 = parameters[0];
                    y1 = parameters[1];
                
                case CLOSE:
                default:
                    break;
            }
        }
    }
    
    
    /**
     * <p>Sets the internal parameters appropriate for the current
     * node type but does not set parameters that are unused.</p>
     *
     * <p>If the parameters array is <code>null</code>,
     * the missing data is treated as 0.</p>
     *
     * <p>If the parameters array is non-<code>null</code>,
     * then its length must be large enough to fill the required
     * number of parameters for the current node type or else an
     * <code>IllegalArgumentException</code> is thrown.</p>
     *
     * @param parameters the numeric data for the node
     */
    public void setParameters(double[] parameters) {
        if (parameters == null) {
            switch (nodetype) {
                case CUBIC:
                    x3 = 0;
                    y3 = 0;
                
                case QUAD:
                    x2 = 0;
                    y2 = 0;
                
                case MOVE:
                case LINE:
                    x1 = 0;
                    y1 = 0;
                
                case CLOSE:
                default:
                    break;
            }
        }
        else {
            int M = getParameterCount();
            int N = parameters.length;
            
            if (N < M)
                throw new IllegalArgumentException
                    ("Too few arguments in PathNode.setParameters");
            
            switch (nodetype) {
                case CUBIC:
                    x3 = (float) parameters[4];
                    y3 = (float) parameters[5];
                
                case QUAD:
                    x2 = (float) parameters[2];
                    y2 = (float) parameters[3];
                
                case MOVE:
                case LINE:
                    x1 = (float) parameters[0];
                    y1 = (float) parameters[1];
                
                case CLOSE:
                default:
                    break;
            }
        }
    }
    
    
    /**
     * <p>Sets the parameters appropriate for the current node
     * type using explicit float values.</p>
     *
     * <p>Does not set internal parameters that are unused for
     * the current node type.</p>
     *
     * @param x1 parameter 0
     * @param y1 parameter 1
     * @param x2 parameter 2
     * @param y2 parameter 3
     * @param x3 parameter 4
     * @param y3 parameter 5
     */
    public void setParameters
        (float x1, float y1, float x2, float y2, float x3, float y3)
    {
        switch (nodetype) {
            case CUBIC:
                this.x3 = x3;
                this.y3 = y3;
            
            case QUAD:
                this.x2 = x2;
                this.y2 = y2;
            
            case MOVE:
            case LINE:
                this.x1 = x1;
                this.y1 = y1;
            
            case CLOSE:
            default:
                break;
        }
    }
    
    
    /**
     * <p>Sets the parameters appropriate for the current node
     * type using explicit float values.</p>
     *
     * <p>Does not set internal parameters that are unused for
     * the current node type.</p>
     *
     * <p>Since 4 parameters is insufficient for a CUBIC node type,
     * throws <code>IllegalArgumentException</code> in this case.</p>
     *
     * <p>Therefore, this method requires that the caller know
     * that the node type is suitable prior to calling the method.</p>
     *
     * @param x1 parameter 0
     * @param y1 parameter 1
     * @param x2 parameter 2
     * @param y2 parameter 3
     */
    public void setParameters
        (float x1, float y1, float x2, float y2)
    {
        switch (nodetype) {
            case CUBIC:
                throw new IllegalArgumentException
                    ("Too few arguments in PathNode.setParameters");
            
            case QUAD:
                this.x2 = x2;
                this.y2 = y2;
            
            case MOVE:
            case LINE:
                this.x1 = x1;
                this.y1 = y1;
            
            case CLOSE:
            default:
                break;
        }
    }
    
    
    /**
     * <p>Sets the parameters appropriate for the current node
     * type using explicit float values.</p>
     *
     * <p>Does not set internal parameters that are unused for
     * the current node type.</p>
     *
     * <p>Since 2 parameters is insufficient for a QUAD or CUBIC node type,
     * throws <code>IllegalArgumentException</code> in this case.</p>
     *
     * <p>Therefore, this method requires that the caller know
     * that the node type is suitable prior to calling the method.</p>
     *
     * @param x1 parameter 0
     * @param y1 parameter 1
     */
    public void setParameters
        (float x1, float y1)
    {
        switch (nodetype) {
            case CUBIC:
            case QUAD:
                throw new IllegalArgumentException
                    ("Too few arguments in PathNode.setParameters");
            
            case MOVE:
            case LINE:
                this.x1 = x1;
                this.y1 = y1;
            
            case CLOSE:
            default:
                break;
        }
    }
    
    
    /** Returns a copy of the current numeric data parameters. */
    public float[] getParameters() {
        return new float[] { x1, y1, x2, y2, x3, y3 };
    }
    
    
    /**
     * Returns the number of numeric data parameters needed by the
     * current node type.
     */
    public int getParameterCount() {
        int M = 0;
        
        switch (nodetype) {
            case MOVE:
            case LINE:
                M = 2; 
                break;
            
            case QUAD:
                M = 4;
                break;
            
            case CUBIC:
                M = 6;
                break;
            
            case CLOSE:
            default:
                break;
        }
        
        return M;
    }
    
    
    /**
     * Returns the number of x,y slots needed by the current node type.
     * This is half of the parameter count.
     */
    public int getSlotCount() {
        int M = 0;
        
        switch (nodetype) {
            case MOVE:
            case LINE:
                M = 1; 
                break;
            
            case QUAD:
                M = 2;
                break;
            
            case CUBIC:
                M = 3;
                break;
            
            case CLOSE:
            default:
                break;
        }
        
        return M;
    }
    
    
    /**
     * <p>Sets the internal parameter x1.</p>
     *
     * <p>This method does not check whether the cell is used
     * by the current node type.</p>
     */
    public final void setX1(float x1) {
        this.x1 = x1;
    }
    
    
    /**
     * <p>Sets the internal parameter y1.</p>
     *
     * <p>This method does not check whether the cell is used
     * by the current node type.</p>
     */
    public final void setY1(float y1) {
        this.y1 = y1;
    }
    
    
    /**
     * <p>Sets the internal parameter x2.</p>
     *
     * <p>This method does not check whether the cell is used
     * by the current node type.</p>
     */
    public final void setX2(float x2) {
        this.x2 = x2;
    }
    
    
    /**
     * <p>Sets the internal parameter y2.</p>
     *
     * <p>This method does not check whether the cell is used
     * by the current node type.</p>
     */
    public final void setY2(float y2) {
        this.y2 = y2;
    }
    
    
    /**
     * <p>Sets the internal parameter x3.</p>
     *
     * <p>This method does not check whether the cell is used
     * by the current node type.</p>
     */
    public final void setX3(float x3) {
        this.x3 = x3;
    }
    
    
    /**
     * <p>Sets the internal parameter y3.</p>
     *
     * <p>This method does not check whether the cell is used
     * by the current node type.</p>
     */
    public final void setY3(float y3) {
        this.y3 = y3;
    }
    
    
    /**
     * <p>Returns the internal parameter x1.</p>
     *
     * <p>This method does not check whether the cell is used
     * by the current node type.</p>
     */
    public final float getX1() {
        return x1;
    }
    
    
    /**
     * <p>Returns the internal parameter y1.</p>
     *
     * <p>This method does not check whether the cell is used
     * by the current node type.</p>
     */
    public final float getY1() {
        return y1;
    }
    
    
    /**
     * <p>Returns the internal parameter x2.</p>
     *
     * <p>This method does not check whether the cell is used
     * by the current node type.</p>
     */
    public final float getX2() {
        return x2;
    }
    
    
    /**
     * <p>Returns the internal parameter y2.</p>
     *
     * <p>This method does not check whether the cell is used
     * by the current node type.</p>
     */
    public final float getY2() {
        return y2;
    }
    
    
    /**
     * <p>Returns the internal parameter x3.</p>
     *
     * <p>This method does not check whether the cell is used
     * by the current node type.</p>
     */
    public final float getX3() {
        return x3;
    }
    
    
    /**
     * <p>Returns the internal parameter y3.</p>
     *
     * <p>This method does not check whether the cell is used
     * by the current node type.</p>
     */
    public final float getY3() {
        return y3;
    }
    
    
    /**
     * <p>Sets the slot with the given slot index to the given x,y
     * values.</p>
     *
     * <p>Requires <code>1 &lt;= index &lt;= getSlotCount()</code>
     * or does nothing.</p>
     *
     * @param slot the slot index 1, 2, 3
     * @param x the x-coordinate
     * @param y the y-coordinate
     */
    public final void setSlot(int slot, float x, float y) {
        if ((slot < 1) || (slot > getSlotCount()))
            return;
        
        switch (slot) {
            case 1:
                x1 = x;
                y1 = y;
                return;
            
            case 2:
                x2 = x;
                y2 = y;
                return;
            
            case 3:
                x3 = x;
                y3 = y;
                return;
            
            default:
                return;
        }
    }
    
    
    /**
     * <p>Sets the slot with the given slot index to the given x,y
     * pair array.</p>
     *
     * <p>Requires <code>1 &lt;= slot &lt;= getSlotCount()</code>
     * or does nothing.</p>
     *
     * <p>Also does nothing if pair is not <code>float[2]</code>.</p>
     *
     * @param slot the slot index 1, 2, 3
     * @param pair the float[2] array with x, y
     */
    public final void setSlot(int slot, float[] pair) {
        if ((pair == null) || (pair.length != 2))
            return;
        
        setSlot(slot, pair[0], pair[1]);
    }
    
    
    /**
     * <p>Returns an (x,y) pair of values corresponding to the given
     * slot index or <code>null</code> if the index is invalid.</p>
     *
     * <p>Requires <code>1 &lt;= slot &lt;= getSlotCount()</code>
     * to return a non-<code>null</code> value.</p>
     *
     * @param slot the slot index 1, 2, 3
     */
    public final float[] getSlot(int slot) {
        if ((slot < 1) || (slot > getSlotCount()))
            return null;
        
        switch (slot) {
            case 1:
                return new float[] { x1, y1 };
            
            case 2:
                return new float[] { x2, y2 };
            
            case 3:
                return new float[] { x3, y3 };
            
            default:
                return null;
        }
    }
    
    
    /**
     * <p>Returns the x-coordinate corresponding to the given
     * slot index or 0 if the index is invalid.</p>
     *
     * <p>Requires <code>1 &lt;= slot &lt;= getSlotCount()</code>
     * to return a valid value.</p>
     *
     * @param slot the slot index 1, 2, 3
     */
    public final float getSlotX(int slot) {
        if ((slot < 1) || (slot > getSlotCount()))
            return 0;
        
        switch (slot) {
            case 1:
                return x1;
            
            case 2:
                return x2;
            
            case 3:
                return x3;
            
            default:
                return 0;
        }
    }
    
    
    /**
     * <p>Returns the y-coordinate corresponding to the given
     * slot index or 0 if the index is invalid.</p>
     *
     * <p>Requires <code>1 &lt;= slot &lt;= getSlotCount()</code>
     * to return a valid value.</p>
     *
     * @param slot the slot index 1, 2, 3
     */
    public final float getSlotY(int slot) {
        if ((slot < 1) || (slot > getSlotCount()))
            return 0;
        
        switch (slot) {
            case 1:
                return y1;
            
            case 2:
                return y2;
            
            case 3:
                return y3;
            
            default:
                return 0;
        }
    }
    
    
    /**
     * <p>Searches the slots in this node and
     * returns a slot index if the given (x,y) is within epsilon
     * of the values in that slot;
     * returns -1 if no such slot exists.</p>
     *
     * <p>The metric Metric.MAX is used.</p>
     *
     * @param x the x-coordinate of the search point
     * @param y the y-coordinate of the search point
     * @param epsilon the measure of closeness
     */
    public final int nearSlot(double x, double y, double epsilon) {
        return nearSlot(x, y, epsilon, Metric.MAX);
    }
    
    
    /**
     * <p>Searches the valid slots in this node in order and
     * returns a slot index if the given (x,y) is within epsilon
     * of the values in that slot relative to the given metric;
     * returns -1 if no such slot exists.</p>
     *
     * <p>If the given metric is <code>null</code>
     * then <code>Metric.MAX</code> is used.</p>
     *
     * @param x the x-coordinate of the search point
     * @param y the y-coordinate of the search point
     * @param epsilon the measure of closeness
     * @param metric  the metric to do the distance computation
     */
    public final int nearSlot
        (double x, double y, double epsilon, Metric metric)
    {
        if (metric == null)
            metric = Metric.MAX;
        
        switch (nodetype) {
            case CUBIC:
                if (metric.isNear(x1, y1, x, y, epsilon))
                    return 1;
                if (metric.isNear(x2, y2, x, y, epsilon))
                    return 2;
                if (metric.isNear(x3, y3, x, y, epsilon))
                    return 3;
                break;
                
            case QUAD:
                if (metric.isNear(x1, y1, x, y, epsilon))
                    return 1;
                if (metric.isNear(x2, y2, x, y, epsilon))
                    return 2;
                break;
            
            case MOVE:
            case LINE:
                if (metric.isNear(x1, y1, x, y, epsilon))
                    return 1;
            
            default:
                break;
        }
        
        return -1;
    }
    
    
    /**
     * <p>Returns the node type as the return value and stores
     * the node numeric data in the supplied array which must
     * be non-<code>null</code> and of size at least 6.</p>
     *
     * <p>Throws <code>IllegalArgumentException</code> if the
     * supplied array does not meet the preconditions.</p>
     *
     * <p>Overwrites the existing contents of the first 6 cells
     * of the supplied array.</p>
     *
     * <p>This method is intended to provide the behavior for
     * a single node that the method <code>currentSegment</code>
     * provides for each successive node in the Java interface
     * <code>PathIterator</code>.</p>
     *
     * @param coords an array of size 6 to hold the node data
     * @throws IllegalArgumentException
     */
    public int nodeInfo(float[] coords) {
        if ((coords == null) || (coords.length < 6)) {
            String message = "Error in PathNode.nodeInfo:\n"
                + "Result array must have size at least 6\n";
            
            throw new IllegalArgumentException(message);
        }
        
        coords[0] = x1;
        coords[1] = y1;
        coords[2] = x2;
        coords[3] = y2;
        coords[4] = x3;
        coords[5] = y3;
        
        return nodetype;
    }
    
    
    /**
     * <p>Returns the node type as the return value and stores
     * the node numeric data in the supplied array which must
     * be non-<code>null</code> and of size at least 6.</p>
     *
     * <p>Throws <code>IllegalArgumentException</code> if the
     * supplied array does not meet the preconditions.</p>
     *
     * <p>Overwrites the existing contents of the first 6 cells
     * of the supplied array.</p>
     *
     * <p>This method is intended to provide the behavior for
     * a single node that the method <code>currentSegment</code>
     * provides for each successive node in the Java interface
     * <code>PathIterator</code>.</p>
     *
     * @param coords an array of size 6 to hold the node data
     * @throws IllegalArgumentException
     */
    public int nodeInfo(double[] coords) {
        if ((coords == null) || (coords.length < 6)) {
            String message = "Error in PathNode.nodeInfo:\n"
                + "Result array must have size at least 6\n";
            
            throw new IllegalArgumentException(message);
        }
        
        coords[0] = x1;
        coords[1] = y1;
        coords[2] = x2;
        coords[3] = y2;
        coords[4] = x3;
        coords[5] = y3;
        
        return nodetype;
    }
    
    
    /**
     * <p>If the node type is MOVE, changes it to LINE,
     * otherwise does nothing.</p>
     *
     * <p>This is a helper method that may permit one to
     * connect a node sequence to a node sequence.</p>
     */
    public void changeMoveToLine() {
        if (nodetype == MOVE)
            nodetype = LINE;
    }
    
    
    /**
     * <p>If the node type is LINE, changes it to MOVE,
     * otherwise does nothing.</p>
     *
     * <p>This is a helper method that may permit one to
     * disconnect a node sequence from a node sequence.</p>
     */
    public void changeLineToMove() {
        if (nodetype == LINE)
            nodetype = MOVE;
    }
    
    
    /**
     * <p>Transforms the internal data points using the given affine transform.</p>
     *
     * <p>Does nothing if the given affine transform is <code>null</code>.</p>
     *
     * @param T the affine transform
     */
    public final void transform(AffineTransform T) {
        if ((T == null) || (nodetype == CLOSE))
            return;
        
        float m00 = (float) T.getScaleX();
        float m01 = (float) T.getShearX();
        float m02 = (float) T.getTranslateX();
        
        float m10 = (float) T.getScaleY();
        float m11 = (float) T.getShearY();
        float m12 = (float) T.getTranslateY();
        
        float x0;
        float y0;
        
        switch (nodetype) {
            case CUBIC:
                x0 = x3;
                y0 = y3;
                x3 = m00 * x0 + m01 * y0 + m02;
                y3 = m10 * x0 + m11 * y0 + m12;
            
            case QUAD:
                x0 = x2;
                y0 = y2;
                x2 = m00 * x0 + m01 * y0 + m02;
                y2 = m10 * x0 + m11 * y0 + m12;
            
            case MOVE:
            case LINE:
                x0 = x1;
                y0 = y1;
                x1 = m00 * x0 + m01 * y0 + m02;
                y1 = m10 * x0 + m11 * y0 + m12;
            
            case CLOSE:
            default:
        }
    }
    
    
    /**
     * <p>Writes the data of this path node to a String.</p>
     *
     * <p>The format is one of the following:</p>
     *
     * <pre>MOVE[x1;y1]</pre>
     *
     * <pre>LINE[x1;y1]</pre>
     *
     * <pre>QUAD[x1;y1;x2;y2]</pre>
     *
     * <pre>CUBIC[x1;y1;x2;y2;x3;y3]</pre>
     *
     * <pre>CLOSE[]</pre>
     *
     * <p>where x1, y1, x2, y2, x3, y3 are floats.</p>
     */
    public String toString() {
        StringBuffer buffer = new StringBuffer();
        
        buffer.append(getNodeTypeString());
        
        buffer.append("[");
        
        switch (nodetype) {
            case CUBIC:
                buffer.append(Float.toString(x1));
                buffer.append(";");
                buffer.append(Float.toString(y1));
                buffer.append(";");
                buffer.append(Float.toString(x2));
                buffer.append(";");
                buffer.append(Float.toString(y2));
                buffer.append(";");
                buffer.append(Float.toString(x3));
                buffer.append(";");
                buffer.append(Float.toString(y3));
                break;
            
            case QUAD:
                buffer.append(Float.toString(x1));
                buffer.append(";");
                buffer.append(Float.toString(y1));
                buffer.append(";");
                buffer.append(Float.toString(x2));
                buffer.append(";");
                buffer.append(Float.toString(y2));
                break;
            
            case MOVE:
            case LINE:
                buffer.append(Float.toString(x1));
                buffer.append(";");
                buffer.append(Float.toString(y1));
                break;
            
            case CLOSE:
            default:
                break;
        }
        
        buffer.append("]");
        
        return buffer.toString();
    }
    
    
    /** Returns the same result as <code>toString()</code>. */
    public String toStringData() {
        return toString();
    }
    
    
    /**
     * <p>Sets the data of this path node using <code>String</code>
     * data in the format produced by <code>toString()</code>.</p>
     *
     * <p>For convenience, views an empty data String as equivalent
     * to the following default: <code>MOVE[0;0]</code>.</p>
     *
     * <p>Throws <code>ParseException</code> if the data is not in
     * the correct format or has numeric errors.</p>
     *
     * @param state the <code>String</code> with the state information
     * @throws ParseException
     */
    public void fromStringData(String state)
        throws ParseException
    {
        if (state == null) {
            String message = "PathNode error: null data\n";
            
            throw new ParseException(message, -1);
        }
        
        state = state.trim();
        
        if (state.length() == 0) {
            nodetype = MOVE;
            x1 = 0;
            y1 = 0;
            return;
        }
        
        double[] doubles = null;
        
        String[] split = Strings.splitIn2(state);
        
        int type = getNodeTypeFromStringData(split[0]);
        
        String[] numbers = Strings.decode(split[1]);
        
        if (numbers != null) {
            try {
                doubles = Strings.stringsToDoubles(numbers);
            }
            catch (ParseException ex) {
                String message =
                    "PathNode.fromStringData numeric error:\n"
                    + ex.getMessage() + "\n"
                    + standardMessage;
                
                throw new ParseException(message, -1);
            }
        }
        else {
            doubles = new double[0];
        }
        
        int N = doubles.length;

        boolean ok = true;
        
        switch (type) {
            case CUBIC:
                ok = (N >= 6);
                break;
            
            case QUAD:
                ok = (N >= 4);
                break;
            
            case MOVE:
            case LINE:
                ok = (N >= 2);
                break;
            
            case CLOSE:
            default:
                break;
        }
        
        if (! ok) {
            String message =
                "PathNode.fromStringData numeric error:\n"
                + "Insufficient numeric parameters for node type\n"
                + state + "\n"
                + standardMessage;
            
            throw new ParseException(message, -1);
        }
        
        // change this object if no parse exceptions
        
        setNodeType(type);
        setParameters(doubles);
    }
    
    
    /** Returns a String label corresponding to the node type. */
    public String getNodeTypeString() {
        switch (nodetype) {
            case MOVE:
                return "MOVE";
                
            case LINE:
                return "LINE";
                
            case QUAD:
                return "QUAD";
                
            case CUBIC:
                return "CUBIC";
                
            case CLOSE:
                return "CLOSE";
            
            default:
                return "";
        }
        
    }
    
    
    /**
     * <p>Returns the node type constant that corresponds to the
     * given <code>String</code> data.</p>
     *
     * <p>The valid input data is "MOVE", "LINE", "QUAD", "CUBIC",
     * and "CLOSE".</p>
     *
     * <p>Throws <code>ParseException</code> if the node type
     * <code>String</code> does not match any node type.</p>
     *
     * @param data the <code>String</code> with the node type
     * @throws ParseException
     */
    public int getNodeTypeFromStringData(String data)
        throws ParseException
    {
        if (data == null) {
            String message = "PathNode type error: null data\n";
            
            throw new ParseException(message, -1);
        }
        
        data = data.trim().toUpperCase();
        
        if (data.equals("MOVE"))
            return MOVE;
        
        if (data.equals("LINE"))
            return LINE;
        
        if (data.equals("QUAD"))
            return QUAD;
        
        if (data.equals("CUBIC"))
            return CUBIC;
        
        if (data.equals("CLOSE"))
            return CLOSE;
        
        String message =
            "PathNode type error: " + data + "\n" + standardMessage;
        
        throw new ParseException(message, -1);
    }
    
    
    /**
     * <p>Returns an array of <code>PathNode</code> objects that
     * corresponds to the information in the given iterator.</p>
     *
     * <p>The method must execute the iteration so when it is
     * complete then <code>iterator.isDone()</code> will be true.</p>
     *
     * <p>Returns an empty array if the given iterator is
     * <code>null</code>.</p>
     *
     * @param iterator the iterator whose data is to be extracted
     */
    public static PathNode[] getPathNodes(PathIterator iterator) {
        if (iterator == null)
            return new PathNode[0];
        
        Vector list = new Vector();
        
        int type;
        float[] data = new float[6];
        
        while (! iterator.isDone()) {
            type = iterator.currentSegment(data);
            
            list.add(new PathNode(type, data));
            
            iterator.next();
        }
        
        PathNode[] result = (PathNode[]) list.toArray(new PathNode[0]);
        
        return result;
    }
    
    
    /**
     * <p>Returns an array of <code>PathNode</code> objects that
     * corresponds to the information in the given shape.</p>
     *
     * <p>Returns an empty array if the given shape is
     * <code>null</code>.</p>
     *
     * @param shape the shape whose data is to be extracted
     */
    public static PathNode[] getPathNodes(Shape shape) {
        if (shape == null)
            return new PathNode[0];
        
        return getPathNodes(shape.getPathIterator(null));
    }
    
    
    /**
     * <p>Returns an array of <code>PathNode</code> objects that
     * corresponds to the information in the given shape after
     * being transformed by the given transform.</p>
     *
     * <p>Returns an empty array if the given shape is
     * <code>null</code>.</p>
     *
     * @param shape the shape whose data is to be extracted
     * @param transform the transform to apply to the shape data
     */
    public static PathNode[] getPathNodes
        (Shape shape, AffineTransform transform)
    {
        if (shape == null)
            return new PathNode[0];
        
        return getPathNodes(shape.getPathIterator(transform));
    }
    
    
    /**
     * <p>Returns an array of <code>PathNode</code> objects that
     * corresponds to the information in the given shape after
     * being transformed by the given transform and appying
     * the given flatness parameter.</p>
     *
     * <p>Returns an empty array if the given shape is
     * <code>null</code>.</p>
     *
     * @param shape the shape whose data is to be extracted
     * @param transform the transform to apply to the shape data
     * @param flatness the flatness to require
     */
    public static PathNode[] getPathNodes
        (Shape shape, AffineTransform transform, double flatness)
    {
        if (shape == null)
            return new PathNode[0];
        
        return getPathNodes(shape.getPathIterator(transform, flatness));
    }
    
}

