/*
 *   
 *
 * 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;

/**
 * A TiledLayer is a visual element composed of a grid of cells that
 * can be filled with a set of
 * tile images.  This class allows large virtual layers to be created
 * without the need for an
 * extremely large Image.  This technique is commonly used in 2D
 * gaming platforms to create
 * very large scrolling backgrounds,
 * <P>
 * <h3>Tiles</h3>
 * The tiles used to fill the TiledLayer's cells are provided in a
 * single Image object which
 * may be mutable or immutable.  The Image is broken up into a series
 * of equally-sized tiles;
 * the tile size is specified along with the Image.  As shown in the
 * figure below, the same
 * tile set can be stored in several different arrangements depending
 * on what is the most
 * convenient for the game developer.  
 * <br>
 * <center><img src="doc-files/tiles.gif" width=588 height=412
 *   ALT="Tiles"></center>
 * <br>
 * Each tile is assigned a unique index number.  The tile located in
 * the upper-left corner
 * of the Image is assigned an index of 1.  The remaining tiles are
 * then numbered consecutively
 * in row-major order (indices are assigned across the first row, then
 * the second row, and so on).
 * These tiles are regarded as <em>static tiles</em> because there is
 * a fixed link between
 * the tile and the image data associated with it.
 * <P> 
 * A static tile set is created when the TiledLayer is instantiated;
 * it can also be updated
 * at any time using the {@link #setStaticTileSet} method.
 * <P>
 * In addition to the static tile set, the developer can also define
 * several <em>animated tiles</em>.
 * An animated tile is a virtual tile that is dynamically associated
 * with a static tile; the appearance
 * of an animated tile will be that of the static tile that it is
 * currently associated with.
 * <P>
 * Animated tiles allow the developer to change the appearance of a
 * group of cells
 * very easily.  With the group of cells all filled with the animated
 * tile, the appearance
 * of the entire group can be changed by simply changing the static
 * tile associated with the
 * animated tile.  This technique is very useful for animating large
 * repeating areas without
 * having to explicitly change the contents of numerous cells.
 * <P>
 * Animated tiles are created using the {@link #createAnimatedTile}
 * method, which returns the
 * index to be used for the new animated tile.  The animated tile
 * indices are always negative
 * and consecutive, beginning with -1.  Once created, the static tile
 * associated with an
 * animated tile can be changed using the {@link #setAnimatedTile}
 * method.
 * <P>
 * <h3>Cells</h3>
 * The TiledLayer's grid is made up of equally sized cells; the number
 * of rows and
 * columns in the grid are specified in the constructor, and the
 * physical size of the cells
 * is defined by the size of the tiles.  
 * <P>
 * The contents of each cell is specified by means of a tile index; a
 * positive tile index refers
 * to a static tile, and a negative tile index refers to an animated
 * tile.  A tile index of 0
 * indicates that the cell is empty; an empty cell is fully
 * transparent and nothing is drawn
 * in that area by the TiledLayer.  By default, all cells contain tile
 * index 0.
 * <P>
 * The contents of cells may be changed using {@link #setCell} and
 * {@link #fillCells}.  Several
 * cells may contain the same tile; however, a single cell cannot
 * contain more than one tile.
 * The following example illustrates how a simple background can be
 * created using a TiledLayer.
 * <br>
 * <center><img src="doc-files/grid.gif" width=735 height=193
 * ALT="TiledLayer Grid"></center>
 * <br>
 * In this example, the area of water is filled with an animated tile
 * having an index of -1, which
 * is initially associated with static tile 5.  The entire area of
 * water may be animated by simply
 * changing the associated static tile using <code>setAnimatedTile(-1,
 * 7)</code>.
 * <br>
 * <center><img src="doc-files/grid2.gif" width=735 height=193
 * ALT="TiledLayer Grid 2"></center>
 * <br>
 * <P>
 * <h3>Rendering a TiledLayer</h3>
 * A TiledLayer can be rendered by manually calling its paint method;
 * it can also be rendered
 * automatically using a LayerManager object.
 * <P>
 * The paint method will attempt to render the entire TiledLayer
 * subject to the
 * clip region of the Graphics object; the upper left corner of the
 * TiledLayer is rendered at
 * its current (x,y) position relative to the Graphics object's
 * origin.  The rendered region
 * may be controlled by setting the clip region of the Graphics object
 * accordingly.
 * <P>
 */
public class TiledLayer extends Layer {

    /**
     * Creates a new TiledLayer.  <p>
     *
     * The TiledLayer's grid will be <code>rows</code> cells high and
     * <code>columns</code> cells wide.  All cells in the grid are initially
     * empty (i.e. they contain tile index 0).  The contents of the grid may
     * be modified through the use of {@link #setCell} and {@link #fillCells}.
     * <P>
     * The static tile set for the TiledLayer is created from the specified
     * Image with each tile having the dimensions of tileWidth x tileHeight.
     * The width of the source image must be an integer multiple of
     * the tile width, and the height of the source image must be an integer
     * multiple of the tile height; otherwise, an IllegalArgumentException
     * is thrown;<p>
     *
     * The entire static tile set can be changed using 
     * {@link  #setStaticTileSet(Image, int, int)}.
     * These methods should be used sparingly since they are both
     * memory and time consuming.
     * Where possible, animated tiles should be used instead to
     * animate tile appearance.<p>
     *
     * @param columns the width of the <code>TiledLayer</code>,
     * expressed as a number of cells
     * @param rows the height of the <code>TiledLayer</code>,
     * expressed as a number of cells
     * @param image the <code>Image</code> to use for creating
     *  the static tile set
     * @param tileWidth the width in pixels of a single tile
     * @param tileHeight the height in pixels of a single tile
     * @throws NullPointerException if <code>image</code> is <code>null</code>
     * @throws IllegalArgumentException if the number of <code>rows</code>
     *  or <code>columns</code> is less than <code>1</code>
     * @throws IllegalArgumentException if <code>tileHeight</code>
     *  or <code>tileWidth</code> is less than <code>1</code>
     * @throws IllegalArgumentException if the <code>image</code>
     *  width is not an integer multiple of the <code>tileWidth</code>
     * @throws IllegalArgumentException if the <code>image</code>
     * height is not an integer multiple of the <code>tileHeight</code>
     */
    public TiledLayer(int columns, int rows, Image image, int tileWidth,
		      int tileHeight) {
	// IllegalArgumentException will be thrown 
	// in the Layer super-class constructor
        super(columns < 1 || tileWidth < 1 ? -1 : columns * tileWidth, 
	         rows < 1 || tileHeight < 1 ? -1 : rows * tileHeight);

        // if img is null img.getWidth() will throw NullPointerException
        if (((image.getWidth() % tileWidth) != 0) || 
            ((image.getHeight() % tileHeight) != 0)) {
             throw new IllegalArgumentException();
	}
        this.columns = columns;
	this.rows = rows;

        cellMatrix = new int[rows][columns];

        int noOfFrames = 
            (image.getWidth() / tileWidth) * (image.getHeight() / tileHeight);
        // the zero th index is left empty for transparent tile
        // so it is passed in  createStaticSet as noOfFrames + 1
        // Also maintain static indices is true
	// all elements of cellMatrix[][] 
	// are set to zero by new, so maintainIndices = true
        createStaticSet(image,  noOfFrames + 1, tileWidth, tileHeight, true);
    }

    /**
     * Creates a new animated tile and returns the index that refers
     * to the new animated tile.  It is initially associated with
     * the specified tile index (either a static tile or 0).
     * <P>
     * The indices for animated tiles are always negative.  The first
     * animated tile shall have the index -1, the second, -2, etc.  
     *
     * @param staticTileIndex the index of the associated tile 
     * (must be <code>0</code> or a valid static tile index)
     * @return the index of newly created animated tile
     * @throws IndexOutOfBoundsException if the 
     * <code>staticTileIndex</code> is invalid
     */
    public int createAnimatedTile(int staticTileIndex) {
        // checks static tile 
        if (staticTileIndex < 0 || staticTileIndex >= numberOfTiles) { 
	    throw new IndexOutOfBoundsException();
	}

        if (anim_to_static == null) {
	    anim_to_static = new int[4];
	    numOfAnimTiles = 1;
        } else if (numOfAnimTiles == anim_to_static.length) {
	    // grow anim_to_static table if needed 
	    int new_anim_tbl[] = new int[anim_to_static.length * 2];
	    System.arraycopy(anim_to_static, 0, 
                         new_anim_tbl, 0, anim_to_static.length);
	    anim_to_static = new_anim_tbl;
	}
	anim_to_static[numOfAnimTiles] = staticTileIndex;
	numOfAnimTiles++;
        return (-(numOfAnimTiles - 1));
    }

    /**
     * Associates an animated tile with the specified static tile.  <p>
     *
     * @param animatedTileIndex the index of the animated tile
     * @param staticTileIndex the index of the associated tile
     * (must be <code>0</code> or a valid static tile index)
     * @throws IndexOutOfBoundsException if the 
     * <code>staticTileIndex</code> is invalid
     * @throws IndexOutOfBoundsException if the animated tile index
     * is invalid
     * @see #getAnimatedTile
     *
     */
    public void setAnimatedTile(int animatedTileIndex, int staticTileIndex) {
        // checks static tile
        if (staticTileIndex < 0 || staticTileIndex >= numberOfTiles) {  
	    throw new IndexOutOfBoundsException();
	}
        // do animated tile index check
	animatedTileIndex = - animatedTileIndex;
	if (anim_to_static == null || animatedTileIndex <= 0 
            || animatedTileIndex >= numOfAnimTiles) { 
	    throw new IndexOutOfBoundsException();
        }

        anim_to_static[animatedTileIndex] = staticTileIndex;

    }

    /**
     * Gets the tile referenced by an animated tile.  <p>
     *
     * Returns the tile index currently associated with the
     * animated tile.
     *
     * @param animatedTileIndex the index of the animated tile
     * @return the index of the tile reference by the animated tile
     * @throws IndexOutOfBoundsException if the animated tile index
     * is invalid
     * @see #setAnimatedTile
     */
    public int getAnimatedTile(int animatedTileIndex) {
        animatedTileIndex = - animatedTileIndex;
        if (anim_to_static == null || animatedTileIndex <= 0 
                   || animatedTileIndex >= numOfAnimTiles) { 
	    throw new IndexOutOfBoundsException();
        }
	
        return anim_to_static[animatedTileIndex];
    }

    /**
     * Sets the contents of a cell.  <P>
     *
     * The contents may be set to a static tile index, an animated
     * tile index, or it may be left empty (index 0)
     * @param col the column of cell to set
     * @param row the row of cell to set
     * @param tileIndex the index of tile to place in cell
     * @throws IndexOutOfBoundsException if there is no tile with index
     *         <code>tileIndex</code>
     * @throws IndexOutOfBoundsException if <code>row</code> or
     *         <code>col</code> is outside the bounds of the 
     *         <code>TiledLayer</code> grid
     * @see #getCell
     * @see #fillCells
     */
    public void setCell(int col, int row, int tileIndex) {

        if (col < 0 || col >= this.columns || row < 0 || row >= this.rows) {
            throw new IndexOutOfBoundsException();
        }

	if (tileIndex > 0) {
            // do checks for static tile 
            if (tileIndex >= numberOfTiles) { 
	        throw new IndexOutOfBoundsException();
	    }
	} else if (tileIndex < 0) {
            // do animated tile index check
	    if (anim_to_static == null ||
                (-tileIndex) >= numOfAnimTiles) { 
	        throw new IndexOutOfBoundsException();
            }
	}

        cellMatrix[row][col] = tileIndex;
 
    }

    /**
     * Gets the contents of a cell.  <p>
     *
     * Gets the index of the static or animated tile currently displayed in
     * a cell.  The returned index will be 0 if the cell is empty.
     *
     * @param col the column of cell to check
     * @param row the row of cell to check
     * @return the index of tile in cell
     * @throws IndexOutOfBoundsException if <code>row</code> or
     *         <code>col</code> is outside the bounds of the 
     *         <code>TiledLayer</code> grid
     * @see #setCell
     * @see #fillCells
     */
    public int getCell(int col, int row) {
        if (col < 0 || col >= this.columns || row < 0 || row >= this.rows) {
            throw new IndexOutOfBoundsException();
        }
        return cellMatrix[row][col];
    }

    /**
     * Fills a region cells with the specific tile.  The cells may be filled
     * with a static tile index, an animated tile index, or they may be left 
     * empty (index <code>0</code>). 
     *
     * @param col the column of top-left cell in the region
     * @param row the row of top-left cell in the region
     * @param numCols the number of columns in the region
     * @param numRows the number of rows in the region
     * @param tileIndex the Index of the tile to place in all cells in the 
     * specified region
     * @throws IndexOutOfBoundsException if the rectangular region
     *         defined by the parameters extends beyond the bounds of the
     *         <code>TiledLayer</code> grid
     * @throws IllegalArgumentException if <code>numCols</code> is less
     * than zero
     * @throws IllegalArgumentException if <code>numRows</code> is less
     * than zero
     * @throws IndexOutOfBoundsException if there is no tile with
     *         index <code>tileIndex</code>
     * @see #setCell
     * @see #getCell
     */
    public void fillCells(int col, int row, int numCols, int numRows,
                          int tileIndex) {


	if (numCols < 0 || numRows < 0) {
            throw new IllegalArgumentException();
	}

        if (col < 0 || col >= this.columns || row < 0 || row >= this.rows ||
	    col + numCols > this.columns || row + numRows > this.rows) {
            throw new IndexOutOfBoundsException();
        }

	if (tileIndex > 0) {
            // do checks for static tile 
            if (tileIndex >= numberOfTiles) { 
	        throw new IndexOutOfBoundsException();
	    }
	} else if (tileIndex < 0) {
            // do animated tile index check
	    if (anim_to_static == null || 
                (-tileIndex) >= numOfAnimTiles) { 
	            throw new IndexOutOfBoundsException();
            }
	}

        for (int rowCount = row; rowCount < row + numRows; rowCount++) {
            for (int columnCount = col; 
                     columnCount < col + numCols; columnCount++) {
                cellMatrix[rowCount][columnCount] = tileIndex;
            }
        }
    }


    /**
     * Gets the width of a single cell, in pixels.
     * @return the width in pixels of a single cell in the 
     * <code>TiledLayer</code> grid
     */
    public final int getCellWidth() {
        return cellWidth;
    }

    /**
     * Gets the height of a single cell, in pixels.
     * @return the height in pixels of a single cell in the 
     * <code>TiledLayer</code> grid
     */
    public final int getCellHeight() {
        return cellHeight;
    }

    /**
     * Gets the number of columns in the TiledLayer grid. 
     * The overall width of the TiledLayer, in pixels, 
     * may be obtained by calling {@link #getWidth}.
     * @return the width in columns of the 
     * <code>TiledLayer</code> grid
     */
    public final int getColumns() {
        return columns;
    }

    /**
     * Gets the number of rows in the TiledLayer grid.  The overall
     * height of the TiledLayer, in pixels, may be obtained by
     * calling {@link #getHeight}.
     * @return the height in rows of the 
     * <code>TiledLayer</code> grid
     */
    public final int getRows() {
        return rows;
    }

    /**
     * Change the static tile set.  <p>
     *
     * Replaces the current static tile set with a new static tile set.
     * See the constructor {@link #TiledLayer(int, int, Image, int, int)}
     * for information on how the tiles are created from the
     * image.<p>
     *
     * If the new static tile set has as many or more tiles than the
     * previous static tile set,
     * the the animated tiles and cell contents will be preserve.  If
     * not, the contents of
     * the grid will be cleared (all cells will contain index 0) and
     * all animated tiles
     * will be deleted.
     * <P>
     * @param image the <code>Image</code> to use for creating the
     * static tile set
     * @param tileWidth the width in pixels of a single tile
     * @param tileHeight the height in pixels of a single tile
     * @throws NullPointerException if <code>image</code> is <code>null</code>
     * @throws IllegalArgumentException if <code>tileHeight</code>
     *  or <code>tileWidth</code> is less than <code>1</code>
     * @throws IllegalArgumentException if the <code>image</code>
     *  width is not an integer  multiple of the <code>tileWidth</code>
     * @throws IllegalArgumentException if the <code>image</code>
     *  height is not an integer  multiple of the <code>tileHeight</code>
     */
    public void setStaticTileSet(Image image, int tileWidth, int tileHeight) {
        // if img is null img.getWidth() will throw NullPointerException
        if (tileWidth < 1 || tileHeight < 1 ||
	    ((image.getWidth() % tileWidth) != 0) || 
            ((image.getHeight() % tileHeight) != 0)) {
             throw new IllegalArgumentException();
	}
        setWidthImpl(columns * tileWidth);
	setHeightImpl(rows * tileHeight);

        int noOfFrames = 
          (image.getWidth() / tileWidth) * (image.getHeight() / tileHeight);

        // the zero th index is left empty for transparent tile
        // so it is passed in  createStaticSet as noOfFrames + 1

	if (noOfFrames >= (numberOfTiles - 1)) {
	    // maintain static indices
	    createStaticSet(image, noOfFrames + 1, tileWidth, tileHeight, true);
	} else {
            createStaticSet(image, noOfFrames + 1, tileWidth, 
                                tileHeight, false);
	}	
    }

    /**
     * Draws the TiledLayer.  
     *
     * The entire TiledLayer is rendered subject to the clip region of
     * the Graphics object.
     * The TiledLayer's upper left corner is rendered at the
     * TiledLayer's current
     * position relative to the origin of the Graphics object.   The current
     * position of the TiledLayer's upper-left corner can be retrieved by 
     * calling {@link #getX()} and {@link #getY()}.
     * The appropriate use of a clip region and/or translation allows
     * an arbitrary region
     * of the TiledLayer to be rendered.
     * <p>
     * If the TiledLayer's Image is mutable, the TiledLayer is rendered 
     * using the current contents of the Image.
     * @param g the graphics object to draw the <code>TiledLayer</code>
     * @throws NullPointerException if <code>g</code> is <code>null</code>
     */
    public final void paint(Graphics g) {

        if (g == null) {
            throw new NullPointerException();
        }

        if (visible) {
	    int startColumn = 0;
	    int endColumn = this.columns;
	    int startRow = 0;
	    int endRow = this.rows;

	    // calculate the number of columns left of the clip
	    int number = (g.getClipX() - this.x) / cellWidth;
	    if (number > 0) {
		startColumn = number;
	    }

	    // calculate the number of columns right of the clip
	    int endX = this.x + (this.columns * cellWidth);
	    int endClipX = g.getClipX() + g.getClipWidth();
	    number = (endX - endClipX) / cellWidth;
	    if (number > 0) {
		endColumn -= number;
	    }

	    // calculate the number of rows above the clip
	    number = (g.getClipY() - this.y) / cellHeight;
	    if (number > 0) {
		startRow = number;
	    }

	    // calculate the number of rows below the clip
	    int endY = this.y + (this.rows * cellHeight);
	    int endClipY = g.getClipY() + g.getClipHeight();
	    number = (endY - endClipY) / cellHeight;
	    if (number > 0) {
		endRow -= number;
	    }

	    // paint all visible cells
	    int tileIndex = 0;

	    // y-coordinate
	    int ty        = this.y + (startRow * cellHeight);
            for (int row = startRow; 
		 row < endRow; row++, ty += cellHeight) {

	        // reset the x-coordinate at the beginning of every row
                // x-coordinate to draw tile into
	        int tx = this.x + (startColumn * cellWidth);
                for (int column = startColumn; column < endColumn; 
		    column++, tx += cellWidth) {
		
                    tileIndex = cellMatrix[row][column];
	            // check the indices 
		    // if animated get the corresponding 
		    // static index from anim_to_static table
		    if (tileIndex == 0) { // transparent tile
			continue;
                    } else if (tileIndex < 0) {
                        tileIndex = getAnimatedTile(tileIndex);
		    }

		    g.drawRegion(sourceImage, 
				 tileSetX[tileIndex], 
				 tileSetY[tileIndex], 
				 cellWidth, cellHeight,
				 Sprite.TRANS_NONE,
				 tx, ty,
				 Graphics.TOP | Graphics.LEFT);

                }
            }
	}
    }

    // private implementation

    /**
     * create the Image Array.
     *
     * @param image Image to use for creating the static tile set
     * @param noOfFrames total number of frames
     * @param tileWidth The width, in pixels, of a single tile
     * @param tileHeight The height, in pixels, of a single tile
     * @param maintainIndices 
     */

    private void createStaticSet(Image image, int noOfFrames, int tileWidth, 
                      int tileHeight, boolean maintainIndices) {

        cellWidth = tileWidth;
        cellHeight = tileHeight;

	int imageW = image.getWidth();
	int imageH = image.getHeight();

	sourceImage = image;

	numberOfTiles = noOfFrames;
	tileSetX = new int[numberOfTiles];
	tileSetY = new int[numberOfTiles];
	
	if (!maintainIndices) {
            // populate cell matrix, all the indices are 0 to begin with
            for (rows = 0; rows < cellMatrix.length; rows++) {
                int totalCols = cellMatrix[rows].length;
                for (columns = 0; columns < totalCols; columns++) {
                    cellMatrix[rows][columns] = 0;
                }
            }
	    // delete animated tiles
	    anim_to_static = null;
	} 

        int currentTile = 1;

        for (int locY = 0; locY < imageH; locY += tileHeight) {
            for (int locX = 0; locX < imageW; locX += tileWidth) {

		tileSetX[currentTile] = locX;
		tileSetY[currentTile] = locY;

                currentTile++;
            }
        }
    }

    /** 
     * the overall height of the TiledLayer grid
     */
    private int cellHeight; // = 0;
    /** 
     * the overall cell width of the TiledLayer grid
     */
    private int cellWidth; // = 0;

    /** 
     * The num of rows of the TiledLayer grid.
     */
    private int rows; // = 0;

    /** 
     * the num of columns in the TiledLayer grid
     */
    private int columns; // = 0;

    /** 
     * int array for storing row and column of cell
     *
     * it contains the tile Index for both static and animated tiles
     */
    private int[][] cellMatrix; // = null;

    /**
     * Source image for tiles
     */
    // package access as it is used by Pixel level Collision
    // detection with a Sprite
    Image sourceImage; // = null;
    
    /**
     * no. of tiles
     */
    private int numberOfTiles; // = 0;

    /**
     * X co-ordinate definitions for individual frames into the source image
     */
    // package access as it is used by Pixel level Collision
    // detection with a Sprite
    int[] tileSetX;

    /**
     * Y co-ordinate definitions for individual frames into the source image
     */ 
    // package access as it is used by Pixel level Collision
    // detection with a Sprite
    int[] tileSetY;
    
    /** 
     * Table to map from animated Index to static Index
     * 0th location is unused.
     * anim --> static Index
     * -1 --> 21
     * -2 --> 34
     * -3 --> 45
     * for now keep 0 the location of the table empty instead of computing
     * -index make index +ve and access this Table.
     *  
     */
    private int[] anim_to_static; // = null;

    /** 
     * total number of animated tiles. This variable is also used as 
     * index in the above table to add new entries to the anim_to_static table.
     * initialized to 1 when table is created.
     */
    private int numOfAnimTiles; // = 0

}
