/*
    Реализация спецификаций 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 Sprite extends Layer
{
    public static final int TRANS_NONE          = 0;
    public static final int TRANS_MIRROR_ROT180 = 1;
    public static final int TRANS_MIRROR        = 2;
    public static final int TRANS_ROT180        = 3;
    public static final int TRANS_MIRROR_ROT270 = 4;
    public static final int TRANS_ROT90         = 5;
    public static final int TRANS_ROT270        = 6;
    public static final int TRANS_MIRROR_ROT90  = 7;
    private static final int MAX_SIZE = 0x7fff;

    private static boolean isPixelCollision(int width, int height, Image image1, int left1, int top1, int transform1, Image image2, int left2, int top2, int transform2) {
        int area;
        int xinc1;
        int xinc2;
        int yinc1;
        int yinc2;
        int start1;
        int start2;
        int[] pixels1 = new int[area = width * height];
        int[] pixels2 = new int[area];
        if((transform1 & 0x04) != 0)
        {
            if((transform1 & 0x01) != 0)
            {
                xinc1 = -height;
                start1 = area - height;
            } else
            {
                xinc1 = height;
                start1 = 0;
            }
            if((transform1 & 0x02) != 0)
            {
                yinc1 = -1;
                start1 += height - 1;
            } else
            {
                yinc1 = 1;
            }
            image1.getRGB(pixels1, 0, height, left1, top1, height, width);
        } else
        {
            if((transform1 & 0x01) != 0)
            {
                yinc1 = -width;
                start1 = area - width;
            } else
            {
                yinc1 = width;
                start1 = 0;
            }
            if((transform1 & 0x02) != 0)
            {
                xinc1 = -1;
                start1 += width - 1;
            } else
            {
                xinc1 = 1;
            }
            image1.getRGB(pixels1, 0, width, left1, top1, width, height);
        }
        if((transform2 & 0x04) != 0)
        {
            if((transform2 & 0x01) != 0)
            {
                xinc2 = -height;
                start2 = area - height;
            } else
            {
                xinc2 = height;
                start2 = 0;
            }
            if((transform2 & 0x02) != 0)
            {
                yinc2 = -1;
                start2 += height - 1;
            } else
            {
                yinc2 = 1;
            }
            image2.getRGB(pixels2, 0, height, left2, top2, height, width);
        } else
        {
            if((transform2 & 0x01) != 0)
            {
                yinc2 = -width;
                start2 = area - width;
            } else
            {
                yinc2 = width;
                start2 = 0;
            }
            if((transform2 & 0x02) != 0)
            {
                xinc2 = -1;
                start2 += width - 1;
            } else
            {
                xinc2 = 1;
            }
            image2.getRGB(pixels2, 0, width, left2, top2, width, height);
        }
        for(int startx1 = start1, startx2 = start2, j = height; j-- > 0; startx1 += yinc1, startx2 += yinc2) for(int x1 = startx1, x2 = startx2, i = width; i-- > 0; x1 += xinc1, x2 += xinc2)
        {
            if((pixels1[x1] & 0xff000000) != 0 && (pixels2[x2] & 0xff000000) != 0) return true;
        }
        return false;
    }

    private static boolean isRectIntersection(int left1, int top1, int right1, int bottom1, int left2, int top2, int right2, int bottom2) {
        return left1 < right2 && left2 < right1 && top1 < bottom2 && top2 < bottom1;
    }

    private int referencePointX;
    private int referencePointY;
    private int collisionRectLeft;
    private int collisionRectTop;
    private int collisionRectWidth;
    private int collisionRectHeight;
    private int transformedCollisionRectLeft;
    private int transformedCollisionRectTop;
    private int transformedCollisionRectWidth;
    private int transformedCollisionRectHeight;
    private int transformation;
    private int frameIndex;
    private int frameSetFrameWidth;
    private int frameSetFrameHeight;
    private int frameSetFramesCount;
    private int frameSetFramesPerRow;
    private int[] frameSequenceDefault;
    private int[] frameSequence;
    private Image frameSet;

    public Sprite(Sprite source) {
        if(source == null)
        {
            throw new NullPointerException("Sprite: аргумент source равен нулевой ссылке.");
        }
        synchronized(source.monitor)
        {
            this.visibility = source.visibility;
            this.left = source.left;
            this.top = source.top;
            this.width = source.width;
            this.height = source.height;
            this.referencePointX = source.referencePointX;
            this.referencePointY = source.referencePointY;
            this.collisionRectLeft = source.collisionRectLeft;
            this.collisionRectTop = source.collisionRectTop;
            this.collisionRectWidth = source.collisionRectWidth;
            this.collisionRectHeight = source.collisionRectHeight;
            this.transformedCollisionRectLeft = source.transformedCollisionRectLeft;
            this.transformedCollisionRectTop = source.transformedCollisionRectTop;
            this.transformedCollisionRectWidth = source.transformedCollisionRectWidth;
            this.transformedCollisionRectHeight = source.transformedCollisionRectHeight;
            this.transformation = source.transformation;
            this.frameIndex = source.frameIndex;
            this.frameSetFrameWidth = source.frameSetFrameWidth;
            this.frameSetFrameHeight = source.frameSetFrameHeight;
            this.frameSetFramesCount = source.frameSetFramesCount;
            this.frameSetFramesPerRow = source.frameSetFramesPerRow;
            this.frameSequenceDefault = source.frameSequenceDefault;
            this.frameSequence = source.frameSequence;
            this.frameSet = source.frameSet;
        }
    }

    public Sprite(Image frame) {
        int frameWidth;
        int frameHeight;
        int[] frameSequence;
        if(frame == null)
        {
            throw new NullPointerException("Sprite: аргумент frame равен нулевой ссылке.");
        }
        this.width = frameWidth = frame.getWidth();
        this.height = frameHeight = frame.getHeight();
        this.collisionRectWidth = frameWidth;
        this.collisionRectHeight = frameHeight;
        this.transformedCollisionRectWidth = frameWidth;
        this.transformedCollisionRectHeight = frameHeight;
        this.frameSetFrameWidth = frameWidth;
        this.frameSetFrameHeight = frameHeight;
        this.frameSetFramesCount = 1;
        this.frameSetFramesPerRow = 1;
        this.frameSequenceDefault = frameSequence = new int[1];
        this.frameSequence = frameSequence;
        this.frameSet = frame;
    }

    public Sprite(Image frameSet, int frameWidth, int frameHeight) {
        int framesCount;
        int framesPerRow;
        int frameSetWidth;
        int frameSetHeight;
        int[] frameSequence;
        if(frameSet == null)
        {
            throw new NullPointerException("Sprite: аргумент frameSet равен нулевой ссылке.");
        }
        if(frameWidth < 1 || frameWidth > MAX_SIZE)
        {
            throw new IllegalArgumentException("Sprite: аргумент frameWidth не может быть меньше 1 или больше " + MAX_SIZE + ".");
        }
        if(frameHeight < 1 || frameHeight > MAX_SIZE)
        {
            throw new IllegalArgumentException("Sprite: аргумент frameHeight не может быть меньше 1 или больше " + MAX_SIZE + ".");
        }
        if((frameSetWidth = frameSet.getWidth()) % frameWidth != 0 || (frameSetHeight = frameSet.getHeight()) % frameHeight != 0)
        {
            throw new IllegalArgumentException("Sprite: размеры кадров оба должны быть делителями размеров набора кадров.");
        }
        this.width = frameWidth;
        this.height = frameHeight;
        this.collisionRectWidth = frameWidth;
        this.collisionRectHeight = frameHeight;
        this.transformedCollisionRectWidth = frameWidth;
        this.transformedCollisionRectHeight = frameHeight;
        this.frameSetFrameWidth = frameWidth;
        this.frameSetFrameHeight = frameHeight;
        this.frameSetFramesCount = framesCount = (framesPerRow = frameSetWidth / frameWidth) * (frameSetHeight / frameHeight);
        this.frameSetFramesPerRow = framesPerRow;
        this.frameSequenceDefault = frameSequence = new int[framesCount];
        this.frameSequence = frameSequence;
        this.frameSet = frameSet;
        for(int i = framesCount; i-- > 1; frameSequence[i] = i);
    }

    public void nextFrame() {
        synchronized(monitor)
        {
            frameIndex = (frameIndex + 1) % frameSequence.length;
        }
    }

    public void prevFrame() {
        synchronized(monitor)
        {
            int len = frameSequence.length;
            frameIndex = (frameIndex + len - 1) % len;
        }
    }

    public void defineReferencePixel(int x, int y) {
        synchronized(monitor)
        {
            referencePointX = x;
            referencePointY = y;
        }
    }

    public void defineCollisionRectangle(int left, int top, int width, int height) {
        if(width < 0)
        {
            throw new IllegalArgumentException("Sprite.defineCollisionRectangle: аргумент width не может быть отрицательным.");
        }
        if(height < 0)
        {
            throw new IllegalArgumentException("Sprite.defineCollisionRectangle: аргумент height не может быть отрицательным.");
        }
        synchronized(monitor)
        {
            collisionRectLeft = left;
            collisionRectTop = top;
            collisionRectWidth = width;
            collisionRectHeight = height;
            computeTransformedBounds(transformation);
        }
    }

    public void setRefPixelPosition(int x, int y) {
        synchronized(monitor)
        {
            int transform = transformation;
            left = x - getTransformedReferencePointX(transform);
            top = y - getTransformedReferencePointY(transform);
        }
    }

    public void setTransform(int transform) {
        if((transform & (-8)) != 0)
        {
            throw new IllegalArgumentException("Sprite.setTransform: аргумент transform имеет недопустимое значение.");
        }
        synchronized(monitor)
        {
            int oldTransform = transformation;
            if(transform != oldTransform)
            {
                this.left += getTransformedReferencePointX(oldTransform) - getTransformedReferencePointX(transform);
                this.top += getTransformedReferencePointY(oldTransform) - getTransformedReferencePointY(transform);
                this.computeTransformedBounds(transform);
                this.transformation = transform;
            }
        }
    }

    public void setImage(Image frameSet, int frameWidth, int frameHeight) {
        int framesCount;
        int framesPerRow;
        int frameSetWidth;
        int frameSetHeight;
        if(frameSet == null)
        {
            throw new NullPointerException("Sprite.setImage: аргумент frameSet равен нулевой ссылке.");
        }
        if(frameWidth < 1 || frameWidth > MAX_SIZE)
        {
            throw new IllegalArgumentException("Sprite.setImage: аргумент frameWidth не может быть меньше 1 или больше " + MAX_SIZE + ".");
        }
        if(frameHeight < 1 || frameHeight > MAX_SIZE)
        {
            throw new IllegalArgumentException("Sprite.setImage: аргумент frameHeight не может быть меньше 1 или больше " + MAX_SIZE + ".");
        }
        if((frameSetWidth = frameSet.getWidth()) % frameWidth != 0 || (frameSetHeight = frameSet.getHeight()) % frameHeight != 0)
        {
            throw new IllegalArgumentException("Sprite.setImage: размеры кадров оба должны быть делителями размеров набора кадров.");
        }
        framesCount = (framesPerRow = frameSetWidth / frameWidth) * (frameSetHeight / frameHeight);
        synchronized(monitor)
        {
            int oldFramesCount;
            int[] frameSequence;
            if(frameWidth != frameSetFrameWidth || frameHeight != frameSetFrameHeight)
            {
                int transform = transformation;
                int x = left + getTransformedReferencePointX(transform);
                int y = top + getTransformedReferencePointY(transform);
                collisionRectLeft = 0;
                collisionRectTop = 0;
                collisionRectWidth = frameWidth;
                collisionRectHeight = frameHeight;
                transformedCollisionRectLeft = 0;
                transformedCollisionRectTop = 0;
                if((transform & 4) != 0)
                {
                    transformedCollisionRectWidth = width = frameHeight;
                    transformedCollisionRectHeight = height = frameWidth;
                } else
                {
                    transformedCollisionRectWidth = width = frameWidth;
                    transformedCollisionRectHeight = height = frameHeight;
                }
                left = x - getTransformedReferencePointX(transform);
                top = y - getTransformedReferencePointY(transform);
                frameSetFrameWidth = frameWidth;
                frameSetFrameHeight = frameHeight;
            }
            oldFramesCount = frameSetFramesCount;
            frameSetFramesPerRow = framesPerRow;
            frameSetFramesCount = framesCount;
            if(oldFramesCount == framesCount)
            {
                frameSequence = null;
            } else
            {
                frameSequence = new int[framesCount];
                for(int i = framesCount; i-- > 1; frameSequence[i] = i);
            }
            if(framesCount < oldFramesCount)
            {
                this.frameIndex = 0;
                this.frameSequence = frameSequence;
            }
            else if(framesCount > oldFramesCount && this.frameSequence == this.frameSequenceDefault)
            {
                this.frameSequence = frameSequence;
            }
            this.frameSequenceDefault = frameSequence;
            this.frameSet = frameSet;
        }
    }

    public void setFrameSequence(int[] sequence) {
        int len;
        int error;
        int[] frameSequence;
        if(sequence == null)
        {
            len = 0;
            frameSequence = null;
        } else
        {
            if((len = sequence.length) < 1)
            {
                throw new IllegalArgumentException("Sprite.setFrameSequence: аргумент sequence не может иметь длину меньше 1.");
            }
            Array.copy(sequence, 0, frameSequence = new int[len], 0, len);
        }
        error = 0;
        synchronized(monitor)
        {
            label0:
            {
                if(frameSequence == null)
                {
                    frameSequence = this.frameSequenceDefault;
                } else
                {
                    int framesCount = this.frameSetFramesCount;
                    for(int i = len; i-- > 0; )
                    {
                        int frame;
                        if((frame = frameSequence[i]) < 0 || frame >= framesCount)
                        {
                            error = 1;
                            break label0;
                        }
                    }
                }
                this.frameIndex = 0;
                this.frameSequence = frameSequence;
            }
        }
        if(error == 1)
        {
            throw new ArrayIndexOutOfBoundsException("Sprite.setFrameSequence: один из элементов аргумента sequence выходит из диапазона.");
        }
    }

    public void setFrame(int frameIndex) {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                if(frameIndex < 0 || frameIndex >= frameSequence.length)
                {
                    error = 1;
                    break label0;
                }
                this.frameIndex = frameIndex;
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("Sprite.setFrame: аргумент frameIndex выходит из диапазона.");
        }
    }

    public int getRefPixelX() {
        int result;
        synchronized(monitor)
        {
            result = left + getTransformedReferencePointX(transformation);
        }
        return result;
    }

    public int getRefPixelY() {
        int result;
        synchronized(monitor)
        {
            result = top + getTransformedReferencePointY(transformation);
        }
        return result;
    }

    public int getRawFrameCount() {
        return frameSetFramesCount;
    }

    public int getFrameSequenceLength() {
        return frameSequence.length;
    }

    public final void paint(Graphics render) {
        int x;
        int y;
        int frame;
        int transform;
        int frameWidth;
        int frameHeight;
        int framesPerRow;
        Image frameSetImage;
        if(render == null)
        {
            throw new NullPointerException("Sprite.paint: аргумент render равен нулевой ссылке.");
        }
        if(!visibility) return;
        synchronized(monitor)
        {
            x = left;
            y = top;
            frame = frameSequence[frameIndex];
            transform = transformation;
            frameWidth = frameSetFrameWidth;
            frameHeight = frameSetFrameHeight;
            framesPerRow = frameSetFramesPerRow;
            frameSetImage = frameSet;
        }
        render.drawRegion(frameSetImage, (frame % framesPerRow) * frameWidth, (frame / framesPerRow) * frameHeight, frameWidth, frameHeight, transform, x, y, 0);
    }

    public final boolean collidesWith(Sprite sprite, boolean pixelLevel) {
        boolean result;
        if(sprite == null)
        {
            throw new NullPointerException("Sprite.collidesWith: аргумент sprite равен нулевой ссылке.");
        }
        if(!this.visibility || !sprite.visibility) return false;
        synchronized(this.monitor)
        {
            synchronized(sprite.monitor)
            {
                label0:
                {
                    int layer1Left = this.left;
                    int layer1Top = this.top;
                    int layer1Right = layer1Left + this.width;
                    int layer1Bottom = layer1Top + this.height;
                    int thisLeft = layer1Left + this.transformedCollisionRectLeft;
                    int thisTop = layer1Top + this.transformedCollisionRectTop;
                    int thisRight = thisLeft + this.transformedCollisionRectWidth;
                    int thisBottom = thisTop + this.transformedCollisionRectHeight;
                    int layer2Left = sprite.left;
                    int layer2Top = sprite.top;
                    int layer2Right = layer2Left + sprite.width;
                    int layer2Bottom = layer2Top + sprite.height;
                    int spriteLeft = layer2Left + sprite.transformedCollisionRectLeft;
                    int spriteTop = layer2Top + sprite.transformedCollisionRectTop;
                    int spriteRight = spriteLeft + sprite.transformedCollisionRectWidth;
                    int spriteBottom = spriteTop + sprite.transformedCollisionRectHeight;
                    int resultLeft;
                    int resultTop;
                    int resultRight;
                    int resultBottom;
                    int resultWidth;
                    int resultHeight;
                    int thisFrameSetLeft;
                    int thisFrameSetTop;
                    int spriteFrameSetLeft;
                    int spriteFrameSetTop;
                    if(!isRectIntersection(thisLeft, thisTop, thisRight, thisBottom, spriteLeft, spriteTop, spriteRight, spriteBottom))
                    {
                        result = false;
                        break label0;
                    }
                    if(!pixelLevel)
                    {
                        result = true;
                        break label0;
                    }
                    resultLeft = Math.max(Math.max(layer1Left, thisLeft), Math.max(layer2Left, spriteLeft));
                    resultTop = Math.max(Math.max(layer1Top, thisTop), Math.max(layer2Top, spriteTop));
                    resultRight = Math.min(Math.min(layer1Right, thisRight), Math.min(layer2Right, spriteRight));
                    resultBottom = Math.min(Math.min(layer1Bottom, thisBottom), Math.min(layer2Bottom, spriteBottom));
                    if((resultWidth = resultRight - resultLeft) <= 0 || (resultHeight = resultBottom - resultTop) <= 0)
                    {
                        result = false;
                        break label0;
                    }
                    thisFrameSetLeft = this.getFrameSetLeft(resultLeft, resultTop, resultRight, resultBottom);
                    thisFrameSetTop = this.getFrameSetTop(resultLeft, resultTop, resultRight, resultBottom);
                    spriteFrameSetLeft = sprite.getFrameSetLeft(resultLeft, resultTop, resultRight, resultBottom);
                    spriteFrameSetTop = sprite.getFrameSetTop(resultLeft, resultTop, resultRight, resultBottom);
                    result = isPixelCollision(
                        resultWidth, resultHeight,
                        this.frameSet, thisFrameSetLeft, thisFrameSetTop, this.transformation,
                        sprite.frameSet, spriteFrameSetLeft, spriteFrameSetTop, sprite.transformation
                    );
                }
            }
        }
        return result;
    }

    public final boolean collidesWith(TiledLayer tiles, boolean pixelLevel) {
        boolean result;
        if(tiles == null)
        {
            throw new NullPointerException("Sprite.collidesWith: аргумент tiles равен нулевой ссылке.");
        }
        if(!this.visibility || !tiles.visibility) return false;
        synchronized(this.monitor)
        {
            synchronized(tiles.monitor)
            {
                label0:
                {
                    int layer1Left = this.left;
                    int layer1Top = this.top;
                    int layer1Right = layer1Left + this.width;
                    int layer1Bottom = layer1Top + this.height;
                    int thisLeft = layer1Left + this.transformedCollisionRectLeft;
                    int thisTop = layer1Top + this.transformedCollisionRectTop;
                    int thisRight = thisLeft + this.transformedCollisionRectWidth;
                    int thisBottom = thisTop + this.transformedCollisionRectHeight;
                    int layer2Left = tiles.left;
                    int layer2Top = tiles.top;
                    int layer2Right = layer2Left + tiles.width;
                    int layer2Bottom = layer2Top + tiles.height;
                    int resultLeft;
                    int resultTop;
                    int resultRight;
                    int resultBottom;
                    int cellsWidth;
                    int cellsHeight;
                    int cellLeft;
                    int cellTop;
                    int cellRight;
                    int cellBottom;
                    int cellsPerRow;
                    int transform;
                    Image frameSet;
                    Image tileSet;
                    if(!isRectIntersection(thisLeft, thisTop, thisRight, thisBottom, layer2Left, layer2Top, layer2Right, layer2Bottom))
                    {
                        result = false;
                        break label0;
                    }
                    resultLeft = Math.max(Math.max(layer1Left, thisLeft), layer2Left);
                    resultTop = Math.max(Math.max(layer1Top, thisTop), layer2Top);
                    resultRight = Math.min(Math.min(layer1Right, thisRight), layer2Right);
                    resultBottom = Math.min(Math.min(layer1Bottom, thisBottom), layer2Bottom);
                    if(resultRight - resultLeft <= 0 || resultBottom - resultTop <= 0)
                    {
                        result = false;
                        break label0;
                    }
                    cellsWidth = tiles.tileSetCellWidth;
                    cellsHeight = tiles.tileSetCellHeight;
                    cellLeft = (resultLeft - layer2Left) / cellsWidth;
                    cellTop = (resultTop - layer2Top) / cellsHeight;
                    cellRight = (resultRight - layer2Right - 1) / cellsWidth;
                    cellBottom = (resultBottom - layer2Bottom - 1) / cellsHeight;
                    if(!pixelLevel)
                    {
                        for(int row = cellTop; row <= cellBottom; row++) for(int col = cellLeft; col <= cellRight; col++) if(tiles.getTileIndex(col, row) != 0)
                        {
                            result = true;
                            break label0;
                        }
                        result = false;
                        break label0;
                    }
                    cellsPerRow = tiles.tileSetCellsPerRow;
                    transform = this.transformation;
                    frameSet = this.frameSet;
                    tileSet = tiles.tileSet;
                    for(int row = cellTop; row <= cellBottom; row++) for(int col = cellLeft; col <= cellRight; col++)
                    {
                        int cell;
                        int tileX;
                        int tileY;
                        int spriteLeft;
                        int spriteTop;
                        int spriteWidth;
                        int spriteHeight;
                        int delta;
                        int spriteRight;
                        int spriteBottom;
                        int frameSetLeft;
                        int frameSetTop;
                        if((cell = tiles.getTileIndex(col, row)) == 0) continue;
                        cell--;
                        tileX = (cell % cellsPerRow) * cellsWidth;
                        tileY = (cell / cellsPerRow) * cellsHeight;
                        spriteLeft = layer2Left + col * cellsWidth;
                        spriteTop = layer2Top + row * cellsHeight;
                        spriteWidth = cellsWidth;
                        spriteHeight = cellsHeight;
                        if((delta = resultLeft - spriteLeft) > 0)
                        {
                            tileX += delta;
                            spriteLeft += delta;
                            spriteWidth -= delta;
                        }
                        if((delta = spriteLeft + spriteWidth - resultRight) > 0) spriteWidth -= delta;
                        if((delta = resultTop - spriteTop) > 0)
                        {
                            tileY += delta;
                            spriteTop += delta;
                            spriteHeight -= delta;
                        }
                        if((delta = spriteTop + spriteHeight - resultBottom) > 0) spriteHeight -= delta;
                        spriteRight = spriteLeft + spriteWidth;
                        spriteBottom = spriteTop + spriteHeight;
                        frameSetLeft = getFrameSetLeft(spriteLeft, spriteTop, spriteRight, spriteBottom);
                        frameSetTop = getFrameSetTop(spriteLeft, spriteTop, spriteRight, spriteBottom);
                        if(isPixelCollision(spriteWidth, spriteHeight, frameSet, frameSetLeft, frameSetTop, transform, tileSet, tileX, tileY, TRANS_NONE))
                        {
                            result = true;
                            break label0;
                        }
                    }
                    result = false;
                }
            }
        }
        return result;
    }

    public final boolean collidesWith(Image image, int left, int top, boolean pixelLevel) {
        boolean result;
        if(image == null)
        {
            throw new NullPointerException("Sprite.collidesWith: аргумент image равен нулевой ссылке.");
        }
        if(!visibility) return false;
        synchronized(monitor)
        {
            label0:
            {
                int layerLeft = this.left;
                int layerTop = this.top;
                int layerRight = layerLeft + this.width;
                int layerBottom = layerTop + this.height;
                int thisLeft = layerLeft + transformedCollisionRectLeft;
                int thisTop = layerTop + transformedCollisionRectTop;
                int thisRight = thisLeft + transformedCollisionRectWidth;
                int thisBottom = thisTop + transformedCollisionRectHeight;
                int srcLeft = left;
                int srcTop = top;
                int srcRight = srcLeft + image.getWidth();
                int srcBottom = srcTop + image.getHeight();
                int resultLeft;
                int resultTop;
                int resultRight;
                int resultBottom;
                int resultWidth;
                int resultHeight;
                int frameSetLeft;
                int frameSetTop;
                if(!isRectIntersection(thisLeft, thisTop, thisRight, thisBottom, srcLeft, srcTop, srcRight, srcBottom))
                {
                    result = false;
                    break label0;
                }
                if(!pixelLevel)
                {
                    result = true;
                    break label0;
                }
                resultLeft = Math.max(Math.max(layerLeft, thisLeft), srcLeft);
                resultTop = Math.max(Math.max(layerTop, thisTop), srcTop);
                resultRight = Math.min(Math.min(layerRight, thisRight), srcRight);
                resultBottom = Math.min(Math.min(layerBottom, thisBottom), srcBottom);
                if((resultWidth = resultRight - resultLeft) <= 0 || (resultHeight = resultBottom - resultTop) <= 0)
                {
                    result = false;
                    break label0;
                }
                frameSetLeft = getFrameSetLeft(resultLeft, resultTop, resultRight, resultBottom);
                frameSetTop = getFrameSetTop(resultLeft, resultTop, resultRight, resultBottom);
                result = isPixelCollision(resultWidth, resultHeight, frameSet, frameSetLeft, frameSetTop, transformation, image, resultLeft - srcLeft, resultTop - srcTop, TRANS_NONE);
            }
        }
        return result;
    }

    public final int getFrame() {
        return frameIndex;
    }

    private void computeTransformedBounds(int transform) {
        int fw;
        int fh;
        int crw;
        int crh;
        switch(transform)
        {
        default:
            width = frameSetFrameWidth;
            height = frameSetFrameHeight;
            transformedCollisionRectLeft = collisionRectLeft;
            transformedCollisionRectTop = collisionRectTop;
            transformedCollisionRectWidth = collisionRectWidth;
            transformedCollisionRectHeight = collisionRectHeight;
            break;
        case TRANS_ROT90:
            fh = frameSetFrameHeight;
            crh = collisionRectHeight;
            width = fh;
            height = frameSetFrameWidth;
            transformedCollisionRectLeft = fh - crh - collisionRectTop;
            transformedCollisionRectTop = collisionRectLeft;
            transformedCollisionRectWidth = crh;
            transformedCollisionRectHeight = collisionRectWidth;
            break;
        case TRANS_ROT180:
            fw = frameSetFrameWidth;
            fh = frameSetFrameHeight;
            crw = collisionRectWidth;
            crh = collisionRectHeight;
            width = fw;
            height = fh;
            transformedCollisionRectLeft = fw - crw - collisionRectLeft;
            transformedCollisionRectTop = fh - crh - collisionRectTop;
            transformedCollisionRectWidth = crw;
            transformedCollisionRectHeight = crh;
            break;
        case TRANS_ROT270:
            fw = frameSetFrameWidth;
            crw = collisionRectWidth;
            width = frameSetFrameHeight;
            height = fw;
            transformedCollisionRectLeft = collisionRectTop;
            transformedCollisionRectTop = fw - crw - collisionRectLeft;
            transformedCollisionRectWidth = collisionRectHeight;
            transformedCollisionRectHeight = crw;
            break;
        case TRANS_MIRROR:
            fw = frameSetFrameWidth;
            crw = collisionRectWidth;
            width = fw;
            height = frameSetFrameHeight;
            transformedCollisionRectLeft = fw - crw - collisionRectLeft;
            transformedCollisionRectTop = collisionRectTop;
            transformedCollisionRectWidth = crw;
            transformedCollisionRectHeight = collisionRectHeight;
            break;
        case TRANS_MIRROR_ROT90:
            fw = frameSetFrameWidth;
            fh = frameSetFrameHeight;
            crw = collisionRectWidth;
            crh = collisionRectHeight;
            width = fh;
            height = fw;
            transformedCollisionRectLeft = fh - crh - collisionRectTop;
            transformedCollisionRectTop = fw - crw - collisionRectLeft;
            transformedCollisionRectWidth = crh;
            transformedCollisionRectHeight = crw;
            break;
        case TRANS_MIRROR_ROT180:
            fh = frameSetFrameHeight;
            crh = collisionRectHeight;
            width = frameSetFrameWidth;
            height = fh;
            transformedCollisionRectLeft = collisionRectLeft;
            transformedCollisionRectTop = fh - crh - collisionRectTop;
            transformedCollisionRectWidth = collisionRectWidth;
            transformedCollisionRectHeight = crh;
            break;
        case TRANS_MIRROR_ROT270:
            width = frameSetFrameHeight;
            height = frameSetFrameWidth;
            transformedCollisionRectLeft = collisionRectTop;
            transformedCollisionRectTop = collisionRectLeft;
            transformedCollisionRectWidth = collisionRectHeight;
            transformedCollisionRectHeight = collisionRectWidth;
            break;
        }
    }

    private int getTransformedReferencePointX(int transform) {
        switch(transform)
        {
        default:
            return referencePointX;
        case TRANS_ROT90:
        case TRANS_MIRROR_ROT90:
            return frameSetFrameHeight - referencePointY - 1;
        case TRANS_ROT180:
        case TRANS_MIRROR:
            return frameSetFrameWidth - referencePointX - 1;
        case TRANS_ROT270:
        case TRANS_MIRROR_ROT270:
            return referencePointY;
        }
    }

    private int getTransformedReferencePointY(int transform) {
        switch(transform)
        {
        default:
            return referencePointY;
        case TRANS_ROT90:
        case TRANS_MIRROR_ROT270:
            return referencePointX;
        case TRANS_ROT180:
        case TRANS_MIRROR_ROT180:
            return frameSetFrameHeight - referencePointY - 1;
        case TRANS_ROT270:
        case TRANS_MIRROR_ROT90:
            return frameSetFrameWidth - referencePointX - 1;
        }
    }

    private int getFrameSetLeft(int left, int top, int right, int bottom) {
        int result;
        switch(transformation)
        {
        default:
            result = left - this.left;
            break;
        case TRANS_ROT90:
        case TRANS_MIRROR_ROT270:
            result = top - this.top;
            break;
        case TRANS_ROT180:
        case TRANS_MIRROR:
            result = this.left + this.width - right;
            break;
        case TRANS_ROT270:
        case TRANS_MIRROR_ROT90:
            result = this.top + this.height - bottom;
            break;
        }
        return result + (frameSequence[frameIndex] % frameSetFramesPerRow) * frameSetFrameWidth;
    }

    private int getFrameSetTop(int left, int top, int right, int bottom) {
        int result;
        switch(transformation)
        {
        default:
            result = top - this.top;
            break;
        case TRANS_ROT90:
        case TRANS_MIRROR_ROT90:
            result = this.left + this.width - right;
            break;
        case TRANS_ROT180:
        case TRANS_MIRROR_ROT180:
            result = this.top + this.height - bottom;
            break;
        case TRANS_ROT270:
        case TRANS_MIRROR_ROT270:
            result = left - this.left;
            break;
        }
        return result + (frameSequence[frameIndex] / frameSetFramesPerRow) * frameSetFrameHeight;
    }
}
