/*
 * @(#)MutatableWrapper.java    1.0  24 October 2004
 *
 * Copyright 2004
 * 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 javax.swing.*;
import java.beans.*;

/**
 * <p>The class <code>MutatableWrapper</code> will wrap a <code>Paintable</code>
 * object to obtain an object that implements <code>MutatablePaintable</code>
 * and <code>SupportsPropertyChange</code>.</p>
 *
 * <p>In 2.3.2, the method <code>getBounds2D</code> was modified to use the
 * default Bounds2D rectangle if that rectangle is non-<code>null</code>
 * before making any other tests or computations.</p>
 *
 * @author  Richard Rasala
 * @version 2.3.2
 * @since   2.3
 */
public class MutatableWrapper
    extends AbstractMutatablePaintable
{
    /** Bound property name for set paintable. */
    public static final String SET_PAINTABLE = "set.paintable";
    
    
    /** The wrapped paintable. */
    private Paintable paintable = null;
    
    
    /**
     * The constructor that leaves the paintable unspecified.
     *
     * @see #MutatableWrapper(Paintable)
     * @see #MutatableWrapper(Paintable, AffineTransform)
     */
    public MutatableWrapper() {}
    
    
    /**
     * The constructor to specify the paintable to be wrapped.
     *
     * @param paintable the paintable to be wrapped
     * @see #MutatableWrapper()
     * @see #MutatableWrapper(Paintable, AffineTransform)
     */
    public MutatableWrapper(Paintable paintable) {
        setPaintable(paintable);
    }
    
    
    /**
     * The constructor to specify the paintable to be wrapped
     * and the initial mutator.
     *
     * @param paintable the paintable to be wrapped
     * @param mutator   the initial mutator
     * @see #MutatableWrapper()
     * @see #MutatableWrapper(Paintable)
     */
    public MutatableWrapper(Paintable paintable, AffineTransform mutator) {
        setPaintable(paintable);
        setMutator(mutator);
    }
    
    
    /**
     * <p>Paints onto a <code>Graphics</code> context using information
     * from the wrapped paintable and from the mutator.</p>
     *
     * <p>If the current paintable is <code>null</code>, this method will
     * not paint.</p>
     *
     * @param g the graphics context on which to paint
     */
    public final void paint(Graphics g) {
        if ((paintable == null) || (g == null) || !isVisible())
            return;
        
        Graphics2D h = getPreparedGraphics2D(g);
        h.transform(getMutator());
        
        paintable.paint(h);
    }
    
    
    /**
     * <p>Sets the paintable to be wrapped.</p>
     *
     * <p>The paintable may be set to <code>null</code> to eliminate the
     * paintable.  This object will then paint nothing.</p>
     *
     * <p>Fires property change: SET_PAINTABLE.</p>
     * 
     * @param paintable the paintable to be wrapped
     * @see #getPaintable()
     */
    public final void setPaintable(Paintable paintable) {
        if (paintable == this.paintable)
            return;
        
        removeAndAddForwardingListener(this.paintable, paintable);
        
        this.paintable = paintable;
        
        firePropertyChange(SET_PAINTABLE, null, null);
    }
    
    
    /**
     * Returns the wrapped paintable.
     *
     * @return the wrapped paintable
     * @see #setPaintable(Paintable)
     */
    public final Paintable getPaintable() {
        return paintable;
    }
    
    
    /**
     * <p>Returns the bounds of the paintable based on the default settings or on
     * more detailed computations.</p>
     *
     * <p>If the value of <code>getDefaultBounds2D</code> is non-<code>null</code>,
     * then this value is returned.</p>
     *
     * <p>Otherwise, if the current paintable is <code>null</code>, this method
     * returns </code>new Rectangle2D.Double()</code>.</p>
     *
     * <p>Otherwise, this method returns the value computed by the method inherited
     * from its superclass.</p>
     * 
     * @see AbstractMutatablePaintable#getBounds2D()
     * @see #getOriginalBounds2D()
     * @see AbstractPaintable#setDefaultBounds2D(Rectangle2D)
     * @see AbstractPaintable#getDefaultBounds2D()
     */
    public final Rectangle2D getBounds2D() {
        // get the default bounds
        Rectangle2D bounds = getDefaultBounds2D();
        
        if (bounds != null)
            return bounds;
        
        // check paintable
        if (paintable == null)
            return new Rectangle2D.Double();
        
        // use inherited method
        return super.getBounds2D();
    }
    
    
    /**
     * <p>If the current paintable is <code>null</code>, this method returns
     * <code>new Point2D.Double()</code>.</p>
     *
     * <p>If the value of <code>getDefaultCenter</code> is non-<code>null</code>,
     * then this value is returned.</p>
     *
     * <p>Otherwise, this method returns the value computed by the method inherited
     * from its superclass.</p>
     *
     * @see AbstractMutatablePaintable#getCenter()
     * @see #getOriginalCenter()
     * @see AbstractPaintable#setDefaultCenter(Point2D)
     * @see AbstractPaintable#getDefaultCenter()
     */
    public final Point2D getCenter() {
        // check paintable
        if (paintable == null)
            return new Point2D.Double();
        
        // check default center
        Point2D center = getDefaultCenter();
        
        if (center != null)
            return center;
        
        // use inherited method
        return super.getCenter();
    }
    
    
    /**
     * <p>If the current paintable is <code>null</code>, this method returns
     * </code>new Rectangle2D.Double()</code>.</p>
     *
     * <p>Otherwise, returns a copy of the 2-dimensional bounds of the original
     * paintable.</p>
     *
     * @return a copy of the 2-dimensional bounds of the original paintable
     */
    public final Rectangle2D getOriginalBounds2D() {
        if (paintable == null)
            return new Rectangle2D.Double();
        
        return paintable.getBounds2D();
    }
    
    
   /**
     * <p>If the current paintable is <code>null</code>, this method returns
     * <code>new Point2D.Double()</code>.</p>
     *
     * <p>Otherwise, returns a copy the center of the original paintable.</p>
     *
     * @return a copy the center of the original paintable
     */
    public final Point2D getOriginalCenter() {
        if (paintable == null)
            return new Point2D.Double();
        
        return paintable.getCenter();
    }
    
    
    /**
     * <p>Applies a <code>Mutator.Strategy</code> object to the paintable by
     * composition on the right using the original center to construct the
     * pre-mutation.</p>
     *
     * <p>If the current paintable is <code>null</code>, this method does nothing.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * @param strategy the mutator strategy to apply
     * @see #setMutator(AffineTransform)
     * @see #addPostMutation(AffineTransform)
     * @see #getMutator()
     */
    public final void addPreMutation(Mutator.Strategy strategy) {
        if (paintable == null)
            return;
        
        super.addPreMutation(strategy);
    }
    
    
    /**
     * <p>Applies a <code>Mutator.Strategy</code> object to the paintable by
     * composition on the left using the mutated center to construct the
     * post-mutation.</p>
     *
     * <p>If the current paintable is <code>null</code>, this method does nothing.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * @param strategy the mutator strategy to apply
     * @see #setMutator(AffineTransform)
     * @see #addPreMutation(AffineTransform)
     * @see #getMutator()
     */
    public final void addPostMutation(Mutator.Strategy strategy) {
        if (paintable == null)
            return;
        
        super.addPostMutation(strategy);
    }
    
    
    /**
     * <p>Tests if a point specified by coordinates is inside the mutated paintable.</p>
     *
     * <p>This method returns <code>false</code> if one or more of the following
     * conditions occurs:</p>
     *
     * <ul>
     *   <li>The current paintable is <code>null</code>.</li>
     *   <li>The point is not in the rectangle <code>getBounds2D</code>.</li>
     *   <li>The method <code>isVisible</code> returns <code>false</code>.</li>
     * </ul>
     *
     * @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 paintable
     */
    public final boolean contains(double x, double y) {
        if (paintable == null)
            return false;
        
        if (!possiblyContains(x, y))
            return false;
        
        Point2D p = new Point2D.Double(x, y);
        
        getMutatorInverse().transform(p, p);
        
        return paintable.contains(p);
    }
    
}
