namespace GarboDev
{
    using System;
    using System.Collections.Generic;
    using System.Text;

    public partial class Renderer : IRenderer
    {
        private Memory memory;
        private uint[] scanline = new uint[240];
        private byte[] blend = new byte[240];
        private byte[] windowCover = new byte[240];
        private uint[] back = new uint[240 * 160];
        //private uint[] front = new uint[240 * 160];
        private const uint pitch = 240;

        // Convenience variable as I use it everywhere, set once in RenderLine
        private ushort dispCnt;

        // Window helper variables
        private byte win0x1, win0x2, win0y1, win0y2;
        private byte win1x1, win1x2, win1y1, win1y2;
        private byte win0Enabled, win1Enabled, winObjEnabled, winOutEnabled;
        private bool winEnabled;

        private byte blendSource, blendTarget;
        private byte blendA, blendB, blendY;
        private int blendType;

        private int curLine = 0;

        private static uint[] colorLUT;

        static Renderer()
        {
            colorLUT = new uint[0x10000];
            // Pre-calculate the color LUT
            for (uint i = 0; i <= 0xFFFF; i++)
            {
                uint r = (i & 0x1FU);
                uint g = (i & 0x3E0U) >> 5;
                uint b = (i & 0x7C00U) >> 10;
                r = (r << 3) | (r >> 2);
                g = (g << 3) | (g >> 2);
                b = (b << 3) | (b >> 2);
                colorLUT[i] = (r << 16) | (g << 8) | b;
            }
        }

        public Memory Memory
        {
            set { this.memory = value; }
        }

        public void Initialize(object data)
        {
        }

        public void Reset()
        {
        }

        public uint[] ShowFrame()
        {
            //Array.Copy(this.back, this.front, this.front.Length);

            //return this.front;
			return this.back;
        }

        public void RenderLine(int line)
        {
            this.curLine = line;

            // Render the line
            this.dispCnt = Memory.ReadU16(this.memory.IORam, Memory.DISPCNT);

            if ((this.dispCnt & (1 << 7)) != 0)
            {
                uint bgColor = Renderer.GbaTo32((ushort)0x7FFF);
                for (int i = 0; i < 240; i++) this.scanline[i] = bgColor;
            }
            else
            {
                this.winEnabled = false;

                if ((this.dispCnt & (1 << 13)) != 0)
                {
                    // Calculate window 0 information
                    ushort winy = Memory.ReadU16(this.memory.IORam, Memory.WIN0V);
                    this.win0y1 = (byte)(winy >> 8);
                    this.win0y2 = (byte)(winy & 0xff);
                    ushort winx = Memory.ReadU16(this.memory.IORam, Memory.WIN0H);
                    this.win0x1 = (byte)(winx >> 8);
                    this.win0x2 = (byte)(winx & 0xff);

                    if (this.win0x2 > 240 || this.win0x1 > this.win0x2)
                    {
                        this.win0x2 = 240;
                    }

                    if (this.win0y2 > 160 || this.win0y1 > this.win0y2)
                    {
                        this.win0y2 = 160;
                    }

                    this.win0Enabled = this.memory.IORam[Memory.WININ];
                    this.winEnabled = true;
                }

                if ((this.dispCnt & (1 << 14)) != 0)
                {
                    // Calculate window 1 information
                    ushort winy = Memory.ReadU16(this.memory.IORam, Memory.WIN1V);
                    this.win1y1 = (byte)(winy >> 8);
                    this.win1y2 = (byte)(winy & 0xff);
                    ushort winx = Memory.ReadU16(this.memory.IORam, Memory.WIN1H);
                    this.win1x1 = (byte)(winx >> 8);
                    this.win1x2 = (byte)(winx & 0xff);

                    if (this.win1x2 > 240 || this.win1x1 > this.win1x2)
                    {
                        this.win1x2 = 240;
                    }

                    if (this.win1y2 > 160 || this.win1y1 > this.win1y2)
                    {
                        this.win1y2 = 160;
                    }

                    this.win1Enabled = this.memory.IORam[Memory.WININ + 1];
                    this.winEnabled = true;
                }

                if ((this.dispCnt & (1 << 15)) != 0 && (this.dispCnt & (1 << 12)) != 0)
                {
                    // Object windows are enabled
                    this.winObjEnabled = this.memory.IORam[Memory.WINOUT + 1];
                    this.winEnabled = true;
                }

                if (this.winEnabled)
                {
                    this.winOutEnabled = this.memory.IORam[Memory.WINOUT];
                }

                // Calculate blending information
                ushort bldcnt = Memory.ReadU16(this.memory.IORam, Memory.BLDCNT);
                this.blendType = (bldcnt >> 6) & 0x3;
                this.blendSource = (byte)(bldcnt & 0x3F);
                this.blendTarget = (byte)((bldcnt >> 8) & 0x3F);

                ushort bldalpha = Memory.ReadU16(this.memory.IORam, Memory.BLDALPHA);
                this.blendA = (byte)(bldalpha & 0x1F);
                if (this.blendA > 0x10) this.blendA = 0x10;
                this.blendB = (byte)((bldalpha >> 8) & 0x1F);
                if (this.blendB > 0x10) this.blendB = 0x10;

                this.blendY = (byte)(this.memory.IORam[Memory.BLDY] & 0x1F);
                if (this.blendY > 0x10) this.blendY = 0x10;

                switch (this.dispCnt & 0x7)
                {
                    case 0: this.RenderMode0Line(); break;
                    case 1: this.RenderMode1Line(); break;
                    case 2: this.RenderMode2Line(); break;
                    case 3: this.RenderMode3Line(); break;
                    case 4: this.RenderMode4Line(); break;
                    case 5: this.RenderMode5Line(); break;
                }
            }

            Array.Copy(this.scanline, 0, this.back, this.curLine * Renderer.pitch, Renderer.pitch);
        }

        private void DrawBackdrop()
        {
            byte[] palette = this.memory.PaletteRam;

            // Initialize window coverage buffer if neccesary
            if (this.winEnabled)
            {
                for (int i = 0; i < 240; i++)
                {
                    this.windowCover[i] = this.winOutEnabled;
                }

                if ((this.dispCnt & (1 << 15)) != 0)
                {
                    // Sprite window
                    this.DrawSpriteWindows();
                }

                if ((this.dispCnt & (1 << 14)) != 0)
                {
                    // Window 1
                    if (this.curLine >= this.win1y1 && this.curLine < this.win1y2)
                    {
                        for (int i = this.win1x1; i < this.win1x2; i++)
                        {
                            this.windowCover[i] = this.win1Enabled;
                        }
                    }
                }

                if ((this.dispCnt & (1 << 13)) != 0)
                {
                    // Window 0
                    if (this.curLine >= this.win0y1 && this.curLine < this.win0y2)
                    {
                        for (int i = this.win0x1; i < this.win0x2; i++)
                        {
                            this.windowCover[i] = this.win0Enabled;
                        }
                    }
                }
            }

            // Draw backdrop first
            uint bgColor = Renderer.GbaTo32((ushort)(palette[0] | (palette[1] << 8)));
            uint modColor = bgColor;

            if (this.blendType == 2 && (this.blendSource & (1 << 5)) != 0)
            {
                // Brightness increase
                uint r = bgColor & 0xFF;
                uint g = (bgColor >> 8) & 0xFF;
                uint b = (bgColor >> 16) & 0xFF;
                r = r + (((0xFF - r) * this.blendY) >> 4);
                g = g + (((0xFF - g) * this.blendY) >> 4);
                b = b + (((0xFF - b) * this.blendY) >> 4);
                modColor = r | (g << 8) | (b << 16);
            }
            else if (this.blendType == 3 && (this.blendSource & (1 << 5)) != 0)
            {
                // Brightness decrease
                uint r = bgColor & 0xFF;
                uint g = (bgColor >> 8) & 0xFF;
                uint b = (bgColor >> 16) & 0xFF;
                r = r - ((r * this.blendY) >> 4);
                g = g - ((g * this.blendY) >> 4);
                b = b - ((b * this.blendY) >> 4);
                modColor = r | (g << 8) | (b << 16);
            }

            if (this.winEnabled)
            {
                for (int i = 0; i < 240; i++)
                {
                    if ((this.windowCover[i] & (1 << 5)) != 0)
                    {
                        this.scanline[i] = modColor;
                    }
                    else
                    {
                        this.scanline[i] = bgColor;
                    }
                    this.blend[i] = 1 << 5;
                }
            }
            else
            {
                for (int i = 0; i < 240; i++)
                {
                    this.scanline[i] = modColor;
                    this.blend[i] = 1 << 5;
                }
            }
        }

        private void RenderTextBg(int bg)
        {
            if (this.winEnabled)
            {
                switch (this.blendType)
                {
                    case 0:
                        this.RenderTextBgWindow(bg);
                        break;
                    case 1:
                        if ((this.blendSource & (1 << bg)) != 0)
                            this.RenderTextBgWindowBlend(bg);
                        else
                            this.RenderTextBgWindow(bg);
                        break;
                    case 2:
                        if ((this.blendSource & (1 << bg)) != 0)
                            this.RenderTextBgWindowBrightInc(bg);
                        else
                            this.RenderTextBgWindow(bg);
                        break;
                    case 3:
                        if ((this.blendSource & (1 << bg)) != 0)
                            this.RenderTextBgWindowBrightDec(bg);
                        else
                            this.RenderTextBgWindow(bg);
                        break;
                }
            }
            else
            {
                switch (this.blendType)
                {
                    case 0:
                        this.RenderTextBgNormal(bg);
                        break;
                    case 1:
                        if ((this.blendSource & (1 << bg)) != 0)
                            this.RenderTextBgBlend(bg);
                        else
                            this.RenderTextBgNormal(bg);
                        break;
                    case 2:
                        if ((this.blendSource & (1 << bg)) != 0)
                            this.RenderTextBgBrightInc(bg);
                        else
                            this.RenderTextBgNormal(bg);
                        break;
                    case 3:
                        if ((this.blendSource & (1 << bg)) != 0)
                            this.RenderTextBgBrightDec(bg);
                        else
                            this.RenderTextBgNormal(bg);
                        break;
                }
            }
        }

        private void RenderRotScaleBg(int bg)
        {
            if (this.winEnabled)
            {
                switch (this.blendType)
                {
                    case 0:
                        this.RenderRotScaleBgWindow(bg);
                        break;
                    case 1:
                        if ((this.blendSource & (1 << bg)) != 0)
                            this.RenderRotScaleBgWindowBlend(bg);
                        else
                            this.RenderRotScaleBgWindow(bg);
                        break;
                    case 2:
                        if ((this.blendSource & (1 << bg)) != 0)
                            this.RenderRotScaleBgWindowBrightInc(bg);
                        else
                            this.RenderRotScaleBgWindow(bg);
                        break;
                    case 3:
                        if ((this.blendSource & (1 << bg)) != 0)
                            this.RenderRotScaleBgWindowBrightDec(bg);
                        else
                            this.RenderRotScaleBgWindow(bg);
                        break;
                }
            }
            else
            {
                switch (this.blendType)
                {
                    case 0:
                        this.RenderRotScaleBgNormal(bg);
                        break;
                    case 1:
                        if ((this.blendSource & (1 << bg)) != 0)
                            this.RenderRotScaleBgBlend(bg);
                        else
                            this.RenderRotScaleBgNormal(bg);
                        break;
                    case 2:
                        if ((this.blendSource & (1 << bg)) != 0)
                            this.RenderRotScaleBgBrightInc(bg);
                        else
                            this.RenderRotScaleBgNormal(bg);
                        break;
                    case 3:
                        if ((this.blendSource & (1 << bg)) != 0)
                            this.RenderRotScaleBgBrightDec(bg);
                        else
                            this.RenderRotScaleBgNormal(bg);
                        break;
                }
            }
        }

        private void DrawSprites(int pri)
        {
            if (this.winEnabled)
            {
                switch (this.blendType)
                {
                    case 0:
                        this.DrawSpritesWindow(pri);
                        break;
                    case 1:
                        if ((this.blendSource & (1 << 4)) != 0)
                            this.DrawSpritesWindowBlend(pri);
                        else
                            this.DrawSpritesWindow(pri);
                        break;
                    case 2:
                        if ((this.blendSource & (1 << 4)) != 0)
                            this.DrawSpritesWindowBrightInc(pri);
                        else
                            this.DrawSpritesWindow(pri);
                        break;
                    case 3:
                        if ((this.blendSource & (1 << 4)) != 0)
                            this.DrawSpritesWindowBrightDec(pri);
                        else
                            this.DrawSpritesWindow(pri);
                        break;
                }
            }
            else
            {
                switch (this.blendType)
                {
                    case 0:
                        this.DrawSpritesNormal(pri);
                        break;
                    case 1:
                        if ((this.blendSource & (1 << 4)) != 0)
                            this.DrawSpritesBlend(pri);
                        else
                            this.DrawSpritesNormal(pri);
                        break;
                    case 2:
                        if ((this.blendSource & (1 << 4)) != 0)
                            this.DrawSpritesBrightInc(pri);
                        else
                            this.DrawSpritesNormal(pri);
                        break;
                    case 3:
                        if ((this.blendSource & (1 << 4)) != 0)
                            this.DrawSpritesBrightDec(pri);
                        else
                            this.DrawSpritesNormal(pri);
                        break;
                }
            }
        }

        private void RenderMode0Line()
        {
            byte[] palette = this.memory.PaletteRam;
            byte[] vram = this.memory.VideoRam;

            this.DrawBackdrop();

            for (int pri = 3; pri >= 0; pri--)
            {
                for (int i = 3; i >= 0; i--)
                {
                    if ((this.dispCnt & (1 << (8 + i))) != 0)
                    {
                        ushort bgcnt = Memory.ReadU16(this.memory.IORam, Memory.BG0CNT + 0x2 * (uint)i);

                        if ((bgcnt & 0x3) == pri)
                        {
                            this.RenderTextBg(i);
                        }
                    }
                }

                this.DrawSprites(pri);
            }
        }

        private void RenderMode1Line()
        {
            byte[] palette = this.memory.PaletteRam;
            byte[] vram = this.memory.VideoRam;

            this.DrawBackdrop();

            for (int pri = 3; pri >= 0; pri--)
            {
                if ((this.dispCnt & (1 << (8 + 2))) != 0)
                {
                    ushort bgcnt = Memory.ReadU16(this.memory.IORam, Memory.BG2CNT);

                    if ((bgcnt & 0x3) == pri)
                    {
                        this.RenderRotScaleBg(2);
                    }
                }

                for (int i = 1; i >= 0; i--)
                {
                    if ((this.dispCnt & (1 << (8 + i))) != 0)
                    {
                        ushort bgcnt = Memory.ReadU16(this.memory.IORam, Memory.BG0CNT + 0x2 * (uint)i);

                        if ((bgcnt & 0x3) == pri)
                        {
                            this.RenderTextBg(i);
                        }
                    }
                }

                this.DrawSprites(pri);
            }
        }

        private void RenderMode2Line()
        {
            byte[] palette = this.memory.PaletteRam;
            byte[] vram = this.memory.VideoRam;

            this.DrawBackdrop();

            for (int pri = 3; pri >= 0; pri--)
            {
                for (int i = 3; i >= 2; i--)
                {
                    if ((this.dispCnt & (1 << (8 + i))) != 0)
                    {
                        ushort bgcnt = Memory.ReadU16(this.memory.IORam, Memory.BG0CNT + 0x2 * (uint)i);

                        if ((bgcnt & 0x3) == pri)
                        {
                            this.RenderRotScaleBg(i);
                        }
                    }
                }

                this.DrawSprites(pri);
            }
        }

        private void RenderMode3Line()
        {
            ushort bg2Cnt = Memory.ReadU16(this.memory.IORam, Memory.BG2CNT);

            byte[] palette = this.memory.PaletteRam;
            byte[] vram = this.memory.VideoRam;

            this.DrawBackdrop();

            byte blendMaskType = (byte)(1 << 2);

            int bgPri = bg2Cnt & 0x3;
            for (int pri = 3; pri > bgPri; pri--)
            {
                this.DrawSprites(pri);
            }

            if ((this.dispCnt & (1 << 10)) != 0)
            {
                // Background enabled, render it
                int x = this.memory.Bgx[0];
                int y = this.memory.Bgy[0];

                short dx = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PA);
                short dy = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PC);

                for (int i = 0; i < 240; i++)
                {
                    int ax = ((int)x) >> 8;
                    int ay = ((int)y) >> 8;

                    if (ax >= 0 && ax < 240 && ay >= 0 && ay < 160)
                    {
                        int curIdx = ((ay * 240) + ax) * 2;
                        this.scanline[i] = Renderer.GbaTo32((ushort)(vram[curIdx] | (vram[curIdx + 1] << 8)));
                        this.blend[i] = blendMaskType;
                    }

                    x += dx;
                    y += dy;
                }
            }

            for (int pri = bgPri; pri >= 0; pri--)
            {
                this.DrawSprites(pri);
            }
        }

        private void RenderMode4Line()
        {
            ushort bg2Cnt = Memory.ReadU16(this.memory.IORam, Memory.BG2CNT);

            byte[] palette = this.memory.PaletteRam;
            byte[] vram = this.memory.VideoRam;

            this.DrawBackdrop();

            byte blendMaskType = (byte)(1 << 2);

            int bgPri = bg2Cnt & 0x3;
            for (int pri = 3; pri > bgPri; pri--)
            {
                this.DrawSprites(pri);
            }

            if ((this.dispCnt & (1 << 10)) != 0)
            {
                // Background enabled, render it
                int baseIdx = 0;
                if ((this.dispCnt & (1 << 4)) == 1 << 4) baseIdx = 0xA000;

                int x = this.memory.Bgx[0];
                int y = this.memory.Bgy[0];

                short dx = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PA);
                short dy = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PC);

                for (int i = 0; i < 240; i++)
                {
                    int ax = ((int)x) >> 8;
                    int ay = ((int)y) >> 8;

                    if (ax >= 0 && ax < 240 && ay >= 0 && ay < 160)
                    {
                        int lookup = vram[baseIdx + (ay * 240) + ax];
                        if (lookup != 0)
                        {
                            this.scanline[i] = Renderer.GbaTo32((ushort)(palette[lookup * 2] | (palette[lookup * 2 + 1] << 8)));
                            this.blend[i] = blendMaskType;
                        }
                    }

                    x += dx;
                    y += dy;
                }
            }

            for (int pri = bgPri; pri >= 0; pri--)
            {
                this.DrawSprites(pri);
            }
        }

        private void RenderMode5Line()
        {
            ushort bg2Cnt = Memory.ReadU16(this.memory.IORam, Memory.BG2CNT);

            byte[] palette = this.memory.PaletteRam;
            byte[] vram = this.memory.VideoRam;

            this.DrawBackdrop();

            byte blendMaskType = (byte)(1 << 2);

            int bgPri = bg2Cnt & 0x3;
            for (int pri = 3; pri > bgPri; pri--)
            {
                this.DrawSprites(pri);
            }

            if ((this.dispCnt & (1 << 10)) != 0)
            {
                // Background enabled, render it
                int baseIdx = 0;
                if ((this.dispCnt & (1 << 4)) == 1 << 4) baseIdx += 160 * 128 * 2;

                int x = this.memory.Bgx[0];
                int y = this.memory.Bgy[0];

                short dx = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PA);
                short dy = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PC);

                for (int i = 0; i < 240; i++)
                {
                    int ax = ((int)x) >> 8;
                    int ay = ((int)y) >> 8;

                    if (ax >= 0 && ax < 160 && ay >= 0 && ay < 128)
                    {
                        int curIdx = (int)(ay * 160 + ax) * 2;

                        this.scanline[i] = Renderer.GbaTo32((ushort)(vram[baseIdx + curIdx] | (vram[baseIdx + curIdx + 1] << 8)));
                        this.blend[i] = blendMaskType;
                    }

                    x += dx;
                    y += dy;
                }
            }

            for (int pri = bgPri; pri >= 0; pri--)
            {
                this.DrawSprites(pri);
            }
        }

        private void DrawSpriteWindows()
        {
            byte[] palette = this.memory.PaletteRam;
            byte[] vram = this.memory.VideoRam;

            // OBJ must be enabled in this.dispCnt
            if ((this.dispCnt & (1 << 12)) == 0) return;

            for (int oamNum = 127; oamNum >= 0; oamNum--)
            {
                ushort attr0 = this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(oamNum * 8) + 0);
                
                // Not an object window, so continue
                if (((attr0 >> 10) & 3) != 2) continue;

                ushort attr1 = this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(oamNum * 8) + 2);
                ushort attr2 = this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(oamNum * 8) + 4);

                int x = attr1 & 0x1FF;
                int y = attr0 & 0xFF;

                int width = -1, height = -1;
                switch ((attr0 >> 14) & 3)
                {
                    case 0:
                        // Square
                        switch ((attr1 >> 14) & 3)
                        {
                            case 0: width = 8; height = 8; break;
                            case 1: width = 16; height = 16; break;
                            case 2: width = 32; height = 32; break;
                            case 3: width = 64; height = 64; break;
                        }
                        break;
                    case 1:
                        // Horizontal Rectangle
                        switch ((attr1 >> 14) & 3)
                        {
                            case 0: width = 16; height = 8; break;
                            case 1: width = 32; height = 8; break;
                            case 2: width = 32; height = 16; break;
                            case 3: width = 64; height = 32; break;
                        }
                        break;
                    case 2:
                        // Vertical Rectangle
                        switch ((attr1 >> 14) & 3)
                        {
                            case 0: width = 8; height = 16; break;
                            case 1: width = 8; height = 32; break;
                            case 2: width = 16; height = 32; break;
                            case 3: width = 32; height = 64; break;
                        }
                        break;
                }

                // Check double size flag here

                int rwidth = width, rheight = height;
                if ((attr0 & (1 << 8)) != 0)
                {
                    // Rot-scale on
                    if ((attr0 & (1 << 9)) != 0)
                    {
                        rwidth *= 2;
                        rheight *= 2;
                    }
                }
                else
                {
                    // Invalid sprite
                    if ((attr0 & (1 << 9)) != 0)
                        width = -1;
                }

                if (width == -1)
                {
                    // Invalid sprite
                    continue;
                }

                // Y clipping
                if (y > ((y + rheight) & 0xff))
                {
                    if (this.curLine >= ((y + rheight) & 0xff) && !(y < this.curLine)) continue;
                }
                else
                {
                    if (this.curLine < y || this.curLine >= ((y + rheight) & 0xff)) continue;
                }

                int scale = 1;
                if ((attr0 & (1 << 13)) != 0) scale = 2;

                int spritey = this.curLine - y;
                if (spritey < 0) spritey += 256;

                if ((attr0 & (1 << 8)) == 0)
                {
                    if ((attr1 & (1 << 13)) != 0) spritey = (height - 1) - spritey;

                    int baseSprite;
                    if ((this.dispCnt & (1 << 6)) != 0)
                    {
                        // 1 dimensional
                        baseSprite = (attr2 & 0x3FF) + ((spritey / 8) * (width / 8)) * scale;
                    }
                    else
                    {
                        // 2 dimensional
                        baseSprite = (attr2 & 0x3FF) + ((spritey / 8) * 0x20);
                    }

                    int baseInc = scale;
                    if ((attr1 & (1 << 12)) != 0)
                    {
                        baseSprite += ((width / 8) * scale) - scale;
                        baseInc = -baseInc;
                    }

                    if ((attr0 & (1 << 13)) != 0)
                    {
                        // 256 colors
                        for (int i = x; i < x + width; i++)
                        {
                            if ((i & 0x1ff) < 240)
                            {
                                int tx = (i - x) & 7;
                                if ((attr1 & (1 << 12)) != 0) tx = 7 - tx;
                                int curIdx = baseSprite * 32 + ((spritey & 7) * 8) + tx;
                                int lookup = vram[0x10000 + curIdx];
                                if (lookup != 0)
                                {
                                    this.windowCover[i & 0x1ff] = this.winObjEnabled;
                                }
                            }
                            if (((i - x) & 7) == 7) baseSprite += baseInc;
                        }
                    }
                    else
                    {
                        // 16 colors
                        int palIdx = 0x200 + (((attr2 >> 12) & 0xF) * 16 * 2);
                        for (int i = x; i < x + width; i++)
                        {
                            if ((i & 0x1ff) < 240)
                            {
                                int tx = (i - x) & 7;
                                if ((attr1 & (1 << 12)) != 0) tx = 7 - tx;
                                int curIdx = baseSprite * 32 + ((spritey & 7) * 4) + (tx / 2);
                                int lookup = vram[0x10000 + curIdx];
                                if ((tx & 1) == 0)
                                {
                                    lookup &= 0xf;
                                }
                                else
                                {
                                    lookup >>= 4;
                                }
                                if (lookup != 0)
                                {
                                    this.windowCover[i & 0x1ff] = this.winObjEnabled;
                                }
                            }
                            if (((i - x) & 7) == 7) baseSprite += baseInc;
                        }
                    }
                }
                else
                {
                    int rotScaleParam = (attr1 >> 9) & 0x1F;

                    short dx = (short)this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(rotScaleParam * 8 * 4) + 0x6);
                    short dmx = (short)this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(rotScaleParam * 8 * 4) + 0xE);
                    short dy = (short)this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(rotScaleParam * 8 * 4) + 0x16);
                    short dmy = (short)this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(rotScaleParam * 8 * 4) + 0x1E);

                    int cx = rwidth / 2;
                    int cy = rheight / 2;

                    int baseSprite = attr2 & 0x3FF;
                    int pitch;

                    if ((this.dispCnt & (1 << 6)) != 0)
                    {
                        // 1 dimensional
                        pitch = (width / 8) * scale;
                    }
                    else
                    {
                        // 2 dimensional
                        pitch = 0x20;
                    }

                    short rx = (short)((dmx * (spritey - cy)) - (cx * dx) + (width << 7));
                    short ry = (short)((dmy * (spritey - cy)) - (cx * dy) + (height << 7));

                    // Draw a rot/scale sprite
                    if ((attr0 & (1 << 13)) != 0)
                    {
                        // 256 colors
                        for (int i = x; i < x + rwidth; i++)
                        {
                            int tx = rx >> 8;
                            int ty = ry >> 8;

                            if ((i & 0x1ff) < 240 && tx >= 0 && tx < width && ty >= 0 && ty < height)
                            {
                                int curIdx = (baseSprite + ((ty / 8) * pitch) + ((tx / 8) * scale)) * 32 + ((ty & 7) * 8) + (tx & 7);
                                int lookup = vram[0x10000 + curIdx];
                                if (lookup != 0)
                                {
                                    this.windowCover[i & 0x1ff] = this.winObjEnabled;
                                }
                            }

                            rx += dx;
                            ry += dy;
                        }
                    }
                    else
                    {
                        // 16 colors
                        int palIdx = 0x200 + (((attr2 >> 12) & 0xF) * 16 * 2);
                        for (int i = x; i < x + rwidth; i++)
                        {
                            int tx = rx >> 8;
                            int ty = ry >> 8;

                            if ((i & 0x1ff) < 240 && tx >= 0 && tx < width && ty >= 0 && ty < height)
                            {
                                int curIdx = (baseSprite + ((ty / 8) * pitch) + ((tx / 8) * scale)) * 32 + ((ty & 7) * 4) + ((tx & 7) / 2);
                                int lookup = vram[0x10000 + curIdx];
                                if ((tx & 1) == 0)
                                {
                                    lookup &= 0xf;
                                }
                                else
                                {
                                    lookup >>= 4;
                                }
                                if (lookup != 0)
                                {
                                    this.windowCover[i & 0x1ff] = this.winObjEnabled;
                                }
                            }

                            rx += dx;
                            ry += dy;
                        }
                    }
                }
            }
        }

        public static uint GbaTo32(ushort color)
        {
            // more accurate, but slower :(
            // return colorLUT[color];
            return ((color & 0x1FU) << 19) | ((color & 0x3E0U) << 6) | ((color & 0x7C00U) >> 7);
        }
    }
}
