/*
 * @(#)PaintableSequence.java   2.4.0   14 September 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 java.awt.*;
import java.awt.geom.*;
import java.util.*;

/**
 * <p>A <code>PaintableSequence</code> encapsulates a sequence of
 * <code>Paintable</code> objects.</p>
 *
 * <p>In 2.3.5, the class was refactored to be consistent with
 * the new <code>Paintable</code> interface and
 * the new <code>AbstractPaintable</code> class.
 *
 * <p>In 2.4.0, the methods to set, add, or append a <code>Paintable</code>
 * were changed to accept a general <code>Object</code> instead.  This object
 * will be transformed by <code>makePaintable</code> into a Paintable if
 * possible and then added to this sequence.  Note that it is possible to put
 * a <code>null</code> object into the sequence.</p>
 *
 * <p>Whenever there is a change in the overall mutator for the sequence, this
 * change is immediately applied to the individual items in the sequence.  As a
 * consequence, the individual sequence items maintain their own knowledge of
 * the mutation that must be applied in paint operations.  In particular, as
 * far as geometry is concerned, each individual sequence item may be viewed
 * either as part of the sequence or as a stand-alone <code>Paintable</code>.
 * This design decision enables interactive manipulation of sequence items to
 * be handled in a natural fashion.</p>
 *
 * <p>The <code>getMutator</code> method inherited from
 * <code>AbstractPaintable</code> will always return the identity transform.</p>
 *
 * <p>In 2.4.0, this class was also updated to be consistent with
 * refinements to the <code>Paintable</code> interface.</p>
 *
 * @author  Richard Rasala
 * @version 2.4.0
 * @since   2.3
 */
public class PaintableSequence
    extends AbstractPaintable
{
    /** The vector to hold the paintable sequence. */
    private Vector paintablesequence = new Vector(16);
    
    /** The mutator strategy usage for the paintable sequence. */
    private Mutator.StrategyUsage usage = Mutator.MUTATE_AS_ITEMS;
    
    
    /**
     * <p>The default constructor with an empty sequence and the
     * mutator strategy usage set to
     * <code>Mutator.MUTATE_AS_ITEMS</code>.</p>
     */
    public PaintableSequence() {
        this(null, null);
    }
    
    
    /**
     * <p>The constructor that sets up the paintable sequence using
     * an initial array of objects that is suitably converted via
     * <code>makePaintable</code>.</p>
     *
     * <p>If the array is <code>null</code> then the sequence is set
     * to empty.</p>
     *
     * <p>The array may contain <code>null</code> objects that will,
     * in effect, reserve positions that may later be filled using
     * <code>setPaintable</code>.</p>
     *
     * @param objects the array of objects to convert and set
     */
    public PaintableSequence(Object[] objects) {
        this(objects, null);
    }
    
    
    /**
     * <p>The constructor that sets up the paintable sequence using
     * an initial array of objects that is suitably converted via
     * <code>makePaintable</code> and the given choice of mutator
     * strategy usage.</p>
     *
     * <p>If the array is <code>null</code> then the sequence is set
     * to empty.</p>
     *
     * <p>The array may contain <code>null</code> objects that will,
     * in effect, reserve positions that may later be filled using
     * <code>setPaintable</code>.</p>
     *
     * @param objects the array of objects to convert and set
     * @param usage the mutator strategy usage
     */
    public PaintableSequence
        (Object[] objects, Mutator.StrategyUsage usage)
    {
        appendSequence(objects);
        setMutatorStrategyUsage(usage);
    }
    
    
    /**
     * <p>Paints onto a <code>Graphics</code> context g using information
     * from the paintable sequence and from its individual items.</p>
     *
     * <p>The z-order of painting is from index (length - 1) to index 0,
     * that is, the item at index 0 is the top-most entity that is painted.</p>
     *
     * <p>As with other paintable entities, it is assumed that this method
     * is called through the <code>paint</code> method so that clipping,
     * antialiasing, and setting the opacity are done by that method.</p>
     *
     * <p>If the paintable sequence is empty, this method will not paint.</p>
     *
     * @param g the graphics context on which to paint
     */
    public final void originalPaint(Graphics g) {
        if ((g == null) || !isVisible())
            return;
        
        int N = length();
        
        if (N == 0)
            return;
        
        Graphics2D h = (Graphics2D) g.create();
        
        for (int i = (N - 1); i >= 0; i--) {
            Paintable paintable = getPaintable(i);
            
            if (paintable != null)
                paintable.paint(h);
        }
    }
    
    
    /**
     * <p>Returns the actual bounds of the original paintable or
     * <code>null</code> if the paintable is effectively empty.</p>
     */
    public final XRect getActualBounds2D() {
        XRect bounds = null;
        
        int N = length();
        
        for (int i = 0; i < N; i++) {
            Paintable paintable = getPaintable(i);
            
            if (paintable == null)
                continue;
            
            // get the next rectangle
            XRect next = paintable.getBounds2D();
            
            // ignore next if its width or its height is zero
            if ((next == null) || (next.getWidth() <= 0) || (next.getHeight() <= 0))
                continue;
            
            // add in the bounds of the next rectangle
            if (bounds == null)
                bounds = next;
            else
                bounds = bounds.createUnionRect(next);
        }
        
        return bounds;
    }
    
    
    /**
     * <p>Tests if a point specified by coordinates is inside this sequence.</p>
     *
     * <p>This method returns <code>false</code> if one or more of the following
     * conditions occurs:</p>
     *
     * <ul>
     *   <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
     */
    public final boolean originalContains(double x, double y) {
        return hitsItem(x, y) != null;
    }
    
    
    /**
     * <p>Sets the mutator for the paintable sequence by recursively setting
     * the mutator for each item in the sequence to the given transform.</p>
     *
     * <p>If the given transform is not invertible, the method does nothing.</p>
     *
     * <p>Because sequence items may also be mutated individually, there is
     * no guarantee that there is one mutator that applies to the sequence
     * as a whole.  Therefore, this method does not retain knowledge of any
     * mutator set.  As a consequence, the method <code>getMutator</code>
     * will always return the identity transform.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * @param M the invertible affine transform to set as the mutator
     */
    public final void setMutator(AffineTransform M) {
        if (M == null)
            return;
        
        try {
            // test to make sure that the inverse of M exists
            AffineTransform N = M.createInverse();
        }
        catch (NoninvertibleTransformException exception) {
            // on error do nothing
            return;
        }
        
        // set the mutator for the individual sequence items
        int N = length();
        
        for (int i = 0; i < N; i++) {
            Paintable paintable = getPaintable(i);
            
            if (paintable == null)
                continue;
            
            paintable.setMutator(M);
        }
    }
    
    
    /**
     * <p>Adds the pre-mutation for the paintable sequence by recursively setting
     * the pre-mutation for each item in the sequence to the given transform.</p>
     *
     * <p>If the given transform is not invertible, the method does nothing.</p>
     *
     * <P>Fires property change: SET_MUTATOR.</P>
     * 
     * @param M the invertible affine transform to add
     */
    public void addPreMutation(AffineTransform M) {
        if (M == null)
            return;
        
        try {
            // test to make sure that the inverse of M exists
            AffineTransform N = M.createInverse();
        }
        catch (NoninvertibleTransformException exception) {
            // on error do nothing
            return;
        }
        
        // apply the mutation to the individual sequence items
        int N = length();
        
        for (int i = 0; i < N; i++) {
            Paintable paintable = getPaintable(i);
            
            if (paintable == null)
                continue;
            
            paintable.addPreMutation(M);
        }
    }
    
    
    /**
     * <p>Adds the post-mutation for the paintable sequence by recursively setting
     * the post-mutation for each item in the sequence to the given transform.</p>
     *
     * <p>If the given transform is not invertible, the method does nothing.</p>
     *
     * <P>Fires property change: SET_MUTATOR.</P>
     * 
     * @param M the invertible affine transform to add
     */
    public void addPostMutation(AffineTransform M) {
        if (M == null)
            return;
        
        try {
            // test to make sure that the inverse of M exists
            AffineTransform N = M.createInverse();
        }
        catch (NoninvertibleTransformException exception) {
            // on error do nothing
            return;
        }
        
        // apply the mutation to the individual sequence items
        int N = length();
        
        for (int i = 0; i < N; i++) {
            Paintable paintable = getPaintable(i);
            
            if (paintable == null)
                continue;
            
            paintable.addPostMutation(M);
        }
    }
    
    
    /**
     * <p>Sets the mutation determined by the given strategy and by the current
     * setting of the mutator strategy usage.</p>
     *
     * <p>If the current mutator strategy usage is <code>MUTATE_AS_GROUP</code>
     * then this method applies the <code>Mutator.Strategy</code> object to
     * the paintable sequence as a whole by using the <i>original</i> center
     * to construct the mutation to set.</p>
     *
     * <p>If the current mutator strategy usage is <code>MUTATE_AS_ITEMS</code>
     * then this method recursively applies the <code>Mutator.Strategy</code>
     * object to the individual items in the paintable sequence.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * @param strategy the mutator strategy to apply
     */
    public void setMutator(Mutator.Strategy strategy) {
        if (strategy == null)
            return;
        
        if (usage == Mutator.MUTATE_AS_ITEMS) {
            int N = length();
            
            for (int i = 0; i < N; i++) {
                Paintable paintable = getPaintable(i);
                
                if (paintable != null)
                    paintable.setMutator(strategy);
            }
        }
        else {
            setMutator(strategy.mutator(getOriginalCenter()));
        }
    }
    
    
    /**
     * <p>Adds a pre-mutation determined by the given strategy and by the current
     * setting of the mutator strategy usage.</p>
     *
     * <p>If the current mutator strategy usage is <code>MUTATE_AS_GROUP</code>
     * then this method applies the <code>Mutator.Strategy</code> object to
     * the paintable sequence as a whole by composition on the right using the
     * <i>original</i> center to construct the pre-mutation.</p>
     *
     * <p>If the current mutator strategy usage is <code>MUTATE_AS_ITEMS</code>
     * then this method recursively applies the <code>Mutator.Strategy</code>
     * object to the individual items in the paintable sequence.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * @param strategy the mutator strategy to apply
     */
    public final void addPreMutation(Mutator.Strategy strategy) {
        if (strategy == null)
            return;
        
        if (usage == Mutator.MUTATE_AS_ITEMS) {
            int N = length();
            
            for (int i = 0; i < N; i++) {
                Paintable paintable = getPaintable(i);
                
                if (paintable != null)
                    paintable.addPreMutation(strategy);
            }
        }
        else {
            addPreMutation(strategy.mutator(getOriginalCenter()));
        }
    }
    
    
    /**
     * <p>Adds a post-mutation determined by the given strategy and by the current
     * setting of the mutator strategy usage.</p>
     *
     * <p>If the current mutator strategy usage is <code>MUTATE_AS_GROUP</code>
     * then this method applies the <code>Mutator.Strategy</code> object to
     * the paintable sequence as a whole by composition on the left using the
     * <i>mutated</i> center to construct the post-mutation.</p>
     *
     * <p>If the current mutator strategy usage is <code>MUTATE_AS_ITEMS</code>
     * then this method recursively applies the <code>Mutator.Strategy</code>
     * object to the individual items in the paintable sequence.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * @param strategy the mutator strategy to apply
     */
    public final void addPostMutation(Mutator.Strategy strategy) {
        if (strategy == null)
            return;
        
        if (usage == Mutator.MUTATE_AS_ITEMS) {
            int N = length();
            
            for (int i = 0; i < N; i++) {
                Paintable paintable = getPaintable(i);
                
                if (paintable != null)
                    paintable.addPostMutation(strategy);
            }
        }
        else {
            addPostMutation(strategy.mutator(getCenter()));
        }
    }
    
    
    /**
     * <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);
    }
    
    
    /**
     * Returns the length of the paintable sequence.
     */
    public final int length() {
        return paintablesequence.size();
    }
    
    
    /**
     * <p>Uses <code>makePaintable</code> to convert the given object to
     * a <code>Paintable</code> and then installs this paintable at the
     * given index in the paintable sequence; the paintable currently at
     * that position in the sequence is replaced.</p>
     *
     * <p>Returns the <code>Paintable</code> installed.</p>
     *
     * <p>This method does nothing if the index is not in [0, length)
     * where length is the value of the method <code>length()</code>
     * and returns <code>null</code> in this case.</p>
     *
     * <p>It is possible for this method to install a <code>null</code>
     * Paintable if that is what is returned by makePaintable.</p>
     *
     * <p>Fires property change: SET_PAINTABLE.</p>
     * 
     * @param index the position in the sequence
     * @param o the object convert to a paintable to set at the position
     */
    public final Paintable setPaintable(int index, Object o) {
        if ((index < 0) || (index >= length()))
            return null;
        
        Paintable newpaintable = makePaintable(o);
        Paintable oldpaintable = getPaintable(index);
        
        if (newpaintable != oldpaintable) {
            removeAndAddForwardingListener(oldpaintable, newpaintable);
            
            paintablesequence.set(index, newpaintable);
            
            firePropertyChange(SET_PAINTABLE, null, null);
        }
        
        return newpaintable;
    }
    
    
    /**
     * <p>This method is equivalent to <code>setPaintable(int, Object)</code>
     * but interchanges the order of the arguments.</p>
     * 
     * <p>Fires property change: SET_PAINTABLE.</p>
     *
     * @param o the object convert to a paintable to set at the position
     * @param index the position in the sequence
     */
    public final Paintable setPaintable(Object o, int index) {
        return setPaintable(index, o);
    }
    
    
    /**
     * <p>Returns the <code>Paintable</code> at the given index in
     * the sequence.</p>
     *
     * <p>This method returns <code>null</code> if index is not in
     * [0, length).</p>
     *
     * @param index the position in the sequence
     */
    public final Paintable getPaintable(int index) {
        if ((index < 0) || (index >= length()))
            return null;
        
        return (Paintable) paintablesequence.get(index);
    }
    
    
    /**
     * <p>Returns the index in the paintable sequence of the given
     * <code>Paintable</code>.</p>
     *
     * <p>The index is also known as the z-index since it represents
     * the order in which items will appear when painted with index
     * 0 in the top-most position.</p>
     *
     * <p>Returns -1 if the given <code>Paintable</code> is
     * <code>null</code> or is not in the paintable sequence.</p>
     *
     * @param the paintable to find
     */
    public final int getIndex(Paintable paintable) {
        int index = -1;
        
        if (paintable == null)
            return index;
        
        int length = length();
        
        for (int i = 0; i < length; i++) {
            if (paintable == getPaintable(i)) {
                index = i;
                break;
            }
        }
        
        return index;
    }
    
    
    /**
     * <p>Uses <code>makePaintable</code> to convert the given object to
     * a <code>Paintable</code> and then inserts this paintable at the
     * given index in the paintable sequence; all current elements in
     * the sequence from that index onward are displaced by one index
     * position.</p>
     *
     * <p>Returns the <code>Paintable</code> inserted.</p>
     *
     * <p>If the given index is 0, then the paintable is inserted as
     * the top-most paintable in the sequence.  This is equivalent to
     * calling the 1-argument version of <code>addPaintable</code>.</p>
     *
     * <p>If the given index is <code>length()</code>, then the paintable
     * is inserted as the bottom-most paintable in the sequence.  This is
     * equivalent to calling <code>appendPaintable</code>.</p>
     *
     * <p>If the given index is less than 0, it is set to 0.</p>
     *
     * <p>If the given index is greater than <code>length()</code>, it is
     * set to <code>length()</code>.</p>
     *
     * <p>It is possible for this method to insert a <code>null</code>
     * paintable if that is what is returned by makePaintable.  This is
     * done to permit the caller to reserve a slot for future additions
     * to the sequence.</p>
     *
     * <p>Fires property change: SET_PAINTABLE.</p>
     * 
     * @param index the position in the sequence
     * @param o the object to convert to a paintable to insert at the position
     */
    public final Paintable addPaintable(int index, Object o) {
        int length = length();
        
        if (index < 0)
            index = 0;
        
        if (index > length)
            index = length;
        
        Paintable paintable = makePaintable(o);
        addForwardingListener(paintable);
        
        paintablesequence.add(index, paintable);
        
        firePropertyChange(SET_PAINTABLE, null, null);
        
        return paintable;
    }
    
    
    /**
     * <p>This method is equivalent to <code>addPaintable(int, Object)</code>
     * but interchanges the order of the arguments.</p>
     * 
     * <p>Fires property change: SET_PAINTABLE.</p>
     *
     * @param o the object to convert to a paintable to insert at the position
     * @param index the position in the sequence
     */
    public final Paintable addPaintable(Object o, int index) {
        return addPaintable(index, o);
    }
    
    
    /**
     * <p>Uses <code>makePaintable</code> to convert the given object to
     * a <code>Paintable</code> and then inserts this paintable at the
     * top-most position in the paintable sequence; all current elements
     * in the sequence are displaced by one index position.</p>
     *
     * <p>Returns the <code>Paintable</code> inserted.</p>
     *
     * <p>This method is equivalent to <code>addPaintable(0, o)</code>.</p>
     * 
     * <p>Fires property change: SET_PAINTABLE.</p>
     *
     * @param o the object to convert to a paintable to insert at the top
     */
    public final Paintable addPaintable(Object o) {
        return addPaintable(0, o);
    }
    
    
    /**
     * <p>This method is equivalent to <code>addPaintable(0, o)</code>.</p>
     *
     * <p>Retained for backward compatibility.</p>
     *
     * <p>Fires property change: SET_PAINTABLE.</p>
     *
     * @param o the object to convert to a paintable to insert at the top
     */
    public final Paintable addPaintableAtTop(Object o) {
        return addPaintable(0, o);
    }
    
    
    /**
     * <p>Uses <code>makePaintable</code> to convert the given object to
     * a <code>Paintable</code> and then inserts this paintable at the
     * bottom-most position in the paintable sequence.</p>
     *
     * <p>Returns the <code>Paintable</code> inserted.</p>
     *
     * <p>This method is equivalent to <code>addPaintable(length(), o)</code>.</p>
     * 
     * <p>Fires property change: SET_PAINTABLE.</p>
     *
     * @param o the object to convert to a paintable to insert at the bottom
     */
    public final Paintable appendPaintable(Object o) {
        return addPaintable(Integer.MAX_VALUE, o);
    }
    
    
    /**
     * <p>Uses <code>makePaintable</code> to install the given array of
     * objects after suitable conversion into the paintable sequence
     * and removes the existing paintables in the sequence.</p>
     *
     * <p>Returns the array of paintable objects installed.</p>
     *
     * <p>Fires property change: REMOVE_PAINTABLE.</p>
     *
     * <p>Fires property change: SET_PAINTABLE.</p>
     * 
     * @param objects the array of objects to install
     */
    public final Paintable[] setSequence(Object[] objects) {
        clear();
        return appendSequence(objects);
    }
    
    
    /**
     * <p>Uses <code>makePaintable</code> to add the given array of
     * objects after suitable conversion into the paintable sequence
     * starting at the given index;
     * returns the array of paintable objects added.</p>
     *
     * <p>If the given index is less than 0, it is set to 0.</p>
     *
     * <p>If the given index is greater than <code>length()</code>, it is
     * set to <code>length()</code>.</p>
     *
     * <p>Fires property change: SET_PAINTABLE.</p>
     * 
     * @param index   the position in the sequence at which to add
     * @param objects the array of objects to add
     */
    public final Paintable[] addSequence(int index, Object[] objects) {
        if (objects == null)
            return null;
        
        int N = objects.length;
        
        Paintable[] paintables = new Paintable[N];
        
        int length = length();
        
        if (index < 0)
            index = 0;
        
        if (index > length)
            index = length;
        
        if (N > 0) {
            for (int i = 0; i < N; i++) {
                paintables[i] = makePaintable(objects[i]);
                addForwardingListener(paintables[i]);
                paintablesequence.add(index + i, paintables[i]);
            }
        
            firePropertyChange(SET_PAINTABLE, null, null);
        }
        
        return paintables;
    }
    
    
    /**
     * <p>This method is equivalent to <code>addSequence(int, Object)</code>
     * but interchanges the order of the arguments.</p>
     *
     * <p>Fires property change: SET_PAINTABLE.</p>
     *
     * @param objects the array of objects to add
     * @param index   the position in the sequence at which to add
     */
    public final Paintable[] addSequence(Object[] objects, int index) {
        return addSequence(index, objects);
    }
    
    
    /**
     * <p>Uses <code>makePaintable</code> to add the given array of
     * objects after suitable conversion to the top of the paintable
     * sequence.</p>
     *
     * <p>Returns the array of paintable objects added.</p>
     *
     * <p>This method is equivalent to
     * <code>addSequence(0, objects)</code>.</p>
     *
     * <p>Fires property change: SET_PAINTABLE.</p>
     * 
     * @param objects the array of objects to add at the top
     */
    public final Paintable[] addSequence(Object[] objects) {
        return addSequence(0, objects);
    }
    
    
    /**
     * <p>This method is equivalent to <code>addSequence(objects)</code>.</p>
     *
     * <p>Retained for backward compatibility.</p>
     *
     * <p>Fires property change: SET_PAINTABLE.</p>
     *
     * @param objects the array of objects to add at the top
     */
    public final Paintable[] addSequenceAtTop(Object[] objects) {
        return addSequence(0, objects);
    }
    
    
    /**
     * <p>Uses <code>makePaintable</code> to append the given array of
     * objects after suitable conversion onto the paintable sequence.</p>
     *
     * <p>Returns the array of paintable objects appended.</p>
     *
     * <p>This method is equivalent to
     * <code>addSequence(length(), objects)</code>.</p>
     *
     * <p>Fires property change: SET_PAINTABLE.</p>
     * 
     * @param objects the array of objects to append
     */
    public final Paintable[] appendSequence(Object[] objects) {
        return addSequence(Integer.MAX_VALUE, objects);
    }
    
    
    /**
     * <p>Removes the <code>Paintable</code> at the given index
     * in the paintable sequence; all objects in the sequence
     * at higher index positions are displaced to one index
     * position lower; returns the paintable removed.</p>
     *
     * <p>This method returns <code>null</code> if the index is
     * not in [0, length).</p>
     *
     * <p>This method may also return <code>null</code> if the
     * paintable at the given index is <code>null</code>.</p>
     *
     * <p>Fires property change: REMOVE_PAINTABLE.</p>
     * 
     * @param index the position of the item to remove
     */
    public final Paintable removePaintable(int index) {
        if ((index < 0) || (index >= length()))
            return null;
        
        Paintable paintable = (Paintable) paintablesequence.get(index);
        removeForwardingListener(paintable);
        
        paintablesequence.remove(index);
        
        firePropertyChange(REMOVE_PAINTABLE, null, null);
        
        return paintable;
    }
    
    
    /**
     * <p>Removes the given <code>Paintable</code> from the
     * paintable sequence.  All objects in the sequence at higher
     * index positions are displaced to one index position lower.</p>
     *
     * <p>This method does nothing if the <code>Paintable</code>
     * is not in the sequence.</p>
     *
     * <p>Place holder entries that were added as <code>null</code>
     * cannot be removed by this method.</p>
     *
     * <p>Fires property change: REMOVE_PAINTABLE.</p>
     * 
     * @param paintable the item to remove
     */
    public final void removePaintable(Paintable paintable) {
        removePaintable(getIndex(paintable));
    }
    
    
    /**
     * <p>Removes the sequence of paintables starting at the given index
     * m inclusive and ending at the given index n exclusive;
     * returns an array with the paintables removed.</p>
     *
     * <p>Forces m and n into range if needed.</p>
     *
     * <p>Returns an array of length 0 if no paintables are removed.</p>
     *
     * <p>Fires property change: REMOVE_PAINTABLE.</p>
     * 
     * @param m the starting index inclusive
     * @param n the ending index exclusive
     */
    public final Paintable[] removeSequence(int m, int n) {
        if (m < 0)
            m = 0;
        
        if (n > length())
            n = length();
        
        int c = n - m;
        
        if (c <= 0)
            return new Paintable[0];
        
        Paintable[] result = new Paintable[c];
        
        while (n > m) {
            c--;
            n--;
            
            Paintable paintable = (Paintable) paintablesequence.get(n);
            removeForwardingListener(paintable);
            
            paintablesequence.remove(n);
            
            result[c] = paintable;
        }
        
        firePropertyChange(REMOVE_PAINTABLE, null, null);
        
        return result;
    }
    
    
    /**
     * <p>Removes all paintables from this paintable sequence
     * and returns an array with the paintables removed.</p>
     *
     * <p>This method is equivalent to
     * <code>removeSequence(0,length())</code>.</p>
     *
     * <p>Fires property change: REMOVE_PAINTABLE.</p>
     */
    public final Paintable[] clearSequence() {
        return removeSequence(0, Integer.MAX_VALUE);
    }
    
    
    /**
     * <p>Removes all paintables from this paintable sequence
     * and returns an array with the paintables removed.</p>
     *
     * <p>This method is equivalent to <code>clearSequence()</code>.</p>
     *
     * <p>Retained for backward compatibility.</p>
     *
     * <p>Fires property change: REMOVE_PAINTABLE.</p>
     */
    public final Paintable[] clear() {
        return removeSequence(0, Integer.MAX_VALUE);
    }
    
    
    /**
     * <p>Shift the paintable at index i to index j and shift
     * all paintables in between as needed.</p>
     *
     * <p>Does nothing if the indices i and j are not in the
     * range [0, length) and also does nothing if i == j.</p>
     *
     * <p>Fires property change: SHIFT_PAINTABLE.</p>
     * 
     * @param i the current position of a paintable
     * @param j the new position of the shifted paintable
     */
    public final void shiftPaintable(int i, int j) {
        int length = length();
        
        if ((i < 0) || (i >= length))
            return;
        
        if ((j < 0) || (j >= length))
            return;
        
        if (i == j)
            return;
        
        // remove item at i and then add item at j
        // if j > i must first adjust j
        
        if (j > i) j--;
        
        Paintable paintable = (Paintable) paintablesequence.get(i);
        paintablesequence.remove(i);
        paintablesequence.add(j, paintable);
        
        firePropertyChange(SHIFT_PAINTABLE, null, null);
    }
    
    
    /**
     * <p>Shift the paintable at index i to the top and shift
     * all paintables in between as needed.</p>
     *
     * <p>Does nothing if the index i is not in the range
     * [0, length) and also does nothing if i == 0.</p>
     *
     * <p>Fires property change: SHIFT_PAINTABLE.</p>
     * 
     * @param i the current position of a paintable
     */
    public final void shiftPaintableToTop(int i) {
        shiftPaintable(i, 0);
    }
    
    
    /**
     * <p>Shift the paintable at index i to the bottom and shift
     * all paintables in between as needed.</p>
     *
     * <p>Does nothing if the index i is not in the range
     * [0, length) and also does nothing if i == (length-1).</p>
     *
     * <p>Fires property change: SHIFT_PAINTABLE.</p>
     * 
     * @param i the current position of a paintable
     */
    public final void shiftPaintableToBottom(int i) {
        shiftPaintable(i, length() - 1);
    }
    
    
    /**
     * <p>Shift the paintable at index i one index closer to the
     * top.</p>
     *
     * <p>Does nothing if the index i is not in the range
     * [0, length) and also does nothing if i == 0.</p>
     *
     * <p>Fires property change: SHIFT_PAINTABLE.</p>
     * 
     * @param i the current position of a paintable
     */
    public final void shiftPaintableUp(int i) {
        shiftPaintable(i, i-1);
    }
    
    
    /**
     * <p>Shift the paintable at index i one index closer to the
     * bottom.</p>
     *
     * <p>Does nothing if the index i is not in the range
     * [0, length) and also does nothing if i == (length-1).</p>
     *
     * <p>Fires property change: SHIFT_PAINTABLE.</p>
     * 
     * @param i the current position of a paintable
     */
    public final void shiftPaintableDown(int i) {
        shiftPaintable(i, i+1);
    }
    
    
    /**
     * <p>Shift the given paintable to index i and shift
     * all paintables in between as needed.</p>
     *
     * <p>Does nothing if the given paintable is not in
     * the sequence.</p>
     *
     * <p>Does nothing if index i is not in the range [0, length)
     * and also does nothing if the given paintable is already
     * at index i.</p>
     *
     * <p>Fires property change: SHIFT_PAINTABLE.</p>
     * 
     * @param paintable the paintable to shift
     * @param i the new position of the shifted paintable
     */
    public final void shiftPaintable(Paintable paintable, int i) {
        shiftPaintable(getIndex(paintable), i);
    }
    
    
    /**
     * <p>Shift the given paintable to the top and shift
     * other paintables as needed.</p>
     *
     * <p>Does nothing if the given paintable is not in
     * the sequence or is already at the top.</p>
     *
     * <p>Fires property change: SHIFT_PAINTABLE.</p>
     * 
     * @param paintable the paintable to shift
     */
    public final void shiftPaintableToTop(Paintable paintable) {
        shiftPaintable(getIndex(paintable), 0);
    }
    
    
    /**
     * <p>Shift the given paintable to the bottom and
     * shift other paintables as needed.</p>
     *
     * <p>Does nothing if the given paintable is not in
     * the sequence or is already at the bottom.</p>
     *
     * <p>Fires property change: SHIFT_PAINTABLE.</p>
     * 
     * @param paintable the paintable to shift
     */
    public final void shiftPaintableToBottom(Paintable paintable) {
        shiftPaintable(getIndex(paintable), length() - 1);
    }
    
    
    /**
     * <p>Shift the given paintable one index closer to the
     * top and shift other paintables as needed.</p>
     *
     * <p>Does nothing if the given paintable is not in
     * the sequence or is already at the top.</p>
     *
     * <p>Fires property change: SHIFT_PAINTABLE.</p>
     * 
     * @param paintable the paintable to shift
     */
    public final void shiftPaintableUp(Paintable paintable) {
        int index = getIndex(paintable);
        shiftPaintable(index, index - 1);
    }
    
    
    /**
     * <p>Shift the given paintable one index closer to the
     * bottom and shift other paintables as needed.</p>
     *
     * <p>Does nothing if the given paintable is not in
     * the sequence or is already at the bottom.</p>
     *
     * <p>Fires property change: SHIFT_PAINTABLE.</p>
     * 
     * @param paintable the paintable to shift
     */
    public final void shiftPaintableDown(Paintable paintable) {
        int index = getIndex(paintable);
        shiftPaintable(index, index + 1);
    }
    
    
    /**
     * Returns an array with the <code>Paintable</code> items in
     * this paintable sequence.
     */
    public final Paintable[] toArray() {
        return (Paintable[])
            paintablesequence.toArray(new Paintable[0]);
    }
    
    
    /**
     * <p>Set the mutator strategy usage to an allowable setting.</p>
     *
     * <p>The allowable settings are:</p>
     *
     * <ul>
     *   <li>MUTATE_AS_GROUP: mutate the sequence as a group</li>
     *   <li>MUTATE_AS_ITEMS: mutate the individual sequence items.</li>
     * </ul>
     *
     * @param usage the mutator strategy usage
     */
    public final void setMutatorStrategyUsage(Mutator.StrategyUsage usage) {
        if (usage != null)
            this.usage = usage;
    }
    
    
    /**
     * <p>Returns the current mutator strategy usage.</p>
     */
    public final Mutator.StrategyUsage getMutatorStrategyUsage() {
        return usage;
    }
    
    
    /**
     * <p>Sets the mutator strategy usage to MUTATE_AS_GROUP.</p>
     */
    public final void setMutateAsGroup() {
        usage = Mutator.MUTATE_AS_GROUP;
    }
    
    
    /**
     * <p>Sets the mutator strategy usage to MUTATE_AS_ITEMS.</p>
     */
    public final void setMutateAsItems() {
        usage = Mutator.MUTATE_AS_ITEMS;
    }
    
    
    /**
     * <p>If the point specified by coordinates is inside one of the items
     * in the <code>PaintableSequence</code> then this method returns the
     * appropriate <code>Paintable</code> object depending on the
     * current <code>Mutator.StrategyUsage</code> setting.</p>
     *
     * <p>If the strategy usage setting is <code>MUTATE_AS_ITEMS</code>,
     * then this method returns the same <code>Paintable</code>
     * as the method <code>hitsItem</code>.</p>
     * 
     * <p>If the strategy usage setting is <code>MUTATE_AS_GROUP</code>,
     * then this method returns a reference to this paintable sequence.
     *
     * <p>If the point is not inside any item of the paintable sequence,
     * then this method returns <code>null</code>.</p>
     *
     * @param  x the x-coordinate of the point
     * @param  y the y-coordinate of the point
     */
    public final Paintable hits(double x, double y) {
        Paintable paintable = hitsItem(x, y);
        
        if (paintable == null)
            return null;
        
        if (usage == Mutator.MUTATE_AS_ITEMS)
            return paintable;
        else
            return this;
    }
    
    
    /**
     * <p>If the point is inside one of the items
     * in the <code>PaintableSequence</code> then this method returns the
     * appropriate <code>Paintable</code> object depending on the
     * current <code>Mutator.StrategyUsage</code> setting.</p>
     *
     * <p>If the strategy usage setting is <code>MUTATE_AS_ITEMS</code>,
     * then this method returns the same <code>Paintable</code>
     * as the method <code>hitsItem</code>.</p>
     * 
     * <p>If the strategy usage setting is <code>MUTATE_AS_GROUP</code>,
     * then this method returns a reference to this paintable sequence.
     *
     * <p>If the point is not inside any item of the paintable sequence,
     * then this method returns <code>null</code>.</p>
     *
     * @param  p a specified <code>Point2D</code>
     */
    public final Paintable hits(Point2D p) {
        if (p == null)
            return null;
        
        return hits(p.getX(), p.getY());
    }
    
    
    /**
     * <p>If the point specified by coordinates is inside one of the items
     * in the <code>PaintableSequence</code> then this method returns
     * the top-most <code>Paintable</code> item that contains
     * the point; otherwise this method returns <code>null</code>.</p>
     *
     * <p>This method returns <code>null</code> if one or more of the
     * following conditions occurs:</p>
     *
     * <ul>
     *   <li>The paintable sequence has no items.</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
     */
    public final Paintable hitsItem(double x, double y) {
        return getPaintable(hitsItemAtIndex(x, y));
    }
    
    
    /**
     * <p>If the point is inside one of the items
     * in the <code>PaintableSequence</code> then this method returns
     * the top-most <code>Paintable</code> item that contains
     * the point; otherwise this method returns <code>null</code>.</p>
     *
     * <p>This method returns <code>null</code> if one or more of the
     * following conditions occurs:</p>
     *
     * <ul>
     *   <li>The point is <code>null</code>.</li>
     *   <li>The paintable sequence has no items.</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  p a specified <code>Point2D</code>
     */
    public final Paintable hitsItem(Point2D p) {
        if (p == null)
            return null;
        
        return hitsItem(p.getX(), p.getY());
    }
    
    
    /**
     * <p>If the point specified by coordinates is inside one of the items
     * in the <code>PaintableSequence</code> then this method returns
     * the index of the top-most <code>Paintable</code> item that contains
     * the point; otherwise this method returns <code>-1</code>.</p>
     *
     * <p>This method returns <code>-1</code> if one or more of the
     * following conditions occurs:</p>
     *
     * <ul>
     *   <li>The paintable sequence has no items.</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
     */
    public final int hitsItemAtIndex(double x, double y) {
        int N = length();
        
        if ((N == 0) || (!possiblyContains(x, y)))
            return -1;
        
        for (int i = 0; i < N; i++) {
            Paintable paintable = getPaintable(i);
            
            if (paintable != null)
                if (paintable.contains(x, y))
                    return i;
        }
        
        return -1;
    }
    
    
    /**
     * <p>If the point is inside one of the items
     * in the <code>PaintableSequence</code> then this method returns
     * the index of the top-most <code>Paintable</code> item that contains
     * the point; otherwise this method returns <code>-1</code>.</p>
     *
     * <p>This method returns <code>-1</code> if one or more of the
     * following conditions occurs:</p>
     *
     * <ul>
     *   <li>The point is <code>null</code>.</li>
     *   <li>The paintable sequence has no items.</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  p a specified <code>Point2D</code>
     */
    public final int hitsItemAtIndex(Point2D p) {
        if (p == null)
            return -1;
        
        return hitsItemAtIndex(p.getX(), p.getY());
    }
    
    
}
