/*
	Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
	и других спецификаций для функционирования компактных приложений на языке
	Java (мидлетов) в среде программного обеспечения Малик Эмулятор.

	Copyright © 2016, 2019 Малик Разработчик

	Это свободная программа: вы можете перераспространять ее и/или изменять
	ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
	в каком она была опубликована Фондом свободного программного обеспечения;
	либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

	Эта программа распространяется в надежде, что она будет полезной,
	но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
	или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
	общественной лицензии GNU.

	Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
	вместе с этой программой. Если это не так, см.
	<https://www.gnu.org/licenses/>.
*/


package javax.microedition.lcdui.game;

import javax.microedition.lcdui.*;

public class TiledLayer extends Layer
{
	private int cols;
	private int rows;
	private int tileWidth;
	private int tileHeight;
	private int tilesCount;
	private int tilesPerRow;
	private int animatedTilesCount;
	private int[] animatedTiles;
	private int[] cells;
	private Image tileSet;

	public TiledLayer(int columns, int rows, Image tileSet, int tileWidth, int tileHeight)
	{
		super(columns * tileWidth, rows * tileHeight);
		int tmp;
		int width;
		int height;
		if(tileSet == null)
		{
			throw new NullPointerException("TiledLayer: " +
					"параметр tileSet равен нулевой ссылке.");
		}
		if(columns < 1 || rows < 1)
		{
			throw new IllegalArgumentException("TiledLayer: " +
					"размеры замощённого слоя могут быть только положительными.");
		}
		if(tileWidth < 1 || tileHeight < 1)
		{
			throw new IllegalArgumentException("TiledLayer: " +
					"размеры плиток замощённого слоя могут быть только положительными.");
		}
		if((width = tileSet.getWidth()) % tileWidth != 0 ||
				(height = tileSet.getHeight()) % tileHeight != 0)
		{
			throw new IllegalArgumentException("TiledLayer: " +
					"размеры набора плиток могут только нацело делиться на размеры самих плиток.");
		}
		this.cols = columns;
		this.rows = rows;
		this.tileWidth = tileWidth;
		this.tileHeight = tileHeight;
		this.tilesCount = (tmp = width / tileWidth) * (height / tileHeight);
		this.tilesPerRow = tmp;
		this.cells = new int[columns * rows];
		this.tileSet = tileSet;
	}

	public void setStaticTileSet(Image tileSet, int tileWidth, int tileHeight)
	{
		int i;
		int tmp;
		int width;
		int height;
		int oldTilesCount;
		int newTilesCount;
		int[] cls;
		if(tileSet == null)
		{
			throw new NullPointerException("TiledLayer.setStaticTileSet: " +
					"параметр tileSet равен нулевой ссылке.");
		}
		if(tileWidth < 1 || tileHeight < 1)
		{
			throw new IllegalArgumentException("TiledLayer.setStaticTileSet: " +
					"размеры плиток замощённого слоя могут быть только положительными.");
		}
		if((width = tileSet.getWidth()) % tileWidth != 0 ||
				(height = tileSet.getHeight()) % tileHeight != 0)
		{
			throw new IllegalArgumentException("TiledLayer.setStaticTileSet: " +
					"размеры набора плиток могут только нацело делиться на размеры самих плиток.");
		}
		synchronized(cls = cells)
		{
			oldTilesCount = this.tilesCount;
			setSize(cols * tileWidth, rows* tileHeight);
			this.tileWidth = tileWidth;
			this.tileHeight = tileHeight;
			this.tilesCount = newTilesCount = (tmp = width / tileWidth) * (height / tileHeight);
			this.tilesPerRow = tmp;
			this.tileSet = tileSet;
			if(newTilesCount < oldTilesCount)
			{
				animatedTilesCount = 0;
				animatedTiles = null;
				for(i = cls.length; i-- > 0; cls[i] = 0);
			}
		}
	}

	public void setAnimatedTile(int animatedTileIndex, int staticTileIndex)
	{
		int error;
		int[] animated;
		if(staticTileIndex < 0 || staticTileIndex > tilesCount)
		{
			throw new IndexOutOfBoundsException("TiledLayer.setAnimatedTile: " +
					"параметр staticTileIndex выходит из диапазона.");
		}
		error = 0;
		synchronized(cells)
		{
			label0:
			{
				if((animated = animatedTiles) == null ||
						(animatedTileIndex = ~animatedTileIndex) < 0 ||
						animatedTileIndex >= animatedTilesCount)
				{
					error = 1;
					break label0;
				}
				animated[animatedTileIndex] = staticTileIndex;
			}
		}
		if(error == 1)
		{
			throw new IndexOutOfBoundsException("TiledLayer.setAnimatedTile: " +
					"параметр animatedTileIndex выходит из диапазона.");
		}
	}

	public void setCell(int col, int row, int tileIndex)
	{
		int c;
		if(col < 0 || col >= (c = cols))
		{
			throw new IndexOutOfBoundsException("TiledLayer.setCell: " +
					"параметр col выходит из диапазона.");
		}
		if(row < 0 || row >= rows)
		{
			throw new IndexOutOfBoundsException("TiledLayer.setCell: " +
					"параметр row выходит из диапазона.");
		}
		if(tileIndex < -animatedTilesCount || tileIndex > tilesCount)
		{
			throw new IndexOutOfBoundsException("TiledLayer.setCell: " +
					"параметр tileIndex выходит из диапазона.");
		}
		cells[col + row * c] = tileIndex;
	}

	public void fillCells(int col, int row, int numCols, int numRows, int tileIndex)
	{
		int i;
		int j;
		int r;
		int b;
		int c;
		int d;
		int lim;
		int len;
		int[] cls;
		if((lim = r = col + numCols) > (len = c = cols) || lim < col || col > len || col < 0 ||
				(lim = b = row + numRows) > (len = rows) || lim < row || row > len || row < 0)
		{
			throw new IndexOutOfBoundsException("TiledLayer.fillCells: " +
					"индекс выходит из диапазона.");
		}
		if(tileIndex < -animatedTilesCount || tileIndex > tilesCount)
		{
			throw new IndexOutOfBoundsException("TiledLayer.fillCells: " +
					"параметр tileIndex выходит из диапазона.");
		}
		for(cls = cells, d = row * c, j = row; j < b; d += c, j++)
		{
			for(i = col; i < r; i++)
			{
				cls[i + d] = tileIndex;
			}
		}
	}

	public int createAnimatedTile(int staticTileIndex)
	{
		int result;
		int[] animated;
		if(staticTileIndex < 0 || staticTileIndex > tilesCount)
		{
			throw new IndexOutOfBoundsException("TiledLayer.createAnimatedTile: " +
					"параметр staticTileIndex выходит из диапазона.");
		}
		synchronized(cells)
		{
			result = animatedTilesCount;
			if((animated = animatedTiles) == null)
			{
				animated = animatedTiles = new int[4];
			}
			else if(result == animated.length)
			{
				Array.copy(animated, 0,
						animated = animatedTiles = new int[result << 1], 0, result);
			}
			animated[result++] = staticTileIndex;
			animatedTilesCount = result;
		}
		return -result;
	}

	public int getAnimatedTile(int animatedTileIndex)
	{
		int error;
		int result;
		int[] animated;
		error = 0;
		synchronized(cells)
		{
			label0:
			{
				if((animated = animatedTiles) == null ||
						(animatedTileIndex = ~animatedTileIndex) < 0 ||
						animatedTileIndex >= animatedTilesCount)
				{
					error = 1;
					result = 0;
					break label0;
				}
				result = animated[animatedTileIndex];
			}
		}
		if(error == 1)
		{
			throw new IndexOutOfBoundsException("TiledLayer.getAnimatedTile: " +
					"параметр animatedTileIndex выходит из диапазона.");
		}
		return result;
	}

	public int getCell(int col, int row)
	{
		int c;
		if(col < 0 || col >= (c = cols))
		{
			throw new IndexOutOfBoundsException("TiledLayer.getCell: " +
					"параметр col выходит из диапазона.");
		}
		if(row < 0 || row >= rows)
		{
			throw new IndexOutOfBoundsException("TiledLayer.getCell: " +
					"параметр row выходит из диапазона.");
		}
		return cells[col + row * c];
	}

	public final void paint(Graphics render)
	{
		int i;
		int j;
		int l;
		int t;
		int r;
		int b;
		int c;
		int d;
		int cell;
		int clipLeft;
		int clipTop;
		int layerLeft;
		int layerTop;
		int tileWidth;
		int tileHeight;
		int tilesCount;
		int tilesPerRow;
		int[] animated;
		int[] cls;
		Image tileSet;
		if(render == null)
		{
			throw new NullPointerException("TiledLayer.paint: " +
					"параметр render равен нулевой ссылке.");
		}
		if(!isVisible())
		{
			return;
		}
		synchronized(cls = cells)
		{
			clipLeft = render.getClipX();
			clipTop = render.getClipY();
			layerLeft = getX();
			layerTop = getY();
			tileWidth = this.tileWidth;
			tileHeight = this.tileHeight;
			tilesCount = this.tilesCount;
			tilesPerRow = this.tilesPerRow;
			animated = this.animatedTiles;
			tileSet = this.tileSet;
			l = Math.max(0, (clipLeft - layerLeft) / tileWidth);
			t = Math.max(0, (clipTop - layerTop) / tileHeight);
			r = Math.min((c = cols) - 1,
					(clipLeft + render.getClipWidth() - layerLeft - 1) / tileWidth);
			b = Math.min(rows - 1,
					(clipTop + render.getClipHeight() - layerTop - 1) / tileHeight);
			for(d = t * c, j = t; j <= b; d += c, j++)
			{
				for(i = l; i <= r; i++)
				{
					if((cell = cls[i + d]) < 0)
					{
						cell = animated[~cell];
					}
					if(cell == 0 || cell > tilesCount)
					{
						continue;
					}
					cell--;
					render.drawRegion(tileSet,
							(cell % tilesPerRow) * tileWidth, (cell / tilesPerRow) * tileHeight,
							tileWidth, tileHeight, 0,
							layerLeft + i * tileWidth, layerTop + j * tileHeight,
							Graphics.LEFT | Graphics.TOP);
				}
			}
		}
	}

	public final int getColumns()
	{
		return cols;
	}

	public final int getRows()
	{
		return rows;
	}

	public final int getCellWidth()
	{
		return tileWidth;
	}

	public final int getCellHeight()
	{
		return tileHeight;
	}

	final int getTilesPerRow()
	{
		return tilesPerRow;
	}

	final int getTileIndex(int col, int row)
	{
		int result;
		if((result = cells[col + row * cols]) < 0)
		{
			result = animatedTiles[~result];
		}
		return result;
	}

	final Image getTileSet()
	{
		return tileSet;
	}

	final Object getMonitor()
	{
		return cells;
	}
}
