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

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

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

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

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

package javax.microedition.lcdui.game;

import javax.microedition.lcdui.*;

public class TiledLayer extends Layer
{
    private static final int MAX_SIZE = 0x7fff;

    private final int cols;
    private final int rows;
    int tileSetCellWidth;
    int tileSetCellHeight;
    int tileSetCellsCount;
    int tileSetCellsPerRow;
    private int animatedTilesCount;
    private int[] animatedTiles;
    private final int[] staticTiles;
    Image tileSet;

    public TiledLayer(int cols, int rows, Image tileSet, int cellWidth, int cellHeight) {
        int tilesPerRow;
        int tileSetWidth;
        int tileSetHeight;
        if(cols < 1 || cols > MAX_SIZE)
        {
            throw new IllegalArgumentException("TiledLayer: аргумент cols не может быть меньше 1 или больше " + MAX_SIZE + ".");
        }
        if(rows < 1 || rows > MAX_SIZE)
        {
            throw new IllegalArgumentException("TiledLayer: аргумент rows не может быть меньше 1 или больше " + MAX_SIZE + ".");
        }
        if(tileSet == null)
        {
            throw new NullPointerException("TiledLayer: аргумент tileSet равен нулевой ссылке.");
        }
        if(cellWidth < 1 || cellWidth > MAX_SIZE)
        {
            throw new IllegalArgumentException("TiledLayer: аргумент cellWidth не может быть меньше 1 или больше " + MAX_SIZE + ".");
        }
        if(cellHeight < 1 || cellHeight > MAX_SIZE)
        {
            throw new IllegalArgumentException("TiledLayer: аргумент cellHeight не может быть меньше 1 или больше " + MAX_SIZE + ".");
        }
        if((tileSetWidth = tileSet.getWidth()) % cellWidth != 0 || (tileSetHeight = tileSet.getHeight()) % cellHeight != 0)
        {
            throw new IllegalArgumentException("TiledLayer: размеры ячеек оба должны быть делителями размеров набора плиток.");
        }
        this.width = cols * cellWidth;
        this.height = rows * cellHeight;
        this.cols = cols;
        this.rows = rows;
        this.tileSetCellWidth = cellWidth;
        this.tileSetCellHeight = cellHeight;
        this.tileSetCellsCount = (tilesPerRow = tileSetWidth / cellWidth) * (tileSetHeight / cellHeight);
        this.tileSetCellsPerRow = tilesPerRow;
        this.staticTiles = new int[cols * rows];
        this.tileSet = tileSet;
    }

    public void fillCells(int startCol, int startRow, int numCols, int numRows, int tileIndex) {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int width;
                int[] tiles;
                if(numCols < 0)
                {
                    error = 1;
                    break label0;
                }
                if(numRows < 0)
                {
                    error = 2;
                    break label0;
                }
                if(!Array.isBoundValid(width = cols, startCol, numCols) || !Array.isBoundValid(rows, startRow, numRows))
                {
                    error = 3;
                    break label0;
                }
                if(tileIndex < -animatedTilesCount || tileIndex > tileSetCellsCount)
                {
                    error = 4;
                    break label0;
                }
                tiles = staticTiles;
                for(int offset = startCol + startRow * width, i = numRows; i-- > 0; offset += width) Array.fill(tiles, offset, numCols, tileIndex);
            }
        }
        switch(error)
        {
        case 1:
            throw new IllegalArgumentException("TiledLayer.fillCells: аргумент numCols не может быть отрицательным.");
        case 2:
            throw new IllegalArgumentException("TiledLayer.fillCells: аргумент numRows не может быть отрицательным.");
        case 3:
            throw new IndexOutOfBoundsException("TiledLayer.fillCells: индекс выходит из диапазона.");
        case 4:
            throw new IndexOutOfBoundsException("TiledLayer.fillCells: аргумент tileIndex выходит из диапазона.");
        }
    }

    public void setCell(int col, int row, int tileIndex) {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int width;
                if(col < 0 || col >= (width = cols) || row < 0 || row >= rows)
                {
                    error = 1;
                    break label0;
                }
                if(tileIndex < -animatedTilesCount || tileIndex > tileSetCellsCount)
                {
                    error = 2;
                    break label0;
                }
                staticTiles[col + row * width] = tileIndex;
            }
        }
        switch(error)
        {
        case 1:
            throw new IndexOutOfBoundsException("TiledLayer.setCell: индекс выходит из диапазона.");
        case 2:
            throw new IndexOutOfBoundsException("TiledLayer.setCell: аргумент tileIndex выходит из диапазона.");
        }
    }

    public void setAnimatedTile(int animatedTileIndex, int staticTileIndex) {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                if(animatedTileIndex < -animatedTilesCount || animatedTileIndex >= 0)
                {
                    error = 1;
                    break label0;
                }
                if(staticTileIndex < 0 || staticTileIndex > tileSetCellsCount)
                {
                    error = 2;
                    break label0;
                }
                animatedTiles[~animatedTileIndex] = staticTileIndex;
            }
        }
        switch(error)
        {
        case 1:
            throw new IndexOutOfBoundsException("TiledLayer.setAnimatedTile: аргумент animatedTileIndex выходит из диапазона.");
        case 2:
            throw new IndexOutOfBoundsException("TiledLayer.setAnimatedTile: аргумент staticTileIndex выходит из диапазона.");
        }
    }

    public void setStaticTileSet(Image tileSet, int cellWidth, int cellHeight) {
        int tileSetWidth;
        int tileSetHeight;
        if(tileSet == null)
        {
            throw new NullPointerException("TiledLayer.setStaticTileSet: аргумент tileSet равен нулевой ссылке.");
        }
        if(cellWidth < 1 || cellWidth > MAX_SIZE)
        {
            throw new IllegalArgumentException("TiledLayer.setStaticTileSet: аргумент cellWidth не может быть меньше 1 или больше " + MAX_SIZE + ".");
        }
        if(cellHeight < 1 || cellHeight > MAX_SIZE)
        {
            throw new IllegalArgumentException("TiledLayer.setStaticTileSet: аргумент cellHeight не может быть меньше 1 или больше " + MAX_SIZE + ".");
        }
        if((tileSetWidth = tileSet.getWidth()) % cellWidth != 0 || (tileSetHeight = tileSet.getHeight()) % cellHeight != 0)
        {
            throw new IllegalArgumentException("TiledLayer.setStaticTileSet: размеры ячеек оба должны быть делителями размеров набора плиток.");
        }
        synchronized(monitor)
        {
            int tilesPerRow;
            int newCellsCount;
            int oldCellsCount = tileSetCellsCount;
            this.width = cols * cellWidth;
            this.height = rows * cellHeight;
            this.tileSetCellWidth = cellWidth;
            this.tileSetCellHeight = cellHeight;
            this.tileSetCellsCount = newCellsCount = (tilesPerRow = tileSetWidth / cellWidth) * (tileSetHeight / cellHeight);
            this.tileSetCellsPerRow = tilesPerRow;
            this.tileSet = tileSet;
            if(newCellsCount < oldCellsCount)
            {
                int[] tiles;
                animatedTilesCount = 0;
                animatedTiles = null;
                Array.fill(tiles = staticTiles, 0, tiles.length, 0);
            }
        }
    }

    public int createAnimatedTile(int staticTileIndex) {
        int result;
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int len;
                int[] tiles;
                if(staticTileIndex < 0 || staticTileIndex > tileSetCellsCount)
                {
                    error = 1;
                    result = 0;
                    break label0;
                }
                result = ~(len = animatedTilesCount);
                if((tiles = animatedTiles) == null)
                {
                    tiles = animatedTiles = new int[7];
                }
                else if(len == tiles.length)
                {
                    Array.copy(tiles, 0, tiles = animatedTiles = new int[(len << 1) + 1], 0, len);
                }
                tiles[len++] = staticTileIndex;
                animatedTilesCount = len;
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("TiledLayer.createAnimatedTile: аргумент staticTileIndex выходит из диапазона.");
        }
        return result;
    }

    public int getCell(int col, int row) {
        int result;
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int width;
                if(col < 0 || col >= (width = cols) || row < 0 || row >= rows)
                {
                    error = 1;
                    result = 0;
                    break label0;
                }
                result = staticTiles[col + row * width];
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("TiledLayer.getCell: индекс выходит из диапазона.");
        }
        return result;
    }

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

    public final void paint(Graphics render) {
        if(render == null)
        {
            throw new NullPointerException("TiledLayer.paint: аргумент render равен нулевой ссылке.");
        }
        if(!visibility) return;
        synchronized(monitor)
        {
            int clip;
            int left = this.left;
            int top = this.top;
            int cols = this.cols;
            int rows = this.rows;
            int cellWidth = tileSetCellWidth;
            int cellHeight = tileSetCellHeight;
            int cellsPerRow = tileSetCellsPerRow;
            int scol = left > (clip = render.getClipX()) ? 0 : (clip - left) / cellWidth;
            int fcol = left > (clip += render.getClipWidth() - 1) ? -1 : (clip - left) / cellWidth;
            int srow = top > (clip = render.getClipY()) ? 0 : (clip - top) / cellHeight;
            int frow = top > (clip += render.getClipHeight() - 1) ? -1 : (clip - top) / cellHeight;
            int[] animatedTiles = this.animatedTiles;
            int[] staticTiles = this.staticTiles;
            Image tileSet = this.tileSet;
            for(int offset = srow * cols, y = top + srow * cellHeight, row = srow; row <= frow && row < rows; offset += cols, y += cellHeight, row++)
            {
                for(int x = left + scol * cellWidth, col = scol; col <= fcol && col < cols; x += cellWidth, col++)
                {
                    int tileIndex;
                    if((tileIndex = staticTiles[offset + col]) < 0) tileIndex = animatedTiles[~tileIndex];
                    if(tileIndex-- > 0)
                    {
                        int tileSetLeft = (tileIndex % cellsPerRow) * cellWidth;
                        int tileSetTop = (tileIndex / cellsPerRow) * cellHeight;
                        render.drawRegion(tileSet, tileSetLeft, tileSetTop, cellWidth, cellHeight, Sprite.TRANS_NONE, x, y, 0);
                    }
                }
            }
        }
    }

    public final int getColumns() {
        return cols;
    }

    public final int getRows() {
        return rows;
    }

    public final int getCellWidth() {
        return tileSetCellWidth;
    }

    public final int getCellHeight() {
        return tileSetCellHeight;
    }

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