/*
 *   
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package javax.microedition.lcdui.game;

import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Display;


/**
 * The LayerManager manages a series of Layers.  The LayerManager simplifies
 * the process of rendering the Layers that have been added to it by
 * automatically rendering the correct regions of each Layer in the
 * appropriate order.
 * <p>
 * The LayerManager maintains an ordered list to which Layers can be appended,
 * inserted and removed.  A Layer's index correlates to its z-order; the layer
 * at index 0 is closest to the user while a the Layer with the highest index
 * is furthest away from the user.  The indices are always contiguous; that
 * is, if a Layer is removed, the indices of subsequent Layers will be
 * adjusted to maintain continuity.
 * <p>
 * The LayerManager class provides several features that control how the
 * game's Layers are rendered on the screen.
 * <p>
 * The <em>view window</em> controls the size of the visible region and its
 * position relative to the LayerManager's coordinate system.  Changing the
 * position of the view window enables effects such as scrolling or panning
 * the user's view.  For example, to scroll to the right, simply move the view
 * window's location to the right.  The size of the view window controls how
 * large the user's view will be, and is usually fixed at a size that is
 * appropriate for the device's screen.
 * <P>
 * In this example, the view window is set to 85 x 85 pixels and is located at
 * (52, 11) in the LayerManager's coordinate system.  The Layers appear at
 * their respective positions relative to the LayerManager's origin.
 * <br>
 * <center><img src="doc-files/viewWindow.gif" width=558 height=292
 * ALT="Specifying the View Window"></center>
 * <br>
 * <p>
 * The {@link #paint(Graphics, int, int)} method includes an (x,y) location
 * that controls where the view window is rendered relative to the screen.
 * Changing these parameters does not change the contents of the view window,
 * it simply changes the location where the view window is drawn.  Note that
 * this location is relative to the origin of the Graphics object, and thus
 * it is subject to the translation attributes of the Graphics object.
 * <P>
 * For example, if a game uses the top of the screen to display the current
 * score, the view window may be rendered at (17, 17) to provide enough space
 * for the score.
 * <br>
 * <center><img src="doc-files/drawWindow.gif" width=321 height=324
 * ALT="Drawing the View Window"></center>
 * <br>
 * <p>
 **/
public class LayerManager {

    /**
     * Creates a new LayerManager.
     */
    public LayerManager() {
        setViewWindow(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
    }

    /**
     * Appends a Layer to this LayerManager.  The Layer is appended to the
     * list of existing Layers such that it has the highest index (i.e. it
     * is furthest away from the user).  The Layer is first removed
     * from this LayerManager if it has already been added.
     *
     * @see #insert(Layer, int)
     * @see #remove(Layer)
     * @param l the <code>Layer</code> to be added
     * @throws NullPointerException if the <code>Layer</code> is
     * <code>null</code>
     */
    public void append(Layer l) {
	// remove the Layer if it is already present
	// will throw NullPointerException if the Layer is null
	removeImpl(l);
        addImpl(l, nlayers);
    }

    /**
     * Inserts a new Layer in this LayerManager at the specified index.
     * The Layer is first removed from this LayerManager if it has already
     * been added.
     * @see #append(Layer)
     * @see #remove(Layer)
     * @param l the <code>Layer</code> to be inserted
     * @param index the index at which the new <code>Layer</code> is
     * to be inserted
     * @throws NullPointerException if the <code>Layer</code> is
     * <code>null</code>
     * @throws IndexOutOfBoundsException if the index is less than
     * <code>0</code> or
     * greater than the number of Layers already added to the this or
     * if the index is greater than the number of Layers already added
     * to this LayerManager minus one and the Layer has already been added
     * to this LayerManager
     * <code>LayerManager</code>
     */
    public void insert(Layer l, int index) {
	/* Check for correct arguments:  index in bounds	     */
	if ((index < 0) || (index > nlayers) || (exist(l) && (index >= nlayers))) {
	    throw new IndexOutOfBoundsException();
	}

    // if the Layer is already present
	// remove it
	// will throw NullPointerException if the Layer is null
	removeImpl(l);
	// insert it at the specified index
	addImpl(l, index);
    }

    /**
     * Gets the Layer with the specified index.
     * @param index the index of the desired Layer
     * @return the Layer that has the specified index
     * @throws IndexOutOfBoundsException if the specified
     * <code>index</code> is less than
     * zero, or if it is equal to or greater than the number of Layers added
     * to the this <code>LayerManager</code>
     **/
    public Layer getLayerAt(int index) {
	if ((index < 0) || (index >= nlayers)) {
	    throw new IndexOutOfBoundsException();
	}
	return component[index];
    }

    /**
     * Gets the number of Layers in this LayerManager.
     * <p>
     * @return the number of Layers
     */
    public int getSize() {
	return nlayers;
    }

    /**
     * Removes the specified Layer from this LayerManager.  This method does
     * nothing if the specified Layer is not added to the this LayerManager.
     * @see #append(Layer)
     * @see #insert(Layer, int)
     * @param l the <code>Layer</code> to be removed
     * @throws NullPointerException if the specified <code>Layer</code> is
     * <code>null</code>
     */
    public void remove(Layer l) {
	removeImpl(l);
    }

    /**
     * Renders the LayerManager's current view window at the specified
     * location.
     * <p>
     * The LayerManager renders each of its layers in order of descending
     * index, thereby implementing the correct z-order.  Layers that are
     * completely outside of the view window are not rendered.
     * <p>
     * The coordinates passed to this method determine where the
     * LayerManager's view window will be rendered relative to the origin
     * of the Graphics object.  For example, a game may use the top of the
     * screen to display the current score, so to render the game's layers
     * below that area, the view window might be rendered at (0, 20).  The
     * location is relative to the Graphics object's origin, so translating
     * the Graphics object will change where the view window is rendered on
     * the screen.
     * <p>
     * The clip region of the Graphics object is intersected with a region
     * having the same dimensions as the view window and located at (x,y).
     * The LayerManager then translates the graphics object such that the
     * point (x,y) corresponds to the location of the viewWindow in the
     * coordinate system of the LayerManager.  The Layers are then rendered
     * in the appropriate order.  The translation and clip region of the
     * Graphics object are restored to their prior values before this method
     * returns.
     * <p>
     * Rendering is subject to the clip region and translation of the Graphics
     * object.  Thus, only part of the specified view window may be rendered
     * if the clip region is not large enough.
     * <p>
     * For performance reasons, this method may ignore Layers that are
     * invisible or that would be rendered entirely outside of the Graphics
     * object's clip region.  The attributes of the Graphics object are not
     * restored to a known state between calls to the Layers' paint methods.
     * The clip region may extend beyond the bounds of a Layer; it is the
     * responsibility of the Layer to ensure that rendering operations are
     * performed within its bounds.
     * <p>
     * @see #setViewWindow
     * @param g the graphics instance with which to draw the LayerManager
     * @param x the horizontal location at which to render the view window,
     * relative to the Graphics' translated origin
     * @param y the vertical location at which to render the view window,
     * relative to the Graphics' translated origin
     * @throws NullPointerException if <code>g</code> is <code>null</code>
     */
    public void paint(Graphics g, int x, int y) {

        // if g == null g.getClipX will throw NullPointerException;

        // save the original clip
        int clipX = g.getClipX();
	int clipY = g.getClipY();
        int clipW = g.getClipWidth();
        int clipH =  g.getClipHeight();


	// translate the LayerManager co-ordinates to Screen co-ordinates
	g.translate(x - viewX, y - viewY);
        // set the clip to view window
        g.clipRect(viewX, viewY, viewWidth, viewHeight);

	// draw last to first
	for (int i = nlayers; --i >= 0; ) {
            Layer comp = component[i];
	    if (comp.visible) {
                // IMPL NOTE: do this if outside Graphics clip region don't paint
		// (comp.contains(x - comp.x, y - comp.y)) &&
	        // paint will happen only in clipped region of view window
	        comp.paint(g);
	    }
	}

	// restore Screen co-ordinates origin and clip

	g.translate(-x + viewX, -y + viewY);
        g.setClip(clipX, clipY, clipW, clipH);
    }

    /**
     * Sets the view window on the LayerManager.
     * <p>
     * The view window specifies the region that the LayerManager draws when
     * its {@link #paint} method is called.  It allows the developer to
     * control the size of the visible region, as well as the location of the
     * view window relative to the LayerManager's coordinate system.
     * <p>
     * The view window stays in effect until it is modified by another call
     * to this method.  By default, the view window is located at (0,0) in
     * the LayerManager's coordinate system and its width and height are both
     * set to Integer.MAX_VALUE.
     *
     * @param x the horizontal location of the view window relative to the
     * LayerManager's origin
     * @param y the vertical location of the view window relative to the
     * LayerManager's origin
     * @param width the width of the view window
     * @param height the height of the view window
     * @throws IllegalArgumentException if the <code>width</code> or
     * <code>height</code> is less than <code>0</code>
     */
    public void setViewWindow(int x, int y, int width, int height) {

	if (width < 0 || height < 0) {
	    throw new IllegalArgumentException();
	}

	viewX = x;
        viewY = y;
        viewWidth = width;
        viewHeight = height;
    }

    /**
     * add or insert a layer
     * @param layer      The Layer to be inserted
     * @param index  the position at which to insert the layer
     */
    private void addImpl(Layer layer, int index) {
	// Add component to list;
	// allocate new array if necessary.

	if (nlayers == component.length) {
	    Layer newcomponents[] = new Layer[nlayers + 4];
	    System.arraycopy(component, 0, newcomponents, 0, nlayers);
            System.arraycopy(component, index, newcomponents,
                             index + 1, nlayers - index);
	    component = newcomponents;
	} else {
	    System.arraycopy(component, index, component,
				 index + 1, nlayers - index);
	}

	component[index] = layer;
	nlayers++;
    }

    /**
     * Removes the specified Layer from this LayerManager.
     * @param l The Layer to be removed
     * @throws NullPointerException if the specified Layer is null
     **/
    private void removeImpl(Layer l) {
        if (l == null) {
            throw new NullPointerException();
        }
        /**
         * Search backwards, expect that more recent additions
	 * are more likely to be removed.
         */

	for (int i = nlayers; --i >= 0; ) {
	    if (component[i] == l) {
		    remove(i);
	    }
	}
    }

    /**
     * Check existence the specified Layer in this LayerManager.
     * @param l The Layer to be check
     * @return true if Layer exist
     **/
    private boolean exist(Layer l) {
        if (l == null) {
            return false;
        }

        for (int i = nlayers; --i >= 0; ) {
            if (component[i] == l) {
                return true;
            }
        }
        return false;
    }

    /**
     * remove a layer at the specified index.
     * @param     index    the position at which to insert the layer,
     */
    private void remove(int index) {
	    System.arraycopy(component, index + 1,
			     component, index,
			     nlayers - index - 1);
	    component[--nlayers] = null;
    }

    /**
     * The number of layers in this LayerManager.
     * This value can be null.
     */
    private int nlayers; // = 0;

    /** 
     * The Layers in this LayerManager.
     * @see #append(Layer)
     */
    private Layer component[] = new Layer[4];

    /**
     * the view window co-ordinates.
     */
    private int viewX, viewY, viewWidth, viewHeight; // = 0;

}


