/*
 * @(#)PlotMark.java    2.4.0   17 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 java.awt.*;
import java.awt.geom.*;
import java.io.*;

/**
 * <p>Class <code>PlotMark</code> is a class for drawing a geometric
 * shape or mark at a specified point in a graphics context or for
 * painting a <code>Paintable</code> at such a point.</p>
 *
 * <p>In 2.4.0, the class was completely refactored to use the class
 * <code>PlotMarkAlgorithm</code> so that it is possible for the
 * user to define a new plot mark.</code>
 *
 * <p>In 2.4.0, the alternative to use a <code>Paintable</code> as a
 * mark was also introduced.</p>
 *
 * <p>When a plot mark is defined via a plot mark algorithm then the
 * paint used for drawing is that determined by the graphics context.
 * When a plot mark is defined by a paintable, then the paintable
 * controls the entire drawing process.</p>
 *
 * <p>In 2.4.0, it was decided to remove options that would permit a
 * <code>PlotMark</code> to be modified after construction. In other
 * words, a <code>PlotMark</code> is immutable.  Instead, there are
 * now constructors available that permit a <code>PlotMark</code> to
 * be fully or partially cloned.</p>
 *
 * <p>This class provides several static instances of itself. The
 * constant names are chosen to promote compatibility with earlier
 * versions of the class although the actual objects referenced by
 * these constants are quite different.</p>
 *
 * @author  Richard Rasala
 * @version 2.4.0
 * @since   1.0
 */
public class PlotMark 
{
    /** The horizontal bar plot mark. */
    public static final PlotMark H_BAR =
        new PlotMark(PlotMarkAlgorithm.HBar, false);
    
    /** The vertical bar plot mark. */
    public static final PlotMark V_BAR =
        new PlotMark(PlotMarkAlgorithm.VBar, false);
    
    /** The plus plot mark. */
    public static final PlotMark PLUS =
        new PlotMark(PlotMarkAlgorithm.Plus, false);
    
    /** The cross plot mark. */
    public static final PlotMark CROSS =
        new PlotMark(PlotMarkAlgorithm.Cross, false);
    
    /** The asterisk plot mark. */
    public static final PlotMark ASTERISK =
        new PlotMark(PlotMarkAlgorithm.Asterisk, false);
    

    /** The square plot mark. */
    public static final PlotMark SQUARE =
        new PlotMark(PlotMarkAlgorithm.Square, false);
    
    /** The circle plot mark. */
    public static final PlotMark CIRCLE =
        new PlotMark(PlotMarkAlgorithm.Circle, false);
    
    /** The diamond plot mark. */
    public static final PlotMark DIAMOND =
        new PlotMark(PlotMarkAlgorithm.Diamond, false);
    
    
    /** The wedge-facing-north plot mark. */
    public static final PlotMark WEDGE_N =
        new PlotMark(PlotMarkAlgorithm.WedgeN, false);
    
    /** The wedge-facing-east plot mark. */
    public static final PlotMark WEDGE_E =
        new PlotMark(PlotMarkAlgorithm.WedgeE, false);
    
    /** The wedge-facing-south plot mark. */
    public static final PlotMark WEDGE_S =
        new PlotMark(PlotMarkAlgorithm.WedgeS, false);
    
    /** The wedge-facing-west plot mark. */
    public static final PlotMark WEDGE_W =
        new PlotMark(PlotMarkAlgorithm.WedgeW, false);
    
    
    /** The blunt-wedge-facing-north plot mark. */
    public static final PlotMark BLUNT_WEDGE_N =
        new PlotMark(PlotMarkAlgorithm.BluntWedgeN, false);
    
    /** The blunt-wedge-facing-east plot mark. */
    public static final PlotMark BLUNT_WEDGE_E =
        new PlotMark(PlotMarkAlgorithm.BluntWedgeE, false);
    
    /** The blunt-wedge-facing-south plot mark. */
    public static final PlotMark BLUNT_WEDGE_S =
        new PlotMark(PlotMarkAlgorithm.BluntWedgeS, false);
    
    /** The blunt-wedge-facing-west plot mark. */
    public static final PlotMark BLUNT_WEDGE_W =
        new PlotMark(PlotMarkAlgorithm.BluntWedgeW, false);
    
    
    /** The sharp-wedge-facing-north plot mark. */
    public static final PlotMark SHARP_WEDGE_N =
        new PlotMark(PlotMarkAlgorithm.SharpWedgeN, false);
    
    /** The sharp-wedge-facing-east plot mark. */
    public static final PlotMark SHARP_WEDGE_E =
        new PlotMark(PlotMarkAlgorithm.SharpWedgeE, false);
    
    /** The sharp-wedge-facing-south plot mark. */
    public static final PlotMark SHARP_WEDGE_S =
        new PlotMark(PlotMarkAlgorithm.SharpWedgeS, false);
    
    /** The sharp-wedge-facing-west plot mark. */
    public static final PlotMark SHARP_WEDGE_W =
        new PlotMark(PlotMarkAlgorithm.SharpWedgeW, false);
    
    
    /** The filled square plot mark. */
    public static final PlotMark FILLED_SQUARE =
        new PlotMark(PlotMarkAlgorithm.Square, true);
    
    /** The filled circle plot mark. */
    public static final PlotMark FILLED_CIRCLE =
        new PlotMark(PlotMarkAlgorithm.Circle, true);
    
    /** The filled diamond plot mark. */
    public static final PlotMark FILLED_DIAMOND =
        new PlotMark(PlotMarkAlgorithm.Diamond, true);
    
    
    /** The filled wedge-facing-north plot mark. */
    public static final PlotMark FILLED_WEDGE_N =
        new PlotMark(PlotMarkAlgorithm.WedgeN, true);
    
    /** The filled wedge-facing-east plot mark. */
    public static final PlotMark FILLED_WEDGE_E =
        new PlotMark(PlotMarkAlgorithm.WedgeE, true);
    
    /** The filled wedge-facing-south plot mark. */
    public static final PlotMark FILLED_WEDGE_S =
        new PlotMark(PlotMarkAlgorithm.WedgeS, true);
    
    /** The filled wedge-facing-west plot mark. */
    public static final PlotMark FILLED_WEDGE_W =
        new PlotMark(PlotMarkAlgorithm.WedgeW, true);
    
    
    /** The filled blunt-wedge-facing-north plot mark. */
    public static final PlotMark FILLED_BLUNT_WEDGE_N =
        new PlotMark(PlotMarkAlgorithm.BluntWedgeN, true);
    
    /** The filled blunt-wedge-facing-east plot mark. */
    public static final PlotMark FILLED_BLUNT_WEDGE_E =
        new PlotMark(PlotMarkAlgorithm.BluntWedgeE, true);
    
    /** The filled blunt-wedge-facing-south plot mark. */
    public static final PlotMark FILLED_BLUNT_WEDGE_S =
        new PlotMark(PlotMarkAlgorithm.BluntWedgeS, true);
    
    /** The filled blunt-wedge-facing-west plot mark. */
    public static final PlotMark FILLED_BLUNT_WEDGE_W =
        new PlotMark(PlotMarkAlgorithm.BluntWedgeW, true);
    
    
    /** The filled sharp-wedge-facing-north plot mark. */
    public static final PlotMark FILLED_SHARP_WEDGE_N =
        new PlotMark(PlotMarkAlgorithm.SharpWedgeN, true);
    
    /** The filled sharp-wedge-facing-east plot mark. */
    public static final PlotMark FILLED_SHARP_WEDGE_E =
        new PlotMark(PlotMarkAlgorithm.SharpWedgeE, true);
    
    /** The filled sharp-wedge-facing-south plot mark. */
    public static final PlotMark FILLED_SHARP_WEDGE_S =
        new PlotMark(PlotMarkAlgorithm.SharpWedgeS, true);
    
    /** The filled sharp-wedge-facing-west plot mark. */
    public static final PlotMark FILLED_SHARP_WEDGE_W =
        new PlotMark(PlotMarkAlgorithm.SharpWedgeW, true);
    
    
    // private constants
    
    /** Default plot mark algorithm. */
    private static final PlotMarkAlgorithm DEFAULT_ALGORITHM =
        PlotMarkAlgorithm.Square;
    
    /** Default plot mark size. */
    private static final int DEFAULT_SIZE = 3;

    /** Default plot mark fill. */
    private static final boolean DEFAULT_FILL = true;
    
    /** The standard stroke. */
    private static final Stroke standardStroke = new BasicStroke(1);
    
    
    // member data
    
    /** The plot mark algorithm. */
    protected PlotMarkAlgorithm algorithm = DEFAULT_ALGORITHM;
    
    /** The plot mark size. */
    protected int size = DEFAULT_SIZE;
    
    /** The plot mark fill. */
    protected boolean fill = DEFAULT_FILL;
    
    /** The paintable if used instead of an algorithm. */
    protected Paintable paintable = null;
    

    //////////////////
    // Constructors //
    //////////////////
    
    /**
     * <p>Constructs a plot mark using the defaults.</p>
     *
     * <ul>
     *   <li><pre>Algorithm: PlotMarkAlgorithm.Square</pre></li>
     *   <li><pre>Size:      3</pre></li>
     *   <li><pre>Fill:      true</pre></li>
     * </ul>
     *
     * <p>The default plot mark is a copy of the static constant
     * plot mark <code>FILLED_SQUARE</code>.</p>
     */
    public PlotMark() {}
    
    
    /**
     * <p>Constructs a plot mark using the given algorithm and
     * the defaults listed below.</p>
     *
     * <ul>
     *   <li><pre>Size:      3</pre></li>
     *   <li><pre>Fill:      true</pre></li>
     * </ul>
     *
     * <p>If the algorithm is <code>null</code>, it is set to
     * <code>PlotMarkAlgorithm.Square</code>.</p>
     *
     * @param algorithm the plot mark algorithm
     */
    public PlotMark(PlotMarkAlgorithm algorithm) { 
        initializePlotMark(
            algorithm,
            DEFAULT_SIZE,
            DEFAULT_FILL,
            null);
    }
    
    
    /**
     * <p>Constructs a plot mark using the given algorithm, the
     * given size, and the default listed below.</p>
     *
     * <ul>
     *   <li><pre>Fill:      true</pre></li>
     * </ul>
     *
     * <p>If the algorithm is <code>null</code>, it is set to
     * <code>PlotMarkAlgorithm.Square</code>.</p>
     *
     * <p>If the size is less than 1, it is set to 1.</p>
     *
     * @param algorithm the plot mark algorithm
     * @param size the plot mark size setting
     */
    public PlotMark(PlotMarkAlgorithm algorithm, int size) { 
        initializePlotMark(
            algorithm,
            size,
            DEFAULT_FILL,
            null);
    }
    
    
    
    /**
     * <p>Constructs a plot mark using the given algorithm, the
     * given fill setting, and the default listed below.</p>
     *
     * <ul>
     *   <li><pre>Size:      3</pre></li>
     * </ul>
     *
     * <p>If the algorithm is <code>null</code>, it is set to
     * <code>PlotMarkAlgorithm.Square</code>.</p>
     *
     * @param algorithm the plot mark algorithm
     * @param fill the plot mark fill setting
     */
    public PlotMark(PlotMarkAlgorithm algorithm, boolean fill) { 
        initializePlotMark(
            algorithm,
            DEFAULT_SIZE,
            fill,
            null);
    }
    
    
    
    /**
     * <p>Constructs a plot mark using the given algorithm, the
     * given size, and the given fill setting.</p>
     *
     * <p>If the algorithm is <code>null</code>, it is set to
     * <code>PlotMarkAlgorithm.Square</code>.</p>
     *
     * <p>If the size is less than 1, it is set to 1.</p>
     *
     * @param algorithm the plot mark algorithm
     * @param size the plot mark size setting
     * @param fill the plot mark fill setting
     */
    public PlotMark(PlotMarkAlgorithm algorithm, int size, boolean fill) { 
        initializePlotMark(
            algorithm,
            size,
            fill,
            null);
    }
    
    
    /**
     * <p>Constructs a plot mark by cloning the given plot mark.</p>
     *
     * <p>If the plot mark is <code>null</code>, it is set to
     * <code>PlotMark.FILLED_SQUARE</code>.</p>
     *
     * <p>If the plot mark is set by a paintable then the new
     * plot mark is set by the same paintable.</p>
     *
     * @param plotmark the plot mark to clone
     */
    public PlotMark(PlotMark plotmark) { 
        if (plotmark == null)
            plotmark = FILLED_SQUARE;
        
        initializePlotMark
            (plotmark.algorithm, plotmark.size, plotmark.fill, plotmark.paintable);
    }
    
    
    
    /**
     * <p>Constructs a plot mark using the given plot mark but
     * changing the specified setting.</p>
     *
     * <p>If the plot mark is <code>null</code>, it is set to
     * <code>PlotMark.FILLED_SQUARE</code>.</p>
     *
     * <p>If the plot mark is set by a paintable then the new
     * plot mark is set by the same paintable.</p>
     *
     * <p>If the size is less than 1, it is set to 1.</p>
     *
     * @param plotmark the plot mark to partially clone
     * @param size the plot mark size setting
     */
    public PlotMark(PlotMark plotmark, int size) { 
        if (plotmark == null)
            plotmark = FILLED_SQUARE;
        
        initializePlotMark
            (plotmark.algorithm, size, plotmark.fill, plotmark.paintable);
    }
    
    
    
    /**
     * <p>Constructs a plot mark using the given plot mark but
     * changing the specified setting.</p>
     *
     * <p>If the plot mark is <code>null</code>, it is set to
     * <code>PlotMark.FILLED_SQUARE</code>.</p>
     *
     * <p>If the plot mark is set by a paintable then the new
     * plot mark is set by the same paintable.</p>
     *
     * @param plotmark the plot mark to partially clone
     * @param fill the plot mark fill setting
     */
    public PlotMark(PlotMark plotmark, boolean fill) { 
        if (plotmark == null)
            plotmark = FILLED_SQUARE;
        
        initializePlotMark
            (plotmark.algorithm, plotmark.size, fill, plotmark.paintable);
    }
    
    
    
    /**
     * <p>Constructs a plot mark using the given plot mark but
     * changing the specified settings.</p>
     *
     * <p>If the plot mark is <code>null</code>, it is set to
     * <code>PlotMark.FILLED_SQUARE</code>.</p>
     *
     * <p>If the plot mark is set by a paintable then the new
     * plot mark is set by the same paintable.</p>
     *
     * <p>If the size is less than 1, it is set to 1.</p>
     *
     * @param plotmark the plot mark to partially clone
     * @param size the plot mark size setting
     * @param fill the plot mark fill setting
     */
    public PlotMark(PlotMark plotmark, int size, boolean fill) { 
        if (plotmark == null)
            plotmark = FILLED_SQUARE;
        
        initializePlotMark
            (plotmark.algorithm, size, fill, plotmark.paintable);
    }
    
    
    /**
     * <p>Constructs a plot mark using the given paintable.</p>
     *
     * <p>Uses the default plot mark algorithm and settings
     * if the given paintable is <code>null</code>.</p>
     *
     * @param paintable the paintable to use for the plot mark
     */
    public PlotMark(Paintable paintable) {
        initializePlotMark(
            DEFAULT_ALGORITHM,
            DEFAULT_SIZE,
            DEFAULT_FILL,
            paintable);
    }
    
    
    /**
     * <p>The common initialization code for a plot mark.</p>
     *
     * @param algorithm the plot mark algorithm if used
     * @param size the plot mark size setting
     * @param fill the plot mark fill setting
     * @param paintable the plot mark paintable if used
     */
    private void initializePlotMark
        (PlotMarkAlgorithm algorithm, int size, boolean fill, Paintable paintable)
    {
        if (paintable != null) {
            this.algorithm = null;
            this.paintable = paintable;
            return;
        }
        
        if (algorithm == null)
            algorithm = PlotMarkAlgorithm.Square;
        
        this.algorithm = algorithm;
        
        if (size < 1)
            size = 1;
        
        this.size = size;
        
        this.fill = fill;
    }
    
    
    /**
     * <p>Draws, fills, or paints a mark at the given plot point.</p>
     *
     * <p>If this plot mark is defined by a plot mark algorithm, then
     * proceed as follows.</p>
     *
     * <ul>
     *   <li>Use the plot mark algorithm together with the plot point
     *       and the size to define a <code>Shape</code></li>
     *   <li>Fill or draw the shape depending on the fill setting and
     *       use BasicStroke(1) as the stroke setting if drawing.  Use
     *       other graphics context settings such as the current Paint
     *       as-is.</li>
     * </ul>
     *
     * <p>Otherwise, if the plot mark is defined by a paintable, then
     * perform the following action.</p>
     *
     * <ul>
     *   <li>paintable.paintAt(g, the-plot-point)</li>
     * </ul>
     *
     * <p>Does nothing if the given plot point is <code>null</code>.
     *
     * @param g the graphics context
     * @param p the plot point
     */
    public final void mark(Graphics g, Point2D p) {
        if (g == null)
            return;
        
        if (p == null)
            return;
        
        mark(g, p.getX(), p.getY());
    }
    
    
    /**
     * <p>Draws, fills, or paints a mark at the given plot point.</p>
     *
     * <p>If this plot mark is defined by a plot mark algorithm, then
     * proceed as follows.</p>
     *
     * <ul>
     *   <li>Use the plot mark algorithm together with the plot point
     *       and the size to define a <code>Shape</code></li>
     *   <li>Fill or draw the shape depending on the fill setting and
     *       use BasicStroke(1) as the stroke setting if drawing.  Use
     *       other graphics context settings such as the current Paint
     *       as-is.</li>
     * </ul>
     *
     * <p>Otherwise, if the plot mark is defined by a paintable, then
     * perform the following action.</p>
     *
     * <ul>
     *   <li>paintable.paintAt(g, the-plot-point)</li>
     * </ul>
     *
     * @param g the graphics context
     * @param x the x-coordinate of the plot point
     * @param y the y-coordinate of the plot point
     */
    public final void mark(Graphics g, double x, double y) {
        if (g == null)
            return;
        
        if (algorithm != null) {
            Shape s = algorithm.makeShape(x, y, size);
            
            Graphics2D h = (Graphics2D) g;
            
            if (fill) {
                h.fill(s);
            }
            else {
                Stroke stroke = h.getStroke();
                h.setStroke(standardStroke);
                h.draw(s);
                h.setStroke(stroke);
            }
        }
        else
        if (paintable != null) {
            paintable.paintAt(g, x, y);
        }
    }
    
    
    /**
     * <p>Draws, fills, or paints a mark at the plot point which is
     * obtained by applying the transform to the given point.</p>
     *
     * <p>In all cases, find the plot point by applying the transform
     * to the given point.</p>
     *
     * <p>Then, if this plot mark is defined by a plot mark algorithm,
     * proceed as follows.</p>
     *
     * <ul>
     *   <li>Use the plot mark algorithm together with the plot point
     *       and the size to define a <code>Shape</code></li>
     *   <li>Fill or draw the shape depending on the fill setting and
     *       use BasicStroke(1) as the stroke setting if drawing.  Use
     *       other graphics context settings such as the current Paint
     *       as-is.</li>
     * </ul>
     *
     * <p>Otherwise, if the plot mark is defined by a paintable, then
     * perform the following action.</p>
     *
     * <ul>
     *   <li>paintable.paintAt(g, the-plot-point)</li>
     * </ul>
     *
     * <p>Does nothing if the original point is <code>null</code>.
     *
     * @param g the graphics context
     * @param T the affine transform to apply to the original point
     * @param p the original point
     */
    public final void mark(Graphics g, AffineTransform T, Point2D p) {
        if (g == null)
            return;
        
        if (p == null)
            return;
        
        mark(g, T, p.getX(), p.getY());
    }
    
    
    /**
     * <p>Draws, fills, or paints a mark at the plot point which is
     * obtained by applying the transform to the given point.</p>
     *
     * <p>In all cases, find the plot point by applying the transform
     * to the given point.</p>
     *
     * <p>Then, if this plot mark is defined by a plot mark algorithm,
     * proceed as follows.</p>
     *
     * <ul>
     *   <li>Use the plot mark algorithm together with the plot point
     *       and the size to define a <code>Shape</code></li>
     *   <li>Fill or draw the shape depending on the fill setting and
     *       use BasicStroke(1) as the stroke setting if drawing.  Use
     *       other graphics context settings such as the current Paint
     *       as-is.</li>
     * </ul>
     *
     * <p>Otherwise, if the plot mark is defined by a paintable, then
     * perform the following action.</p>
     *
     * <ul>
     *   <li>paintable.paintAt(g, the-plot-point)</li>
     * </ul>
     *
     * @param g the graphics context
     * @param T the affine transform to apply to the original point
     * @param x the x-coordinate of the original point
     * @param y the y-coordinate of the original point
     */
    public final void mark(Graphics g, AffineTransform T, double x, double y) {
        if (g == null)
            return;
        
        if ((T == null) || (T.isIdentity()))
            mark(g, x, y);
        else {
            Point2D.Double p = new Point2D.Double(x, y);
            T.transform(p, p);
            mark(g, p.getX(), p.getY());
        }
    }
    
    
    /**
     * Returns the internal <code>PlotMarkAlgorithm</code>
     * or <code>null</code> if an algorithm is not used.
     */
    public final PlotMarkAlgorithm getAlgorithm() { return algorithm; }
    
    
    /**
     * Returns the size setting.  This will not be used if
     * the plot mark is defined by a paintable.
     *
     */
    public final int getSize() { return size; }
    
    
    /**
     * Returns the fill setting.  This will not be used if
     * the plot mark is defined by a paintable.
     *
     */
    public final boolean getFill() { return fill; }
    
    
    /**
     * Returns the internal <code>Paintable</code>
     * or <code>null</code> if a paintable is not used.
     */
    public final Paintable getPaintable() { return paintable; }
    
}
