/*
 * @(#)Tile.java    2.5.0   4 May 2006
 *
 * Copyright 2006
 * 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 java.awt.*;
import java.awt.geom.*;
import javax.swing.*;


/**
 * <p>The class <code>Tile</code> will wrap a <code>Paintable</code> object
 * to obtain to a copy that may be subject to mutation that does not affect
 * the data in the encapsulated paintable.</p>
 *
 * <p>As of 2.4.0, the class will actually accept an arbitrary object and
 * use the method <code>makePaintable</code> if needed to create the
 * <code>Paintable</code> to be wrapped.  This generality turns out to be
 * convenient at times.</code>
 *
 * <p>The original purpose of this class is to permit multiple copies of one
 * source paintable to exist at once and be mutated independently.</p>
 *
 * <p>The following principles should be observed:</p>
 *
 * <ul>
 *   <li>A mutation that is done on the original paintable will affect all
 *       <code>Tile</code> objects based on that paintable.</li>
 *   <li>A mutation that is done on a <code>Tile</code> object will affect
 *       only that <code>Tile</code> object and not the original paintable.</li>
 * </ul>
 *
 * <p>Normally, the least confusing way to work with <code>Tile</code> objects
 * is to mutate only the <code>Tile</code> objects and leave the paintable
 * alone.</p>
 *
 * <p>A <code>Tile</code> supports the notion of a <i>background paint</i>.
 * If the <i>background paint</i> is non-<code>null</code> then the bounds
 * rectangle of the encapsulated paintable will be painted with the
 * <i>background paint</i> prior to painting the paintable.</p>
 *
 * <p>This class is a generalization of the class <code>MutatableWrapper</code>
 * which has been removed from JPT.</p>
 *
 * <p>In 2.4.0, this class was also updated to be consistent with
 * refinements to the <code>Paintable</code> interface.</p>
 *
 * <p>In 2.5.0, this class added fields <code>row</code> and
 * <code>col</code> that permit the user to tag a tile with
 * a row,col position in a user-defined structure.  Usage of
 * this facility is entirely optional.</p>
 * 
 * @author Richard Rasala
 * @version 2.5.0
 * @since   2.3.5
 */
public class Tile
    extends AbstractPaintable
{
    /** Bound property name for set background paint. */
    public static final String SET_BACKGROUND_PAINT = "set.background.paint";
    
    
    /** The wrapped paintable. */
    protected Paintable paintable = null;
    
    /** The background paint if any. */
    protected Paint background = null;
    
    /** The optional row. */
    protected int row = 0;
    
    /** The optional col. */
    protected int col = 0;
    
    
    /**
     * The constructor that leaves the paintable unspecified;
     * the background paint is set to <code>null</code>.
     */
    public Tile() {}
    
    
    /**
     * The constructor to specify the object to be wrapped;
     * the background paint is set to <code>null</code>.
     *
     * @param o          the object to convert to a paintable to wrap in this tile
     */
    public Tile(Object o) {
        initializeTile(o, null, null);
    }
    
    
    /**
     * The constructor to specify the object to be wrapped
     * and the background paint.
     *
     * @param o          the object to convert to a paintable to wrap in this tile
     * @param background the background paint for the tile
     */
    public Tile(Object o, Paint background) {
        initializeTile(o, background, null);
    }
    
    
    /**
     * The constructor to specify the object to be wrapped
     * and the initial mutator;
     * the background paint is set to <code>null</code>.
     *
     * @param o          the object to convert to a paintable to wrap in this tile
     * @param m          the initial mutator
     */
    public Tile(Object o, AffineTransform m) {
        initializeTile(o, null, m);
    }
    
    
    /**
     * The constructor to specify the object to be wrapped,
     * the background paint, and the initial mutator.
     *
     * @param o          the object to convert to a paintable to wrap in this tile
     * @param background the background paint for the tile
     * @param m          the initial mutator
     */
    public Tile(Object o, Paint background, AffineTransform m) {
        initializeTile(o, background, m);
    }
    
    
    /**
     * The common initialization method.
     *
     * @param o          the object to convert to a paintable to wrap in this tile
     * @param background the background paint for the tile
     * @param m          the initial mutator
     */
    public void initializeTile(Object o, Paint background, AffineTransform m) {
        setPaintable(o);
        setBackgroundPaint(background);
        setMutator(m);
    }
    
    
    /**
     * <p>Paints onto a <CODE>Graphics</CODE> context using the wrapped
     * paintable object but without the use of the mutator transform;
     * if the background paint is non-<code>null</code>, then the
     * rectangle returned by <code>getActualBounds2D()</code> is
     * painted with the background paint prior to the painting of the
     * wrapped paintable.</p>
     *
     * @param g the graphics context on which to paint
     */
    public void originalPaint(Graphics g) {
        if ((g == null) || (paintable == null))
            return;
        
        Graphics2D h = (Graphics2D) g.create();
        
        if (background != null) {
            Rectangle2D bounds = getActualBounds2D();
            
            if (bounds != null) {
                h.setPaint(background);
                h.fill(bounds);
            }
        }
        
        paintable.paint(h);
    }
    
    
    /**
     * <p>Returns the actual bounds of this tile or
     * <code>null</code> if the tile is effectively empty.</p>
     *
     * <p>If the internal paintable is non-<code>null</code>, then
     * returns a copy of the 2-dimensional bounds of the internal
     * paintable prior to any mutation of this object.</p>
     *
     * <p>Since the internal paintable may have a mutation or defaults
     * this method calls <code>getBounds2D</code> on the internal
     * paintable.  Hence any mutation or defaults set for the internal
     * paintable are respected.</p>
     *
     * @return a copy of the 2-dimensional bounds of the original paintable
     */
    public XRect getActualBounds2D() {
        if (paintable == null)
            return null;
        
        return paintable.getBounds2D();
    }
    
    
    /**
     * <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) {
        if (paintable == null)
            return false;
        
        return paintable.contains(x, y);
    }
    
    
    /**
     * <p>Returns a <code>Paintable</code> constructed from the given
     * <code>Object</code>.</p>
     *
     * <p>By default, uses <code>ComponentFactory.makePaintable</code>.</p>
     *
     * <p>May be overridden in a derived class if the designer has a more
     * sophisticated way to construct a <code>Paintable</code>.</p>
     *
     * <p>Any override should respect the convention that if the object
     * is already a <code>Paintable</code> then it is returned as is.</p>
     *
     * @param o the object to make into a Paintable
     */
    public Paintable makePaintable(Object o) {
        return ComponentFactory.makePaintable(o);
    }
    
    
    /**
     * <p>Sets the object to be wrapped.  If the object is a paintable, then
     * it is wrapped as is.  Otherwise the <code>makePaintable</code> method is
     * called to create the paintable that will be wrapped in this tile.</p>
     *
     * <p>The given object may be set to <code>null</code> to eliminate the
     * paintable.  This tile will then paint nothing.</p>
     *
     * <p>Fires property change: SET_PAINTABLE.</p>
     * 
     * @param o the object to convert to a paintable to wrap in this tile
     */
    public void setPaintable(Object o) {
        Paintable paintable = makePaintable(o);
        
        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
     */
    public Paintable getPaintable() {
        return paintable;
    }
    
    
    /**
     * <p>Sets the background paint for the tile.</p>
     *
     * <p>The paint may be set to <code>null</code> to eliminate
     * background painting.</p>
     *
     * <p>Fires property change: SET_BACKGROUND_PAINT.</p>
     * 
     * @param background the background paint for the tile
     */
    public final void setBackgroundPaint(Paint background) {
        if (background == this.background)
            return;
        
        this.background = background;
        
        firePropertyChange(SET_BACKGROUND_PAINT, null, null);
    }
    
    
    /**
     * Returns the background paint for the tile.
     *
     * @return the background paint for the tile
     */
    public final Paint getBackgroundPaint() {
        return background;
    }
    
    
    /**
     * Sets the optional row and col tags for this tile.
     * 
     * @param row the row tag for this tile
     * @param col the col tag for this tile
     */
    public void setRowCol(int row, int col) {
        this.row = row;
        this.col = col;
    }
    
    
    /**
     * Sets the optional row tag for this tile.
     * 
     * @param row the row tag for this tile
     */
    public void setRow(int row) {
        this.row = row;
    }
    
    
    /**
     * Sets the optional col tag for this tile.
     * 
     * @param col the col tag for this tile
     */
    public void setCol(int col) {
        this.col = col;
    }
    
    
    /**
     * Returns the optional row tag for this tile.
     */
    public int getRow() {
        return row;
    }
    
    
    /**
     * Returns the optional col tag for this tile.
     */
    public int getCol() {
        return col;
    }
    
}
