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

namespace ImageQuantization;

public abstract class Quantizer
{
	[StructLayout(LayoutKind.Explicit)]
	public struct Color32
	{
		[FieldOffset(0)]
		public byte Blue;

		[FieldOffset(1)]
		public byte Green;

		[FieldOffset(2)]
		public byte Red;

		[FieldOffset(3)]
		public byte Alpha;

		[FieldOffset(0)]
		public int ARGB;

		public Color Color => Color.FromArgb(Alpha, Red, Green, Blue);
	}

	private bool _singlePass;

	public Quantizer(bool singlePass)
	{
		_singlePass = singlePass;
	}

	public Bitmap Quantize(Image source)
	{
		int height = source.Height;
		int width = source.Width;
		Rectangle rectangle = new Rectangle(0, 0, width, height);
		Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
		Bitmap bitmap2 = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
		using (Graphics graphics = Graphics.FromImage(bitmap))
		{
			graphics.PageUnit = GraphicsUnit.Pixel;
			graphics.DrawImage(source, rectangle);
		}
		BitmapData bitmapData = null;
		try
		{
			bitmapData = bitmap.LockBits(rectangle, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
			if (!_singlePass)
			{
				FirstPass(bitmapData, width, height);
			}
			bitmap2.Palette = GetPalette(bitmap2.Palette);
			SecondPass(bitmapData, bitmap2, width, height, rectangle);
			return bitmap2;
		}
		finally
		{
			bitmap.UnlockBits(bitmapData);
		}
	}

	protected virtual void FirstPass(BitmapData sourceData, int width, int height)
	{
		IntPtr intPtr = sourceData.Scan0;
		checked
		{
			byte[] array = new byte[width * 4 + 1];
			int num = height - 1;
			Color32 pixel = default(Color32);
			for (int i = 0; i <= num; i++)
			{
				IntPtr intPtr2 = intPtr;
				Marshal.Copy(intPtr, array, 0, width * 4);
				int num2 = width - 1;
				for (int j = 0; j <= num2; j++)
				{
					pixel.Blue = array[j * 4];
					pixel.Green = array[j * 4 + 1];
					pixel.Red = array[j * 4 + 2];
					pixel.Alpha = array[j * 4 + 3];
					InitialQuantizePixel(pixel);
					intPtr2 = (IntPtr)(intPtr2.ToInt64() + 4);
				}
				intPtr = (IntPtr)(intPtr.ToInt64() + sourceData.Stride);
			}
		}
	}

	protected virtual void SecondPass(BitmapData sourceData, Bitmap output, int width, int height, Rectangle bounds)
	{
		BitmapData bitmapData = null;
		checked
		{
			try
			{
				bitmapData = output.LockBits(bounds, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
				IntPtr intPtr = sourceData.Scan0;
				IntPtr ptr = intPtr;
				IntPtr intPtr2 = bitmapData.Scan0;
				IntPtr intPtr3 = intPtr2;
				object obj = Marshal.PtrToStructure(ptr, typeof(Color32));
				Color32 color = default(Color32);
				Color32 pixel = ((obj != null) ? ((Color32)obj) : color);
				byte b = QuantizePixel(pixel);
				byte[] array = new byte[width + 1];
				byte[] array2 = new byte[width * 4 + 1];
				int num = height - 1;
				Color32 color2 = default(Color32);
				for (int i = 0; i <= num; i++)
				{
					Marshal.Copy(intPtr, array2, 0, width * 4);
					int num2 = width - 1;
					for (int j = 0; j <= num2; j++)
					{
						color2.Blue = array2[j * 4];
						color2.Green = array2[j * 4 + 1];
						color2.Red = array2[j * 4 + 2];
						color2.Alpha = array2[j * 4 + 3];
						if (pixel.ARGB != color2.ARGB)
						{
							b = QuantizePixel(color2);
							pixel = color2;
						}
						array[j] = b;
					}
					Marshal.Copy(array, 0, intPtr2, width);
					intPtr = (IntPtr)(intPtr.ToInt64() + sourceData.Stride);
					intPtr2 = (IntPtr)(intPtr2.ToInt64() + bitmapData.Stride);
				}
			}
			finally
			{
				output.UnlockBits(bitmapData);
			}
		}
	}

	protected virtual void InitialQuantizePixel(Color32 pixel)
	{
	}

	protected abstract byte QuantizePixel(Color32 pixel);

	protected abstract ColorPalette GetPalette(ColorPalette original);
}
