#nullable disable

using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace WWT.Imaging
{
    public struct PixelData
    {
        public byte blue;
        public byte green;
        public byte red;
        public byte alpha;

        public PixelData(int r, int g, int b, int a) // values between 0 and 255
        {
            blue = (byte)(b >= 255 ? 255 : (b < 0 ? 0 : b));
            red = (byte)(r >= 255 ? 255 : (r < 0 ? 0 : r));
            green = (byte)(g >= 255 ? 255 : (g < 0 ? 0 : g));
            alpha = (byte)(a >= 255 ? 255 : (a < 0 ? 0 : a));
        }
    }
    public unsafe class FastBitmapEnumerator : IDisposable
    {
        int x;
        int y;
        FastBitmap fastBitmap;
        PixelData* pCurrentPixel;
        bool locked;

        public FastBitmapEnumerator(FastBitmap fastBitmap)
        {
            fastBitmap.LockBitmap();
            locked = true;
            this.fastBitmap = fastBitmap;
            x = -1;
            y = 0;
            pCurrentPixel = fastBitmap[x, y];
        }

        public void Dispose()
        {
            if (locked)
            {
                fastBitmap.UnlockBitmap();
            }
        }

        public bool MoveNext()
        {
            x++;
            pCurrentPixel++;
            if (x == fastBitmap.Size.X)
            {
                y++;
                if (y == fastBitmap.Size.Y)
                {
                    return false;
                }
                else
                {
                    x = 0;
                    pCurrentPixel = fastBitmap[0, y];
                    //Debug.WriteLine(String.Format("{0}", pCurrentPixel - fastBitmap[0, 0]));
                }
            }
            return true;
        }

        public PixelData* Current
        {
            get
            {
                return pCurrentPixel;
            }
        }


    }

    /// <summary>
    /// A bitmap class that allows fast x, y access 
    /// </summary>
    public unsafe class FastBitmap : IDisposable
    {
        Bitmap bitmap;

        // three elements used for MakeGreyUnsafe
        int width;
        BitmapData bitmapData = null;
        Byte* pBase = null;
        PixelData* pCurrentPixel = null;
        int xLocation;
        int yLocation;
        Point size;
        internal bool locked = false;

        /// <summary>
        /// Create an instance from an existing bitmap
        /// </summary>
        /// <param name="bitmap">The bitmap</param>
        public FastBitmap(Bitmap bitmap)
        {
            this.bitmap = bitmap;

            GraphicsUnit unit = GraphicsUnit.Pixel;
            RectangleF bounds = bitmap.GetBounds(ref unit);

            size = new Point((int)bounds.Width, (int)bounds.Height);
        }

        /// <summary>
        /// Save the bitmap to a file
        /// </summary>
        /// <param name="filename">Filename to save the bitmap to</param>
        public void Save(string filename)
        {
            bitmap.Save(filename, ImageFormat.Jpeg);
        }

        public void Dispose()
        {
            bitmap.Dispose();
        }

        /// <summary>
        /// Size of the bitmap in pixels
        /// </summary>
        public Point Size
        {
            get
            {
                return size;
            }
        }

        /// <summary>
        /// The Bitmap object wrapped by this instance
        /// </summary>
        public Bitmap Bitmap
        {
            get
            {
                return (bitmap);
            }
        }

        /// <summary>
        /// Start at the beginning of the bitmap
        /// </summary>
        public void InitCurrentPixel()
        {
            LockBitmap();
            //if (pBase == null)
            //{
            //		throw new InvalidOperationException("Bitmap must be locked before calling InitCurrentPixel()");
            //		}
            pCurrentPixel = (PixelData*)pBase;
        }

        /// <summary>
        /// Return the next pixel
        /// </summary>
        /// <returns>The next pixel, or null if done</returns>
        public PixelData* GetNextPixel()
        {
            PixelData* pReturnPixel = pCurrentPixel;
            if (xLocation == size.X)
            {
                xLocation = 0;
                yLocation++;
                if (yLocation == size.Y)
                {
                    UnlockBitmap();
                    return null;
                }
                else
                {
                    pCurrentPixel = this[0, yLocation];
                }
            }
            else
            {
                xLocation++;
                pCurrentPixel++;
            }
            return pReturnPixel;
        }

        /// <summary>
        /// Get the pixel data at a specific x and y location
        /// </summary>
        public PixelData* this[int x, int y]
        {
            get
            {
                return (PixelData*)(pBase + y * width + x * sizeof(PixelData));
            }
        }

        public FastBitmapEnumerator GetEnumerator()
        {
            return new FastBitmapEnumerator(this);
        }

        /// <summary>
        /// Lock the bitmap. 
        /// </summary>
        public void LockBitmap()
        {
            GraphicsUnit unit = GraphicsUnit.Pixel;
            RectangleF boundsF = bitmap.GetBounds(ref unit);
            Rectangle bounds = new Rectangle((int)boundsF.X,
                (int)boundsF.Y,
                (int)boundsF.Width,
                (int)boundsF.Height);

            // Figure out the number of bytes in a row
            // This is rounded up to be a multiple of 4
            // bytes, since a scan line in an image must always be a multiple of 4 bytes
            // in length. 
            width = (int)boundsF.Width * sizeof(PixelData);
            if (width % 4 != 0)
            {
                width = 4 * (width / 4 + 1);
            }

            bitmapData =
                bitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

            pBase = (Byte*)bitmapData.Scan0.ToPointer();
            locked = true;
        }

        /// <summary>
        /// Unlock the bitmap
        /// </summary>
        public void UnlockBitmap()
        {
            bitmap.UnlockBits(bitmapData);
            bitmapData = null;
            pBase = null;
            locked = false;
        }

        public PixelData GetFilteredPixel(double xd, double yd)
        {
            int x = (int)(xd);

            double xr = xd - (double)x;

            int y = (int)(yd);

            double yr = yd - (double)y;

            if (x < 0 || x > (bitmap.Width - 1) || y < 0 || y > (bitmap.Height - 1))
            {
                return new PixelData();
            }

            int stepX = 1;
            int stepY = 1;

            if (x == bitmap.Width - 1)
            {
                stepX = 0;
            }
            if (y == bitmap.Height - 1)
            {
                stepY = 0;
            }

            PixelData* tl = this[x, y];
            PixelData* tr = this[x + stepX, y];
            PixelData* bl = this[x, y + stepY];
            PixelData* br = this[x + stepX, y + stepY];

            PixelData result;

            result.alpha = (byte)((((((double)tl->alpha * (1.0 - xr)) + ((double)tr->alpha * xr))) * (1.0 - yr)) +
                             (((((double)bl->alpha * (1.0 - xr)) + ((double)br->alpha * xr))) * yr));
            result.red = (byte)((((((double)tl->red * (1.0 - xr)) + ((double)tr->red * xr))) * (1.0 - yr)) +
                             (((((double)bl->red * (1.0 - xr)) + ((double)br->red * xr))) * yr));
            result.green = (byte)((((((double)tl->green * (1.0 - xr)) + ((double)tr->green * xr))) * (1.0 - yr)) +
                             (((((double)bl->green * (1.0 - xr)) + ((double)br->green * xr))) * yr));
            result.blue = (byte)((((((double)tl->blue * (1.0 - xr)) + ((double)tr->blue * xr))) * (1.0 - yr)) +
                             (((((double)bl->blue * (1.0 - xr)) + ((double)br->blue * xr))) * yr));


            return result;
        }


    }
}
