/*
 * @(#)TiledLayer.java	1.49 02/07/24 @(#)
 *
 * Copyright (c) 2002 Sun Microsystems, Inc.  All rights reserved.
 * PROPRIETARY/CONFIDENTIAL
 * Use is subject to license terms.
 */

package com.amaker.game;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;

/**
 * A TiledLayer is a visual element composed of a grid of cells that can be
 * filled with a set of tile Bitmaps. This class allows large virtual layers to
 * be created without the need for an extremely large Bitmap. 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 Bitmap
 * object which may be mutable or immutable. The Bitmap is broken up into a
 * series of equally-sized tiles; the tile size is specified along with the
 * Bitmap. 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 Bitmap 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 Bitmap 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 Canvas object; the upper left corner of the TiledLayer is
 * rendered at its current (x,y) position relative to the Canvas object's
 * origin. The rendered region may be controlled by setting the clip region of
 * the Canvas object accordingly.
 * <P>
 * 
 * @since MIDP 2.0
 */
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
	 * Bitmap with each tile having the dimensions of tileWidth x tileHeight.
	 * The width of the source Bitmap must be an integer multiple of the tile
	 * width, and the height of the source Bitmap 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(Bitmap, 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 Bitmap
	 *            the <code>Bitmap</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>Bitmap</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>Bitmap</code> width is not an integer multiple
	 *             of the <code>tileWidth</code>
	 * @throws IllegalArgumentException
	 *             if the <code>Bitmap</code> height is not an integer multiple
	 *             of the <code>tileHeight</code>
	 */
	public TiledLayer(int columns, int rows, Bitmap Bitmap, 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 (((Bitmap.getWidth() % tileWidth) != 0)
				|| ((Bitmap.getHeight() % tileHeight) != 0)) {
			throw new IllegalArgumentException();
		}
		this.columns = columns;
		this.rows = rows;

		cellMatrix = new int[rows][columns];

		int noOfFrames = (Bitmap.getWidth() / tileWidth)
				* (Bitmap.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(Bitmap, 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 (col < 0 || col >= this.columns || row < 0 || row >= this.rows
				|| numCols < 0 || col + numCols > this.columns || numRows < 0
				|| 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, Bitmap, int, int)} for
	 * information on how the tiles are created from the Bitmap.
	 * <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 Bitmap
	 *            the <code>Bitmap</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>Bitmap</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>Bitmap</code> width is not an integer multiple
	 *             of the <code>tileWidth</code>
	 * @throws IllegalArgumentException
	 *             if the <code>Bitmap</code> height is not an integer multiple
	 *             of the <code>tileHeight</code>
	 */
	public void setStaticTileSet(Bitmap Bitmap, int tileWidth, int tileHeight) {
		// if img is null img.getWidth() will throw NullPointerException
		if (tileWidth < 1 || tileHeight < 1
				|| ((Bitmap.getWidth() % tileWidth) != 0)
				|| ((Bitmap.getHeight() % tileHeight) != 0)) {
			throw new IllegalArgumentException();
		}
		setWidthImpl(columns * tileWidth);
		setHeightImpl(rows * tileHeight);

		int noOfFrames = (Bitmap.getWidth() / tileWidth)
				* (Bitmap.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(Bitmap, noOfFrames + 1, tileWidth, tileHeight, true);
		} else {
			createStaticSet(Bitmap, noOfFrames + 1, tileWidth, tileHeight,
					false);
		}
	}

	/**
	 * Draws the TiledLayer.
	 * 
	 * The entire TiledLayer is rendered subject to the clip region of the
	 * Canvas object. The TiledLayer's upper left corner is rendered at the
	 * TiledLayer's current position relative to the origin of the Canvas
	 * 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 Bitmap is mutable, the TiledLayer is rendered using
	 * the current contents of the Bitmap.
	 * 
	 * @param g
	 *            the Canvas object to draw the <code>TiledLayer</code>
	 * @throws NullPointerException
	 *             if <code>g</code> is <code>null</code>
	 */
	
	
	public final void paint(Canvas c) {

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

		if (visible) {
			int tileIndex = 0;

			// y-coordinate
			int ty = this.y;
			for (int row = 0; row < cellMatrix.length; row++, ty += cellHeight) {

				// reset the x-coordinate at the beginning of every row
				// x-coordinate to draw tile into
				int tx = this.x;
				int totalCols = cellMatrix[row].length;

				for (int column = 0; column < totalCols; 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);
					}
					
					
					int sx = tileSetX[tileIndex];
					int sy = tileSetY[tileIndex];
					int w = cellWidth;
					int h = cellHeight;
					
					drawBitmap(c,tx,ty,sourceBitmap, sx, sy,w,h);
					
				}
			}
		}
	}

	// private implementationv

	/**
	 * create the Bitmap Array.
	 * 
	 * @param Bitmap
	 *            Bitmap 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(Bitmap Bitmap, int noOfFrames, int tileWidth,
			int tileHeight, boolean maintainIndices) {

		cellWidth = tileWidth;
		cellHeight = tileHeight;

		int BitmapW = Bitmap.getWidth();
		int BitmapH = Bitmap.getHeight();

		sourceBitmap = Bitmap;

		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 y = 0; y < BitmapH; y += tileHeight) {
			for (int x = 0; x < BitmapW; x += tileWidth) {

				tileSetX[currentTile] = x;
				tileSetY[currentTile] = y;

				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 Bitmap for tiles
	 */
	// package access as it is used by Pixel level Collision
	// detection with a Sprite
	Bitmap sourceBitmap; // = null;

	/**
	 * no. of tiles
	 */
	private int numberOfTiles; // = 0;

	/**
	 * X co-ordinate definitions for individual frames into the source Bitmap
	 */
	// 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 Bitmap
	 */
	// 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

}
