/*
 * @(#)AbstractMutatablePaintable.java    1.0  15 December 2003
 *
 * 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.geom.*;

/**
 * <P>The abstract class <CODE>AbstractMutatablePaintable</CODE>
 * defines objects that extend <CODE>AbstractPaintable</CODE>
 * and implement <CODE>MutatablePaintable</CODE>.</P>
 *
 * @author  Richard Rasala
 * @version 2.3
 * @since   2.3
 */
public abstract class AbstractMutatablePaintable
    extends AbstractPaintable
    implements MutatablePaintable
{
    /** Bound property name for set mutator. */
    public static final String SET_MUTATOR = "set.mutator";
    
    
    /** The mutator transform. */
    private AffineTransform mutator = new AffineTransform();
    
    /** The mutator inverse transform. */
    private AffineTransform inverse = new AffineTransform();
    
    
    /**
     * <P>Returns a copy of the 2-dimensional bounds of the mutated paintable.</P>
     *
     * <P>By default, this method returns the bounds rectangle of the image
     * of the original bounds rectangle, <CODE>getOriginalBounds2D</CODE>,
     * under the mutator transform.</P>
     *
     * <P>This method may be overridden in a derived class if a more exact
     * bounds rectangle may be calculated or if the relationship between
     * <CODE>getBounds2D</CODE> and <CODE>getOriginalBounds2D</CODE> is
     * more subtle.</P>
     *
     * <P>This method does not use the default Bounds2D rectangle as defined
     * by <CODE>setDefaultBounds2D</CODE> in <CODE>AbstractPaintable</CODE>.
     * The decision to use the default Bounds2D rectangle is left to derived
     * classes.</P>
     *
     * <P>This method must not return <CODE>null</CODE>.</P>
     *
     * @return a copy of the 2-dimensional bounds of the mutated paintable
     * @see #getOriginalBounds2D()
     * @see AbstractPaintable#setDefaultBounds2D(Rectangle2D)
     * @see AbstractPaintable#getDefaultBounds2D()
     */
    public Rectangle2D getBounds2D() {
        Rectangle2D bounds = getOriginalBounds2D();
        
        double x1 = bounds.getMinX();
        double y1 = bounds.getMinY();
        double x2 = bounds.getMaxX();
        double y2 = bounds.getMaxY();
        
        double[] source = new double[] { x1, y1, x2, y1, x2, y2, x1, y2 };
        double[] target = new double[8];
        
        mutator.transform(source, 0, target, 0, 4);
        
        x1 = Math.min(target[0], Math.min(target[2], Math.min(target[4], target[6])));
        y1 = Math.min(target[1], Math.min(target[3], Math.min(target[5], target[7])));
        
        x2 = Math.max(target[0], Math.max(target[2], Math.max(target[4], target[6])));
        y2 = Math.max(target[1], Math.max(target[3], Math.max(target[5], target[7])));
        
        return new Rectangle2D.Double(x1, y1, x2 - x1, y2 - y1);
    }
    
    
    /**
     * <P>Returns a copy the center of the mutated paintable.</P>
     *
     * <P>By default, this returns the image of <CODE>getOriginalCenter()</CODE>
     * under the mutator transform.</P>
     *
     * <P>This method may be overridden in a derived class if a more exact
     * center may be calculated.</P>
     *
     * <P>This method does not use the default center as defined
     * by <CODE>setDefaultCenter</CODE> in <CODE>AbstractPaintable</CODE>.
     * The decision to use the default center is left to derived classes.</P>
     *
     * <P>This method must not return <CODE>null</CODE>.</P>
     *
     * @return a copy the center of the mutated paintable
     * @see #getOriginalCenter()
     * @see AbstractPaintable#setDefaultCenter(Point2D)
     * @see AbstractPaintable#getDefaultCenter()
     */
    public Point2D getCenter() {
        Point2D center = getOriginalCenter();
        
        return mutator.transform(center, center);
    }
    
    
    /**
     * <P>Returns a copy of the 2-dimensional bounds of the original paintable.</P>
     *
     * <P>If the current paintable is empty, this method should return
     * new Rectangle2D.Double().</P>
     *
     * <P>This method must not return <CODE>null</CODE>.</P>
     *
     * @return a copy of the 2-dimensional bounds of the original paintable
     * @see #getBounds2D()
     */
    public abstract Rectangle2D getOriginalBounds2D();
    
    
    /**
     * <P>Returns a copy the center of the original paintable.</P>
     *
     * <P>If the current paintable is empty, this method should return
     * new Point2D.Double().</P>
     *
     * <P>This method must not return <CODE>null</CODE>.</P>
     *
     * @return a copy the center of the original paintable
     * @see #getCenter()
     */
    public abstract Point2D getOriginalCenter();
    
    
    /**
     * <P>Sets the mutator transform to the given transform provided that the
     * given transform is invertible.</P>
     *
     * <P>If the given transform is not invertible, the method does nothing.</P>
     *
     * <P>Fires property change: SET_MUTATOR.</P>
     * 
     * <P>This method may be overridden in a derived class if additional work
     * must be done when the mutator is set.</P>
     *
     * @param M the invertible affine transform to set as the mutator
     * @see #addPreMutation(AffineTransform)
     * @see #addPreMutation(Mutator.Strategy)
     * @see #addPostMutation(AffineTransform)
     * @see #addPostMutation(Mutator.Strategy)
     * @see #getMutator()
     * @see #getMutatorInverse()
     */
    public void setMutator(AffineTransform M) {
        if ((M == null) || (M.equals(mutator)))
            return;
        
        try {
            // test to make sure that the inverse of M exists
            AffineTransform N = M.createInverse();
            
            // if the inverse exists replace the mutator and its inverse
            mutator = M;
            inverse = N;
        }
        catch (NoninvertibleTransformException exception) {
            // on error do nothing
            return;
        }
        
        firePropertyChange(SET_MUTATOR, null, null);
    }
    
    
    /**
     * <P>Composes the existing mutator on the right with the given transform
     * provided that the given transform is invertible.</P>
     *
     * <P>The action of the new mutator will be to apply the given transform
     * and then to apply the old mutator.</P>
     *
     * <P>This method is equivalent to:</P>
     *
     * <P><CODE>setMutator(TransformFactory.compose(getMutator(), M))</CODE></P>
     *
     * <P>Fires property change: SET_MUTATOR.</P>
     * 
     * @param M the invertible affine transform to compose on the right with
     *        the existing mutator
     * @see #setMutator(AffineTransform)
     * @see #addPreMutation(Mutator.Strategy)
     * @see #addPostMutation(AffineTransform)
     * @see #addPostMutation(Mutator.Strategy)
     * @see #getMutator()
     * @see #getMutatorInverse()
     */
    public final void addPreMutation(AffineTransform M) {
        if (M == null)
            return;
        
        setMutator(TransformFactory.compose(getMutator(), M));
    }
    
    
    
    /**
     * <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>Fires property change: SET_MUTATOR.</P>
     * 
     * <P>This method may be overridden in a derived class if the mutator strategy
     * will be used in a more subtle fashion.  The overriding method must continue
     * to fire the SET_MUTATOR property change.</P>
     *
     * @param strategy the mutator strategy to apply
     * @see #setMutator(AffineTransform)
     * @see #addPreMutation(AffineTransform)
     * @see #addPostMutation(AffineTransform)
     * @see #addPostMutation(Mutator.Strategy)
     * @see #getMutator()
     * @see #getMutatorInverse()
     */
    public void addPreMutation(Mutator.Strategy strategy) {
        if (strategy == null)
            return;
        
        addPreMutation(strategy.mutator(getOriginalCenter()));
    }
    
    
    /**
     * <P>Composes the existing mutator on the left with the given transform
     * provided that the given transform is invertible.</P>
     *
     * <P>The action of the new mutator will be to apply the old mutator and
     * then to apply the given transform.</P>
     *
     * <P>This method is equivalent to:</P>
     *
     * <P><CODE>setMutator(TransformFactory.compose(M, getMutator()))</CODE></P>
     *
     * <P>Fires property change: SET_MUTATOR.</P>
     * 
     * @param M the invertible affine transform to compose on the left with
     *        the existing mutator
     * @see #setMutator(AffineTransform)
     * @see #addPreMutation(AffineTransform)
     * @see #addPreMutation(Mutator.Strategy)
     * @see #addPostMutation(Mutator.Strategy)
     * @see #getMutator()
     * @see #getMutatorInverse()
     */
    public final void addPostMutation(AffineTransform M) {
        if (M == null)
            return;
        
        setMutator(TransformFactory.compose(M, getMutator()));
    }
    
    
    /**
     * <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>Fires property change: SET_MUTATOR.</P>
     * 
     * <P>This method may be overridden in a derived class if the mutator strategy
     * will be used in a more subtle fashion.  The overriding method must continue
     * to fire the SET_MUTATOR property change.</P>
     *
     * @param strategy the mutator strategy to apply
     * @see #setMutator(AffineTransform)
     * @see #addPreMutation(AffineTransform)
     * @see #addPreMutation(Mutator.Strategy)
     * @see #addPostMutation(AffineTransform)
     * @see #getMutator()
     * @see #getMutatorInverse()
     */
    public void addPostMutation(Mutator.Strategy strategy) {
        if (strategy == null)
            return;
        
        addPostMutation(strategy.mutator(getCenter()));
    }
    
    
    /**
     * Returns a copy of the existing mutator transform.
     *
     * @return a copy of the existing mutator transform
     * @see #setMutator(AffineTransform)
     * @see #addPreMutation(AffineTransform)
     * @see #addPreMutation(Mutator.Strategy)
     * @see #addPostMutation(AffineTransform)
     * @see #addPostMutation(Mutator.Strategy)
     * @see #getMutatorInverse()
     */
    public final AffineTransform getMutator() {
        return new AffineTransform(mutator);
    }
    
    
    /**
     * Returns a copy of the existing mutator inverse transform.
     *
     * @return a copy of the existing mutator inverse transform
     * @see #setMutator(AffineTransform)
     * @see #addPreMutation(AffineTransform)
     * @see #addPreMutation(Mutator.Strategy)
     * @see #addPostMutation(AffineTransform)
     * @see #addPostMutation(Mutator.Strategy)
     * @see #getMutator()
     */
    public final AffineTransform getMutatorInverse() {
        return new AffineTransform(inverse);
    }
    
    
    /**
     * <P>Moves the mutatable by a translation using the data in the point
     * specified by coordinates.</P>
     *
     * <P>This method should act as a convenience method that performs an
     * <CODE>addPostMutation</CODE> operation using a translation defined
     * by the given point.</P>
     *
     * @param x the x-coordinate of the point
     * @param y the y-coordinate of the point
     */
    public final void move(double x, double y) {
        addPostMutation(TransformFactory.translate(x, y));
    }
    
    
    /**
     * <P>Moves the mutatable by a translation using the data in the point.
     *
     * <P>This method should act as a convenience method that performs an
     * <CODE>addPostMutation</CODE> operation using a translation defined
     * by the given point.</P>
     *
     * <P>If the point is <CODE>null</CODE>, this method should do nothing.
     *
     * @param p a specified <CODE>Point2D</CODE>
     */
    public final void move(Point2D p) {
        if (p == null)
            return;
        
        move(p.getX(), p.getY());
    }
    
}
