using BlurHash;
using System.Drawing.Imaging;

namespace System.Drawing.BlurHash;

public static class BlurHasher
{

#pragma warning disable CA1416

    /// <summary>
    /// Encodes a picture into a BlurHash string
    /// </summary>
    /// <param name="image">The picture to encode</param>
    /// <param name="componentsX">The number of components used on the X-Axis for the DCT</param>
    /// <param name="componentsY">The number of components used on the Y-Axis for the DCT</param>
    /// <returns>The resulting BlurHash string</returns>
    public static string Encode(Image image, int componentsX, int componentsY) => Core.Encode(ConvertBitmap(image as Bitmap ?? new Bitmap(image)), componentsX, componentsY);

    /// <summary>
    /// Decodes a BlurHash string into a <c>System.Drawing.Image</c>
    /// </summary>
    /// <param name="blurhash">The blurhash string to decode</param>
    /// <param name="outputWidth">The desired width of the output in pixels</param>
    /// <param name="outputHeight">The desired height of the output in pixels</param>
    /// <param name="punch">A value that affects the contrast of the decoded image. 1 means normal, smaller values will make the effect more subtle, and larger values will make it stronger.</param>
    /// <returns>The decoded preview</returns>
    public static Image Decode(string blurhash, int outputWidth, int outputHeight, double punch = 1.0)
    {
        Pixel[,] pixelData = new Pixel[outputWidth, outputHeight];
        Core.Decode(blurhash, pixelData, punch);
        return ConvertToBitmap(pixelData);
    }

    /// <summary>
    /// Converts the given bitmap to the library-independent representation used within the BlurHash-core
    /// </summary>
    /// <param name="sourceBitmap">The bitmap to encode</param>
    public static unsafe Pixel[,] ConvertBitmap(Image sourceBitmap)
    {
        int width = sourceBitmap.Width;
        int height = sourceBitmap.Height;

        using Bitmap temporaryBitmap = new(width, height, PixelFormat.Format24bppRgb);
        using (Graphics graphics = Graphics.FromImage(temporaryBitmap))
        {
            graphics.DrawImageUnscaled(sourceBitmap, 0, 0);
        }

        // Lock the bitmap's bits.
        BitmapData bmpData = temporaryBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, temporaryBitmap.PixelFormat);

        // Get the address of the first line.
        nint ptr = bmpData.Scan0;

        Pixel[,] result = new Pixel[width, height];

        byte* rgb = (byte*)ptr.ToPointer();
        for (int y = 0; y < height; y++)
        {
            int index = bmpData.Stride * y;

            for (int x = 0; x < width; x++)
            {
                ref Pixel res = ref result[x, y];
                res.Blue = MathUtils.SRgbToLinear(rgb[index++]);
                res.Green = MathUtils.SRgbToLinear(rgb[index++]);
                res.Red = MathUtils.SRgbToLinear(rgb[index++]);
            }
        }

        temporaryBitmap.UnlockBits(bmpData);

        return result;
    }

    /// <summary>
    /// Converts the library-independent representation of pixels into a bitmap
    /// </summary>
    /// <param name="pixelData">The library-independent representation of the image</param>
    /// <returns>A <c>System.Drawing.Bitmap</c> in 32bpp-RGB representation</returns>
    public static unsafe Bitmap ConvertToBitmap(Pixel[,] pixelData)
    {
        int width = pixelData.GetLength(0);
        int height = pixelData.GetLength(1);

        byte[] data = new byte[width * height * 4];

        int index = 0;
        for (int yPixel = 0; yPixel < height; yPixel++)
            for (int xPixel = 0; xPixel < width; xPixel++)
            {
                Pixel pixel = pixelData[xPixel, yPixel];

                data[index++] = (byte)MathUtils.LinearTosRgb(pixel.Blue);
                data[index++] = (byte)MathUtils.LinearTosRgb(pixel.Green);
                data[index++] = (byte)MathUtils.LinearTosRgb(pixel.Red);
                data[index++] = 0;
            }

        Bitmap bmp;

        fixed (byte* ptr = data)
        {
            bmp = new Bitmap(width, height, width * 4, PixelFormat.Format32bppRgb, new IntPtr(ptr));
        }

        return bmp;
    }
}