using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using Texture2DDecoder;

namespace AssetStudio
{
    public class Texture2DConverter
    {
        private int m_Width;
        private int m_Height;
        private TextureFormat m_TextureFormat;
        private int image_data_size;
        private byte[] image_data;
        private int[] version;
        private BuildTarget platform;

        public Texture2DConverter(Texture2D m_Texture2D)
        {
            image_data = m_Texture2D.image_data.GetData();
            image_data_size = image_data.Length;
            m_Width = m_Texture2D.m_Width;
            m_Height = m_Texture2D.m_Height;
            m_TextureFormat = m_Texture2D.m_TextureFormat;
            version = m_Texture2D.version;
            platform = m_Texture2D.platform;
        }

        public Bitmap ConvertToBitmap(bool flip)
        {
            if (image_data == null || image_data.Length == 0)
                return null;
            var buff = DecodeTexture2D();
            if (buff == null)
            {
                return null;
            }
            var handle = GCHandle.Alloc(buff, GCHandleType.Pinned);
            var scan0 = handle.AddrOfPinnedObject();
            var bitmap = new Bitmap(m_Width, m_Height, m_Width * 4, PixelFormat.Format32bppArgb, scan0);
            handle.Free();
            if (flip)
            {
                bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
            }
            return bitmap;
        }

        public byte[] DecodeTexture2D()
        {
            byte[] bytes = null;
            switch (m_TextureFormat)
            {
                case TextureFormat.Alpha8: //test pass
                    bytes = DecodeAlpha8();
                    break;
                case TextureFormat.ARGB4444: //test pass
                    SwapBytesForXbox();
                    bytes = DecodeARGB4444();
                    break;
                case TextureFormat.RGB24: //test pass
                    bytes = DecodeRGB24();
                    break;
                case TextureFormat.RGBA32: //test pass
                    bytes = DecodeRGBA32();
                    break;
                case TextureFormat.ARGB32: //test pass
                    bytes = DecodeARGB32();
                    break;
                case TextureFormat.RGB565: //test pass
                    SwapBytesForXbox();
                    bytes = DecodeRGB565();
                    break;
                case TextureFormat.R16: //test pass
                    bytes = DecodeR16();
                    break;
                case TextureFormat.DXT1: //test pass
                    SwapBytesForXbox();
                    bytes = DecodeDXT1();
                    break;
                case TextureFormat.DXT5: //test pass
                    SwapBytesForXbox();
                    bytes = DecodeDXT5();
                    break;
                case TextureFormat.RGBA4444: //test pass
                    bytes = DecodeRGBA4444();
                    break;
                case TextureFormat.BGRA32: //test pass
                    bytes = DecodeBGRA32();
                    break;
                case TextureFormat.RHalf:
                    bytes = DecodeRHalf();
                    break;
                case TextureFormat.RGHalf:
                    bytes = DecodeRGHalf();
                    break;
                case TextureFormat.RGBAHalf: //test pass
                    bytes = DecodeRGBAHalf();
                    break;
                case TextureFormat.RFloat:
                    bytes = DecodeRFloat();
                    break;
                case TextureFormat.RGFloat:
                    bytes = DecodeRGFloat();
                    break;
                case TextureFormat.RGBAFloat:
                    bytes = DecodeRGBAFloat();
                    break;
                case TextureFormat.YUY2: //test pass
                    bytes = DecodeYUY2();
                    break;
                case TextureFormat.RGB9e5Float: //test pass
                    bytes = DecodeRGB9e5Float();
                    break;
                case TextureFormat.BC4: //test pass
                    bytes = DecodeBC4();
                    break;
                case TextureFormat.BC5: //test pass
                    bytes = DecodeBC5();
                    break;
                case TextureFormat.BC6H: //test pass
                    bytes = DecodeBC6H();
                    break;
                case TextureFormat.BC7: //test pass
                    bytes = DecodeBC7();
                    break;
                case TextureFormat.DXT1Crunched: //test pass
                    if (UnpackCrunch())
                    {
                        bytes = DecodeDXT1();
                    }
                    break;
                case TextureFormat.DXT5Crunched: //test pass
                    if (UnpackCrunch())
                    {
                        bytes = DecodeDXT5();
                    }
                    break;
                case TextureFormat.PVRTC_RGB2: //test pass
                case TextureFormat.PVRTC_RGBA2: //test pass
                    bytes = DecodePVRTC(true);
                    break;
                case TextureFormat.PVRTC_RGB4: //test pass
                case TextureFormat.PVRTC_RGBA4: //test pass
                    bytes = DecodePVRTC(false);
                    break;
                case TextureFormat.ETC_RGB4: //test pass
                case TextureFormat.ETC_RGB4_3DS:
                    bytes = DecodeETC1();
                    break;
                case TextureFormat.ATC_RGB4: //test pass
                    bytes = DecodeATCRGB4();
                    break;
                case TextureFormat.ATC_RGBA8: //test pass
                    bytes = DecodeATCRGBA8();
                    break;
                case TextureFormat.EAC_R: //test pass
                    bytes = DecodeEACR();
                    break;
                case TextureFormat.EAC_R_SIGNED:
                    bytes = DecodeEACRSigned();
                    break;
                case TextureFormat.EAC_RG: //test pass
                    bytes = DecodeEACRG();
                    break;
                case TextureFormat.EAC_RG_SIGNED:
                    bytes = DecodeEACRGSigned();
                    break;
                case TextureFormat.ETC2_RGB: //test pass
                    bytes = DecodeETC2();
                    break;
                case TextureFormat.ETC2_RGBA1: //test pass
                    bytes = DecodeETC2A1();
                    break;
                case TextureFormat.ETC2_RGBA8: //test pass
                case TextureFormat.ETC_RGBA8_3DS:
                    bytes = DecodeETC2A8();
                    break;
                case TextureFormat.ASTC_RGB_4x4: //test pass
                case TextureFormat.ASTC_RGBA_4x4: //test pass
                case TextureFormat.ASTC_HDR_4x4: //test pass
                    bytes = DecodeASTC(4);
                    break;
                case TextureFormat.ASTC_RGB_5x5: //test pass
                case TextureFormat.ASTC_RGBA_5x5: //test pass
                case TextureFormat.ASTC_HDR_5x5: //test pass
                    bytes = DecodeASTC(5);
                    break;
                case TextureFormat.ASTC_RGB_6x6: //test pass
                case TextureFormat.ASTC_RGBA_6x6: //test pass
                case TextureFormat.ASTC_HDR_6x6: //test pass
                    bytes = DecodeASTC(6);
                    break;
                case TextureFormat.ASTC_RGB_8x8: //test pass
                case TextureFormat.ASTC_RGBA_8x8: //test pass
                case TextureFormat.ASTC_HDR_8x8: //test pass
                    bytes = DecodeASTC(8);
                    break;
                case TextureFormat.ASTC_RGB_10x10: //test pass
                case TextureFormat.ASTC_RGBA_10x10: //test pass
                case TextureFormat.ASTC_HDR_10x10: //test pass
                    bytes = DecodeASTC(10);
                    break;
                case TextureFormat.ASTC_RGB_12x12: //test pass
                case TextureFormat.ASTC_RGBA_12x12: //test pass
                case TextureFormat.ASTC_HDR_12x12: //test pass
                    bytes = DecodeASTC(12);
                    break;
                case TextureFormat.RG16: //test pass
                    bytes = DecodeRG16();
                    break;
                case TextureFormat.R8: //test pass
                    bytes = DecodeR8();
                    break;
                case TextureFormat.ETC_RGB4Crunched: //test pass
                    if (UnpackCrunch())
                    {
                        bytes = DecodeETC1();
                    }
                    break;
                case TextureFormat.ETC2_RGBA8Crunched: //test pass
                    if (UnpackCrunch())
                    {
                        bytes = DecodeETC2A8();
                    }
                    break;
            }
            return bytes;
        }

        private void SwapBytesForXbox()
        {
            if (platform == BuildTarget.XBOX360)
            {
                for (var i = 0; i < image_data_size / 2; i++)
                {
                    var b = image_data[i * 2];
                    image_data[i * 2] = image_data[i * 2 + 1];
                    image_data[i * 2 + 1] = b;
                }
            }
        }

        private byte[] DecodeAlpha8()
        {
            var buff = Enumerable.Repeat<byte>(0xFF, m_Width * m_Height * 4).ToArray();
            for (var i = 0; i < m_Width * m_Height; i++)
            {
                buff[i * 4 + 3] = image_data[i];
            }
            return buff;
        }

        private byte[] DecodeARGB4444()
        {
            var buff = new byte[m_Width * m_Height * 4];
            for (var i = 0; i < m_Width * m_Height; i++)
            {
                var pixelNew = new byte[4];
                var pixelOldShort = BitConverter.ToUInt16(image_data, i * 2);
                pixelNew[0] = (byte)(pixelOldShort & 0x000f);
                pixelNew[1] = (byte)((pixelOldShort & 0x00f0) >> 4);
                pixelNew[2] = (byte)((pixelOldShort & 0x0f00) >> 8);
                pixelNew[3] = (byte)((pixelOldShort & 0xf000) >> 12);
                for (var j = 0; j < 4; j++)
                    pixelNew[j] = (byte)((pixelNew[j] << 4) | pixelNew[j]);
                pixelNew.CopyTo(buff, i * 4);
            }
            return buff;
        }

        private byte[] DecodeRGB24()
        {
            var buff = new byte[m_Width * m_Height * 4];
            for (var i = 0; i < m_Width * m_Height; i++)
            {
                buff[i * 4] = image_data[i * 3 + 2];
                buff[i * 4 + 1] = image_data[i * 3 + 1];
                buff[i * 4 + 2] = image_data[i * 3 + 0];
                buff[i * 4 + 3] = 255;
            }
            return buff;
        }

        private byte[] DecodeRGBA32()
        {
            var buff = new byte[m_Width * m_Height * 4];
            for (var i = 0; i < buff.Length; i += 4)
            {
                buff[i] = image_data[i + 2];
                buff[i + 1] = image_data[i + 1];
                buff[i + 2] = image_data[i + 0];
                buff[i + 3] = image_data[i + 3];
            }
            return buff;
        }

        private byte[] DecodeARGB32()
        {
            var buff = new byte[m_Width * m_Height * 4];
            for (var i = 0; i < buff.Length; i += 4)
            {
                buff[i] = image_data[i + 3];
                buff[i + 1] = image_data[i + 2];
                buff[i + 2] = image_data[i + 1];
                buff[i + 3] = image_data[i + 0];
            }
            return buff;
        }

        private byte[] DecodeRGB565()
        {
            var buff = new byte[m_Width * m_Height * 4];
            for (var i = 0; i < m_Width * m_Height; i++)
            {
                var p = BitConverter.ToUInt16(image_data, i * 2);
                buff[i * 4] = (byte)((p << 3) | (p >> 2 & 7));
                buff[i * 4 + 1] = (byte)((p >> 3 & 0xfc) | (p >> 9 & 3));
                buff[i * 4 + 2] = (byte)((p >> 8 & 0xf8) | (p >> 13));
                buff[i * 4 + 3] = 255;
            }
            return buff;
        }

        private byte[] DecodeR16()
        {
            var buff = new byte[m_Width * m_Height * 4];
            for (var i = 0; i < m_Width * m_Height; i++)
            {
                buff[i * 4 + 2] = image_data[i * 2 + 1]; //r
                buff[i * 4 + 3] = 255; //a
            }
            return buff;
        }

        private byte[] DecodeDXT1()
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodeDXT1(image_data, m_Width, m_Height, buff))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodeDXT5()
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodeDXT5(image_data, m_Width, m_Height, buff))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodeRGBA4444()
        {
            var buff = new byte[m_Width * m_Height * 4];
            for (var i = 0; i < m_Width * m_Height; i++)
            {
                var pixelNew = new byte[4];
                var pixelOldShort = BitConverter.ToUInt16(image_data, i * 2);
                pixelNew[0] = (byte)((pixelOldShort & 0x00f0) >> 4);
                pixelNew[1] = (byte)((pixelOldShort & 0x0f00) >> 8);
                pixelNew[2] = (byte)((pixelOldShort & 0xf000) >> 12);
                pixelNew[3] = (byte)(pixelOldShort & 0x000f);
                for (var j = 0; j < 4; j++)
                    pixelNew[j] = (byte)((pixelNew[j] << 4) | pixelNew[j]);
                pixelNew.CopyTo(buff, i * 4);
            }
            return buff;
        }

        private byte[] DecodeBGRA32()
        {
            var buff = new byte[m_Width * m_Height * 4];
            for (var i = 0; i < buff.Length; i += 4)
            {
                buff[i] = image_data[i];
                buff[i + 1] = image_data[i + 1];
                buff[i + 2] = image_data[i + 2];
                buff[i + 3] = image_data[i + 3];
            }
            return buff;
        }

        private byte[] DecodeRHalf()
        {
            var buff = new byte[m_Width * m_Height * 4];
            for (var i = 0; i < buff.Length; i += 4)
            {
                buff[i] = 0;
                buff[i + 1] = 0;
                buff[i + 2] = (byte)Math.Round(Half.ToHalf(image_data, i / 2) * 255f);
                buff[i + 3] = 255;
            }
            return buff;
        }

        private byte[] DecodeRGHalf()
        {
            var buff = new byte[m_Width * m_Height * 4];
            for (var i = 0; i < buff.Length; i += 4)
            {
                buff[i] = 0;
                buff[i + 1] = (byte)Math.Round(Half.ToHalf(image_data, i + 2) * 255f);
                buff[i + 2] = (byte)Math.Round(Half.ToHalf(image_data, i) * 255f);
                buff[i + 3] = 255;
            }
            return buff;
        }

        private byte[] DecodeRGBAHalf()
        {
            var buff = new byte[m_Width * m_Height * 4];
            for (var i = 0; i < buff.Length; i += 4)
            {
                buff[i] = (byte)Math.Round(Half.ToHalf(image_data, i * 2 + 4) * 255f);
                buff[i + 1] = (byte)Math.Round(Half.ToHalf(image_data, i * 2 + 2) * 255f);
                buff[i + 2] = (byte)Math.Round(Half.ToHalf(image_data, i * 2) * 255f);
                buff[i + 3] = (byte)Math.Round(Half.ToHalf(image_data, i * 2 + 6) * 255f);
            }
            return buff;
        }

        private byte[] DecodeRFloat()
        {
            var buff = new byte[m_Width * m_Height * 4];
            for (var i = 0; i < buff.Length; i += 4)
            {
                buff[i] = 0;
                buff[i + 1] = 0;
                buff[i + 2] = (byte)Math.Round(BitConverter.ToSingle(image_data, i) * 255f);
                buff[i + 3] = 255;
            }
            return buff;
        }

        private byte[] DecodeRGFloat()
        {
            var buff = new byte[m_Width * m_Height * 4];
            for (var i = 0; i < buff.Length; i += 4)
            {
                buff[i] = 0;
                buff[i + 1] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 2 + 4) * 255f);
                buff[i + 2] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 2) * 255f);
                buff[i + 3] = 255;
            }
            return buff;
        }

        private byte[] DecodeRGBAFloat()
        {
            var buff = new byte[m_Width * m_Height * 4];
            for (var i = 0; i < buff.Length; i += 4)
            {
                buff[i] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 4 + 8) * 255f);
                buff[i + 1] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 4 + 4) * 255f);
                buff[i + 2] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 4) * 255f);
                buff[i + 3] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 4 + 12) * 255f);
            }
            return buff;
        }

        private static byte ClampByte(int x)
        {
            return (byte)(byte.MaxValue < x ? byte.MaxValue : (x > byte.MinValue ? x : byte.MinValue));
        }

        private byte[] DecodeYUY2()
        {
            var buff = new byte[m_Width * m_Height * 4];
            int p = 0;
            int o = 0;
            int halfWidth = m_Width / 2;
            for (int j = 0; j < m_Height; j++)
            {
                for (int i = 0; i < halfWidth; ++i)
                {
                    int y0 = image_data[p++];
                    int u0 = image_data[p++];
                    int y1 = image_data[p++];
                    int v0 = image_data[p++];
                    int c = y0 - 16;
                    int d = u0 - 128;
                    int e = v0 - 128;
                    buff[o++] = ClampByte((298 * c + 516 * d + 128) >> 8);            // b
                    buff[o++] = ClampByte((298 * c - 100 * d - 208 * e + 128) >> 8);  // g
                    buff[o++] = ClampByte((298 * c + 409 * e + 128) >> 8);            // r
                    buff[o++] = 255;
                    c = y1 - 16;
                    buff[o++] = ClampByte((298 * c + 516 * d + 128) >> 8);            // b
                    buff[o++] = ClampByte((298 * c - 100 * d - 208 * e + 128) >> 8);  // g
                    buff[o++] = ClampByte((298 * c + 409 * e + 128) >> 8);            // r
                    buff[o++] = 255;
                }
            }
            return buff;
        }

        private byte[] DecodeRGB9e5Float()
        {
            var buff = new byte[m_Width * m_Height * 4];
            for (var i = 0; i < buff.Length; i += 4)
            {
                var n = BitConverter.ToInt32(image_data, i);
                var scale = n >> 27 & 0x1f;
                var scalef = Math.Pow(2, scale - 24);
                var b = n >> 18 & 0x1ff;
                var g = n >> 9 & 0x1ff;
                var r = n & 0x1ff;
                buff[i] = (byte)Math.Round(b * scalef * 255f);
                buff[i + 1] = (byte)Math.Round(g * scalef * 255f);
                buff[i + 2] = (byte)Math.Round(r * scalef * 255f);
                buff[i + 3] = 255;
            }
            return buff;
        }

        private byte[] DecodeBC4()
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodeBC4(image_data, m_Width, m_Height, buff))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodeBC5()
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodeBC5(image_data, m_Width, m_Height, buff))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodeBC6H()
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodeBC6(image_data, m_Width, m_Height, buff))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodeBC7()
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodeBC7(image_data, m_Width, m_Height, buff))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodePVRTC(bool is2bpp)
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodePVRTC(image_data, m_Width, m_Height, buff, is2bpp))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodeETC1()
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodeETC1(image_data, m_Width, m_Height, buff))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodeATCRGB4()
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodeATCRGB4(image_data, m_Width, m_Height, buff))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodeATCRGBA8()
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodeATCRGBA8(image_data, m_Width, m_Height, buff))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodeEACR()
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodeEACR(image_data, m_Width, m_Height, buff))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodeEACRSigned()
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodeEACRSigned(image_data, m_Width, m_Height, buff))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodeEACRG()
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodeEACRG(image_data, m_Width, m_Height, buff))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodeEACRGSigned()
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodeEACRGSigned(image_data, m_Width, m_Height, buff))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodeETC2()
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodeETC2(image_data, m_Width, m_Height, buff))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodeETC2A1()
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodeETC2A1(image_data, m_Width, m_Height, buff))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodeETC2A8()
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodeETC2A8(image_data, m_Width, m_Height, buff))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodeASTC(int blocksize)
        {
            var buff = new byte[m_Width * m_Height * 4];
            if (!TextureDecoder.DecodeASTC(image_data, m_Width, m_Height, blocksize, blocksize, buff))
            {
                return null;
            }
            return buff;
        }

        private byte[] DecodeRG16()
        {
            var buff = new byte[m_Width * m_Height * 4];
            for (var i = 0; i < m_Width * m_Height; i += 2)
            {
                buff[i * 2 + 1] = image_data[i + 1];//G
                buff[i * 2 + 2] = image_data[i];//R
                buff[i * 2 + 3] = 255;//A
            }
            return buff;
        }

        private byte[] DecodeR8()
        {
            var buff = new byte[m_Width * m_Height * 4];
            for (var i = 0; i < m_Width * m_Height; i++)
            {
                buff[i * 4 + 2] = image_data[i];//R
                buff[i * 4 + 3] = 255;//A
            }
            return buff;
        }

        private bool UnpackCrunch()
        {
            byte[] result;
            if (version[0] > 2017 || (version[0] == 2017 && version[1] >= 3) //2017.3 and up
                || m_TextureFormat == TextureFormat.ETC_RGB4Crunched
                || m_TextureFormat == TextureFormat.ETC2_RGBA8Crunched)
            {
                result = TextureDecoder.UnpackUnityCrunch(image_data);
            }
            else
            {
                result = TextureDecoder.UnpackCrunch(image_data);
            }
            if (result != null)
            {
                image_data = result;
                image_data_size = result.Length;
                return true;
            }
            return false;
        }
    }
}