using System;
using System.Drawing;
using System.Drawing.Imaging;
namespace IrrlichtNetSwig
{
    public partial class ITexture
    {
        public delegate bool ModifyPixel(int x, int y, out Color result);

        public Bitmap ImageDotNet
        {
            get
            {
                Bitmap bmp = new Bitmap(Size.Width, Size.Height);
                BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly,
                             PixelFormat.Format32bppArgb);
                int stride = bmpData.Stride;
                IntPtr scan0 = bmpData.Scan0;
                Color[,] colorMatrix = Retrieve();
                unsafe
                {
                    byte* p = (byte*)(void*)scan0;
                    int offset = stride - bmp.Width * 4;
                    for (int y = 0; y < bmp.Height; ++y)
                    {
                        for (int x = 0; x < bmp.Width; ++x)
                        {
                            p[0] = (byte)colorMatrix[x, y].B;
                            p[1] = (byte)colorMatrix[x, y].G;
                            p[2] = (byte)colorMatrix[x, y].R;
                            p[3] = (byte)colorMatrix[x, y].A;

                            p += 4;
                        }
                        p += offset;
                    }
                }
                bmp.UnlockBits(bmpData);
                return bmp;
            }
            set
            {
                int w = Math.Min(value.Width, OriginalSize.Width);
                int h = Math.Min(value.Height, OriginalSize.Height);

                BitmapData bmpData = value.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
                int stride = bmpData.Stride;
                System.IntPtr Scan0 = bmpData.Scan0;
                unsafe
                {
                    byte* p = (byte*)(void*)Scan0;
                    ModifyPixel del = delegate(int x, int y, out Color result)
                    {
                        if (x >= w || y >= h)
                        {
                            result = Color.Black;
                            return false;
                        }
                        int ind = y * stride + (x * 4);
                        result = Color.FromArgb(p[ind + 3], p[ind + 2], p[ind + 1], p[ind + 0]);
                        return true;
                    };
                    this.Modify(del);
                }
                value.UnlockBits(bmpData);
            }
        }


        /// <summary>
        /// Safe and fast way to retrieve all pixels of the texture.
        /// </summary>
        /// <returns>A two-dimension array with all pixels [x,y]</returns>
        public Color[,] Retrieve()
        {
            ECOLOR_FORMAT colorFormat = getColorFormat();
            switch (colorFormat)
            {
                case ECOLOR_FORMAT.ECF_A1R5G5B5:
                    return RetrieveA1R5G5B5();

                case ECOLOR_FORMAT.ECF_R5G6B5:
                    return RetrieveR5G6B5();

                case ECOLOR_FORMAT.ECF_A8R8G8B8:
                    return RetrieveA8R8G8B8();

                default:
                    throw new NotImplementedException(colorFormat + " retrieving options are not implemented. Consider using unsafe acess or Get/SetPixel instead.");
            }
        }

        private unsafe Color[,] RetrieveA1R5G5B5()
        {
            int w = OriginalSize.Width;
            int h = OriginalSize.Height;
            Color[,] tor = new Color[w, h];
            IntPtr lockresult = Lock();
            short* directacces = (short*)(void*)lockresult;
            int pitch = (int)getPitch() / 2;
            for (int x = 0; x < w; ++x)
            {
                for (int y = 0; y < h; ++y)
                {
                    short color = directacces[x + y*pitch];
                    int colorValue = (int) ((color & 0x8000) << 16 |
                                            (color & 0x7C00) << 9 |
                                            (color & 0x03E0) << 6 |
                                            (color & 0x001F) << 3);
                    tor[x, y] = Color.FromArgb(colorValue);
                }
            }
            unlock();
            return tor;
        }

        protected unsafe Color[,] RetrieveR5G6B5()
        {
            int w = OriginalSize.Width;
            int h = OriginalSize.Height;
            Color[,] tor = new Color[w, h];
            IntPtr lockresult = Lock();
            short* directacces = (short*)(void*)lockresult;
            int pitch = (int)getPitch() / 2;
            for (int x = 0; x < w; ++x)
            {
                for (int y = 0; y < h; ++y)
                {
                    uint color = (uint) directacces[x + y*pitch];
                    int colorValue = (int) ((0xFF000000 | ((color & 0xF800) << 8) |
                                             ((color & 0x07E0) << 5) | (color & 0x1F) << 3));
                    tor[x, y] = Color.FromArgb(colorValue);
                }
            }
            unlock();
            return tor;
        }

        protected unsafe Color[,] RetrieveA8R8G8B8()
        {
            int w = OriginalSize.Width;
            int h = OriginalSize.Height;
            Color[,] tor = new Color[w,h];
            IntPtr lockresult = Lock();
            int* directacces = (int*) (void*) lockresult;
            int pitch = (int) getPitch()/4;
            for (int x = 0; x < w; ++x)
            {
                for (int y = 0; y < h; ++y)
                {
                    int colorValue = directacces[x + y*pitch];
                    tor[x, y] = Color.FromArgb(colorValue);
                }
            }

            unlock();
            return tor;
        }

        #region Modify
        /// <summary>
        /// Modifies the texture using a simple callback called on each pixel's modification.
        /// </summary>
        /// <param name="callback">Callback called for each pixel</param>
        public void Modify(ModifyPixel callback)
        {
            ECOLOR_FORMAT colorFormat = getColorFormat();
            switch (colorFormat)
            {
                case ECOLOR_FORMAT.ECF_A1R5G5B5:
                    ModifyA1R5G5B5(callback);
                    break;

                case ECOLOR_FORMAT.ECF_R5G6B5:
                    ModifyR5G6B5(callback);
                    break;

                case ECOLOR_FORMAT.ECF_A8R8G8B8:
                    ModifyA8R8G8B8(callback);
                    break;

                default:
                    throw new NotImplementedException(colorFormat + " modifying options are not implemented. Consider using unsafe acess or Get/SetPixel instead.");
            }
        }

        unsafe protected void ModifyA8R8G8B8(ModifyPixel callback)
        {
            int w = OriginalSize.Width;
            int h = OriginalSize.Height;
            IntPtr lockresult = Lock();
            int* directacces = (int*)(void*)lockresult;
            int pitch = (int)getPitch() / 4;
            for (int y = 0; y < h; ++y)
                for (int x = 0; x < w; ++x)
                {
                    Color col;
                    if (callback(x, y, out col))
                        directacces[x + y * pitch] = col.ToArgb();
                }
            unlock();
        }

        unsafe protected void ModifyR5G6B5(ModifyPixel callback)
        {
            int w = OriginalSize.Width;
            int h = OriginalSize.Height;
            IntPtr lockresult = Lock();
            short* directacces = (short*)(void*)lockresult;
            int pitch = (int)getPitch() / 2;
            for (int y = 0; y < h; ++y)
                for (int x = 0; x < w; ++x)
                {
                    Color col;
                    if (callback(x, y, out col))
                    {
                        uint color = (uint)col.ToArgb();
                        directacces[x + y * pitch] = (short)(0x8000 | (color & 0x1F) | ((color >> 1) & 0x7FE0));
                    }
                }
            unlock();
        }

        unsafe protected void ModifyA1R5G5B5(ModifyPixel callback)
        {
            int w = OriginalSize.Width;
            int h = OriginalSize.Height;
            IntPtr lockresult = Lock();
            short* directacces = (short*)(void*)lockresult;
            int pitch = (int)getPitch() / 2;
            for (int y = 0; y < h; ++y)
                for (int x = 0; x < w; ++x)
                {
                    Color col;
                    if (callback(x, y, out col))
                    {
                        uint color = (uint)col.ToArgb();
                        directacces[x + y * pitch] = (short)((color & 0x80000000) >> 16 |
                                                             (color & 0x00F80000) >> 9 |
                                                             (color & 0x0000F800) >> 6 |
                                                             (color & 0x000000F8) >> 3);
                    }
                }
            unlock();
        }
        #endregion
    }
}