/*
 * @(#)PointPaintable.java    2.4.0   29 August 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 javax.swing.*;

/**
 * <p>A <code>PointPaintable</code> creates a <code>Paintable</code>
 * using a point, a <code>PlotMark</code>, and a <code>Paint</code>.</p>
 *
 * <p>Intuitively, the effect of painting the plotmark at the origin
 * using the paint is translated from the origin to the point.</p>
 *
 * <p>More precisely, the idea is that the point defines the mutator
 * for the object as a translation whose whose x,y coefficients are
 * those of the point.  In fact, in this class, the mutator must be
 * a translation.  When the method <code>setPoint</code> is called,
 * the mutator is set at the same time to be the corresponding
 * translation.</p>
 *
 * <p>In addition, when the method <code>setMutator</code> is called,
 * the linear part of the given mutator is ignored and the object
 * mutator is set to be the translation portion of the given mutator.
 * At the same time, the point associated with this object is set.</p>
 *
 * <p>The behaviors of the methods <code>setPreMutation</code> and
 * <code>setPostMutation</code> are then determined by the above rules.</p>
 *
 * <ul>
 *   <li>For <code>setPreMutation</code>, the translation component
 *       of the given mutator is composed with the translation
 *       corresponding to the current point.</li>
 *   <li>For <code>setPostMutation</code>, the given mutator is
 *       applied as an affine transform to the current point to
 *       obtain the new point. In particular, a succession of
 *       post mutation operations is equivalent to applying a
 *       succession of affine transforms to the current point.
 *       From a mathematical standpoint, this behavior is quite
 *       natural.</li>
 * </ul>
 *
 * <p>Note that, if the plotmark is defined by a paintable, then
 * the property changes of the internal paintable will be sent to
 * the forwarding listener of the point paintable.</p>
 *
 * @author  Richard Rasala
 * @version 2.4.0
 * @since   2.4.0
 */
public class PointPaintable extends AbstractPaintable
{
    /** The origin for use in originalPaint. */
    private static final XPoint2D ORIGIN = new XPoint2D();
    
    /** Bound property name for set plotmark. */
    public static final String SET_PLOTMARK = "set.plotmark";
    
    /** Bound property name for set paint. */
    public static final String SET_PAINT    = "set.paint";
    
    
    /** The internal point for this <code>PointPaintable</code>. */
    private XPoint2D point = new XPoint2D();
    
    /** The internal plotmark for this <code>PointPaintable</code>. */
    private PlotMark plotmark = new PlotMark();
    
    /** The internal paint for this <code>PointPaintable</code>. */
    private Paint paint = Color.black;
    
    
    /**
     * <p>The default constructor.</p>
     *
     * <p>Sets the following defaults:</p>
     *
     * <ul>
     *   <li><pre>Point2D  The origin (0,0)</pre></li>
     *   <li><pre>Plotmark The default plotmark</pre></li>
     *   <li><pre>Paint    Color.black</pre></li>
     * </ul>
     *
     */
    public PointPaintable() {
    
    }
    
    
    /**
     * <p>Constructor that sets the point from the given data and sets the
     * plotmark and the paint.</p>
     *
     * <p>Parameters that are <code>null</code> revert to the defaults
     * listed in the default constructor.</p>
     *
     * @param x the x-coordinate of the point
     * @param y the y-coordinate of the point
     * @param pm the plotmark to set
     * @param paint the paint to set
     */
    public PointPaintable(double x, double y, PlotMark pm, Paint paint) {
        setPoint(x, y);
        setPlotMark(pm);
        setPaint(paint);
    }
    
    
    /**
     * <p>Constructor that sets the point from the given data and sets the
     * plotmark and the paint.</p>
     *
     * <p>Parameters that are <code>null</code> revert to the defaults
     * listed in the default constructor.</p>
     *
     * @param p the point whose data will set the internal point
     * @param pm the plotmark to set
     * @param paint the paint to set
     */
    public PointPaintable(Point2D p, PlotMark pm, Paint paint) {
        setPoint(p);
        setPlotMark(pm);
        setPaint(paint);
    }
    
    
    /**
     * <p>Constructor that sets the point from the given data and sets the
     * plotmark and the paint.</p>
     *
     * <p>Parameters that are <code>null</code> revert to the defaults
     * listed in the default constructor.</p>
     *
     * @param data the array of size 2 with the x,y data
     * @param pm the plotmark to set
     * @param paint the paint to set
     */
    public PointPaintable(double[] data, PlotMark pm, Paint paint) {
        setPoint(data);
        setPlotMark(pm);
        setPaint(paint);
    }
    
    
    /**
     * <p>Constructor that sets the point from the given data and sets the
     * plotmark and the paint.</p>
     *
     * <p>Parameters that are <code>null</code> revert to the defaults
     * listed in the default constructor.</p>
     *
     * @param data the array of size 2 with the x,y data
     * @param pm the plotmark to set
     * @param paint the paint to set
     */
    public PointPaintable(float[] data, PlotMark pm, Paint paint) {
        setPoint(data);
        setPlotMark(pm);
        setPaint(paint);
    }
    
    
    /**
     * <P>Paints onto a <CODE>Graphics</CODE> context.</P>
     *
     * <p>Sets the paint of the graphics context to the internal paint
     * and then uses the plotmark to place a mark at the origin, that
     * is, at (0,0).</p>
     *
     * <P>When the method call is complete, the internal state of g is
     * unchanged.</P>
     * 
     * @param g the graphics context on which to paint
     */
    public void originalPaint(Graphics g) {
        Graphics2D h = (Graphics2D) g;
        
        Paint oldpaint = h.getPaint();
        h.setPaint(paint);
        
        plotmark.mark(h, ORIGIN);
        
        h.setPaint(oldpaint);
    }
    
    
    /**
     * <p>Returns the actual bounds of the original paintable or
     * <code>null</code> if the paintable is effectively empty.</p>
     *
     * <p>If the plotmark is defined by a paintable,
     * then the actual bounds of that paintable is returned.</p>
     *
     * <p>Otherwise, if the plotmark is defined by an algorithm
     * and a size, then the bounds of the shape
     * <code>algorithm.makeShape(0, 0, size)</code> is returned
     * or <code>null</code> if that shape is <code>null</code>.</p>
     */
    public XRect getActualBounds2D() {
        Paintable paintable = plotmark.getPaintable();
        
        if (paintable != null)
            return paintable.getActualBounds2D();
        
        PlotMarkAlgorithm algorithm = plotmark.getAlgorithm();
        int size = plotmark.getSize();
        
        Shape shape = algorithm.makeShape(0, 0, size);
        
        if (shape == null)
            return null;
        
        return new XRect(shape.getBounds2D());
    }
    
    
    /**
     * <P>Returns a copy of the original center of the paint region.</P>
     *
     * <P>The method is implemented as follows.</p>
     *
     * <p>If the value of <code>getDefaultOriginalCenter</code>
     * is non-<code>null</code>, then this center is returned.</p>
     *
     * <p>Otherwise, if the plotmark is defined by a paintable,
     * then the original center of that paintable is returned.</p>
     *
     * <p>Otherwise, a copy of the origin is returned, that is,
     * <code>new XPoint2D()</code>.</p>
     *
     * @return a copy of the center of the paint region
     */
    public XPoint2D getOriginalCenter() {
        XPoint2D center = getDefaultOriginalCenter();
        
        if (center != null)
            return center;
        
        Paintable paintable = plotmark.getPaintable();
        
        if (paintable != null)
            return paintable.getOriginalCenter();
        
        return new XPoint2D();
    }
    
    
    /**
     * <p>Tests if a point specified by coordinates is inside the
     * original paintable without mutation.</p>
     *
     * @param  x the x-coordinate of the point
     * @param  y the y-coordinate of the point
     * @return whether or not a specified point is inside the
     *         original paintable
     */
    public boolean originalContains(double x, double y) {
        Paintable paintable = plotmark.getPaintable();
        
        if (paintable != null)
            return paintable.originalContains(x, y);
        
        PlotMarkAlgorithm algorithm = plotmark.getAlgorithm();
        int size = plotmark.getSize();
        
        Shape shape = algorithm.makeShape(0, 0, size);
        
        if (shape == null)
            return false;
        
        return shape.contains(x, y);
    }
    
    
    /**
     * <p>Sets the internal point using the given data.</p>
     * 
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * @param x the x-coordinate of the point
     * @param y the y-coordinate of the point
     */
    public void setPoint(double x, double y) {
        point.setValue(x, y);
        
        super.setMutator((AffineTransform) TransformFactory.translate(x, y));
    }
    
    
    /**
     * <p>Sets the internal point using the given data.</p>
     * 
     * <p>Does nothing if the given point data is <code>null</code>.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * @param p the point whose data will set the internal point
     */
    public void setPoint(Point2D p) {
        if (p != null)
            setPoint(p.getX(), p.getY());
    }
    
    
    /**
     * <p>Sets the internal point using the given data.</p>
     * 
     * <p>Does nothing if the given array data is <code>null</code>.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * @param data the array of size 2 with the x,y data
     */
    public void setPoint(double[] data) {
        if (data != null)
            if (data.length == 2)
                setPoint(data[0], data[1]);
    }
    
    
    /**
     * <p>Sets the internal point using the given data.</p>
     * 
     * <p>Does nothing if the given array data is <code>null</code>.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * @param data the array of size 2 with the x,y data
     */
    public void setPoint(float[] data) {
        if (data != null)
            if (data.length == 2)
                setPoint(data[0], data[1]);
    }
    
    
    /**
     * <p>Sets the internal plotmark using the given data.</p>
     *
     * <p>Does nothing if the given data is <code>null</code>.</p>
     *
     * <p>Fires property change: SET_PLOTMARK.</p>
     * 
     * <p>For convenience, if the plotmark happens to be defined by a
     * paintable, then property changes from that internal paintable
     * will be sent to the forwarding listener of the point paintable.</p>
     *
     * @param pm the plotmark to set
     */
    public void setPlotMark(PlotMark pm) {
        if ((pm != null) && (pm != plotmark))
        {
            Paintable paintable = plotmark.getPaintable();
            
            if (paintable != null)
                removeForwardingListener(paintable);
        
            plotmark = pm;
            
            paintable = plotmark.getPaintable();
            
            if (paintable != null)
                addForwardingListener(paintable);
        
            firePropertyChange(SET_PLOTMARK, null, null);
        }
    }
    
    
    /**
     * <p>Sets the internal plotmark using the given data to call
     * the corresponding <code>PlotMark</code> constructor.</p>
     * 
     * <p>Does nothing if the given algorithm data is
     * <code>null</code>.</p>
     *
     * <p>Fires property change: SET_PLOTMARK.</p>
     * 
     * @param algorithm the plot mark algorithm
     * @param size the plot mark size setting
     * @param fill the plot mark fill setting
     */
    public void setPlotMark(PlotMarkAlgorithm algorithm, int size, boolean fill) {
        if (algorithm != null)
            setPlotMark(new PlotMark(algorithm, size, fill));
    }
    
    
    /**
     * <p>Sets the internal plotmark using the given data to call
     * the corresponding <code>PlotMark</code> constructor.</p>
     * 
     * <p>Does nothing if the given paintable data is
     * <code>null</code>.</p>
     *
     * <p>Fires property change: SET_PLOTMARK.</p>
     * 
     * <p>For convenience, if the plotmark happens to be defined by a
     * paintable, then property changes from that internal paintable
     * will be sent to the forwarding listener of the point paintable.</p>
     *
     * @param paintable the paintable to define the internal plotmark
     */
    public void setPlotMark(Paintable paintable) {
        if (paintable != null)
            setPlotMark(new PlotMark(paintable));
    }
    
    
    /**
     * <p>Sets the paint to the given paint.</p>
     *
     * <p>Does nothing if the given paint is <code>null</code>.</p>
     *
     * <p>Fires property change: SET_PAINT.</p>
     * 
     * @param paint the paint to set
     */
    public void setPaint(Paint paint) {
        if (paint != null)
            this.paint = paint;
    }
    
    
    /** Returns a copy of the current point of this object. */
    public XPoint2D getPoint() { return new XPoint2D(point); }
    
    
    /** Returns the current plotmark of this object. */
    public PlotMark getPlotMark() { return plotmark; }
    
    
    /** Returns the current paint of this object. */
    public Paint getPaint() { return paint; }
    
    
    /**
     * <p>The linear part of the given mutator is ignored and the object
     * mutator is set to the translation portion of the given mutator.
     * At the same time, the point associated with this object is set.</p>
     *
     * @param M the transform whose translation will set the mutator
     */
    public void setMutator(AffineTransform M) {
        if (M == null)
            return;
        
        double x = M.getTranslateX();
        double y = M.getTranslateY();
        
        setPoint(x, y);
    }
    
    
    /** Calls the super method. */
    public void setMutator(Mutator.Strategy strategy) {
        super.setMutator(strategy);
    }
    
    
    /**
     * <p>Factory to make an array of point paintable objects from the
     * given point array data and from the given plotmark and paint.</p>
     *
     * <p>Returns <code>null</code>
     * if the given array of points is <code>null</code> or
     * if any point in the array is <code>null</code>.</p>
     *
     * @param points the point array data
     * @param pm the plotmark to set
     * @param paint the paint to set
     */
    public static PointPaintable[] makePointPaintableArray
        (Point2D[] points, PlotMark pm, Paint paint)
    {
        if (points == null)
            return null;
        
        int N = points.length;
        
        for (int i = 0; i < N; i++)
            if (points[i] == null)
                return  null;
        
        PointPaintable[] result = new PointPaintable[N];
        
        for (int i = 0; i < N; i++)
            result[i] = new PointPaintable(points[i], pm, paint);
        
        return result;
    }
    
    
    /**
     * <p>Factory to make an array of point paintable objects from the
     * given point array data and from the given plotmark and paint.</p>
     *
     * <P>Precondition:</P>
     * <UL>
     *   <LI>For some integer N, the given points array is
     *          double[N][2] with non-<CODE>null</CODE> entries.</LI>
     * </UL>
     * 
     * <P>Returns <code>null</code> if the precondition fails.</P>
     *
     * @param points the point array data
     * @param pm the plotmark to set
     * @param paint the paint to set
     */
    public static PointPaintable[] makePointPaintableArray
        (double[][] points, PlotMark pm, Paint paint)
    {
        if (points == null)
            return null;
        
        int N = points.length;
        
        for (int i = 0; i < N; i++) {
            if (points[i] == null)
                return null;
            
            if (points[i].length != 2)
                return null;
        }
        
        PointPaintable[] result = new PointPaintable[N];
        
        for (int i = 0; i < N; i++)
            result[i] = new PointPaintable(points[i], pm, paint);
        
        return result;
    }
    
    
    /**
     * <p>Factory to make an array of point paintable objects from the
     * given point array data and from the given plotmark and paint.</p>
     *
     * <P>Precondition:</P>
     * <UL>
     *   <LI>For some integer N, the given points array is
     *          float[N][2] with non-<CODE>null</CODE> entries.</LI>
     * </UL>
     * 
     * <P>Returns <code>null</code> if the precondition fails.</P>
     *
     * @param points the point array data
     * @param pm the plotmark to set
     * @param paint the paint to set
     */
    public static PointPaintable[] makePointPaintableArray
        (float[][] points, PlotMark pm, Paint paint)
    {
        if (points == null)
            return null;
        
        int N = points.length;
        
        for (int i = 0; i < N; i++) {
            if (points[i] == null)
                return null;
            
            if (points[i].length != 2)
                return null;
        }
        
        PointPaintable[] result = new PointPaintable[N];
        
        for (int i = 0; i < N; i++)
            result[i] = new PointPaintable(points[i], pm, paint);
        
        return result;
    }
    
    
    /**
     * <p>Factory to make a <code>PaintableSequence</code> from the
     * given point array data and from the given plotmark and paint.</p>
     *
     * <p>Returns <code>null</code>
     * if the given array of points is <code>null</code> or
     * if any point in the array is <code>null</code>.</p>
     *
     * @param points the point array data
     * @param pm the plotmark to set
     * @param paint the paint to set
     */
    public static PaintableSequence makePaintableSequence
        (Point2D[] points, PlotMark pm, Paint paint)
    {
        PointPaintable[] array =
            makePointPaintableArray(points, pm, paint);
        
        if (array ==  null)
            return null;
        
        return new PaintableSequence(array);
    }
    
    
    /**
     * <p>Factory to make a <code>PaintableSequence</code> from the
     * given point array data and from the given plotmark and paint.</p>
     *
     * <P>Precondition:</P>
     * <UL>
     *   <LI>For some integer N, the given points array is
     *          double[N][2] with non-<CODE>null</CODE> entries.</LI>
     * </UL>
     * 
     * <P>Returns <code>null</code> if the precondition fails.</P>
     *
     * @param points the point array data
     * @param pm the plotmark to set
     * @param paint the paint to set
     */
    public static PaintableSequence makePaintableSequence
        (double[][] points, PlotMark pm, Paint paint)
    {
        PointPaintable[] array =
            makePointPaintableArray(points, pm, paint);
        
        if (array ==  null)
            return null;
        
        return new PaintableSequence(array);
    }
    
    
    /**
     * <p>Factory to make a <code>PaintableSequence</code> from the
     * given point array data and from the given plotmark and paint.</p>
     *
     * <P>Precondition:</P>
     * <UL>
     *   <LI>For some integer N, the given points array is
     *          float[N][2] with non-<CODE>null</CODE> entries.</LI>
     * </UL>
     * 
     * <P>Returns <code>null</code> if the precondition fails.</P>
     *
     * @param points the point array data
     * @param pm the plotmark to set
     * @param paint the paint to set
     */
    public static PaintableSequence makePaintableSequence
        (float[][] points, PlotMark pm, Paint paint)
    {
        PointPaintable[] array =
            makePointPaintableArray(points, pm, paint);
        
        if (array ==  null)
            return null;
        
        return new PaintableSequence(array);
    }
    
}
