using System.Numerics;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;

namespace SDFMapCreator;

public static class ImageUtil {
    public static T[,] LoadImage<T>(string path) where T : struct, IEquatable<T> {
        var image = SixLabors.ImageSharp.Image.Load(path);
        return image switch {
            Image<Rgba64> img => img.ProcessPixelsRgba64<T>(),
            Image<Rgb24> img => img.ProcessPixelsRgb24<T>(),
            Image<Rgba32> img => img.ProcessPixelsRgba32<T>(),
            Image<Rgb48> img => img.ProcessPixelsRgb48<T>(),
            _ => throw new NotSupportedException($"Image format not supported")
        };
    }

    static T[,] ProcessPixelsRgba64<T>(this Image<Rgba64> image) where T : struct, IEquatable<T> {
        var max = 65535f;
        var width = image.Width;
        var height = image.Height;
        var result = new T[image.Width, image.Height];
        image.ProcessPixelRows(accessor => {
            //we use Y as the row index and X as the column index
            for (var y = 0; y < height; y++) {
                var span = accessor.GetRowSpan(y);
                for (var x = 0; x < width; x++) {
                    switch (result) {
                        case float[,] f:
                            f[x, y] = span[x].R / max * (span[x].A / max);
                            break;
                        case Vector2[,] f:
                            f[x, y] = new Vector2(span[x].R / max, span[x].G / max) * new Vector2(span[x].A / max);
                            break;
                        case Vector3[,] f:
                            f[x, y] = new Vector3(span[x].R / max, span[x].G / max, span[x].B / max) * new Vector3(span[x].A / max);
                            break;
                        case Vector4[,] f:
                            f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max, span[x].A / max);
                            break;
                    }
                }
            }
        });
        return result;
    }

    static T[,] ProcessPixelsRgb24<T>(this Image<Rgb24> image) where T : struct, IEquatable<T> {
        var max = 255f;
        var width = image.Width;
        var height = image.Height;
        var result = new T[image.Width, image.Height];
        image.ProcessPixelRows(accessor => {
            //we use Y as the row index and X as the column index
            for (var y = 0; y < height; y++) {
                var span = accessor.GetRowSpan(y);
                for (var x = 0; x < width; x++) {
                    switch (result) {
                        case float[,] f:
                            f[x, y] = span[x].R / max;
                            break;
                        case Vector2[,] f:
                            f[x, y] = new(span[x].R / max, span[x].G / max);
                            break;
                        case Vector3[,] f:
                            f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max);
                            break;
                        case Vector4[,] f:
                            f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max, 1f);
                            break;
                    }
                }
            }
        });
        return result;
    }

    static T[,] ProcessPixelsRgba32<T>(this Image<Rgba32> image) where T : struct, IEquatable<T> {
        var max = 255f;
        var width = image.Width;
        var height = image.Height;
        var result = new T[image.Width, image.Height];
        image.ProcessPixelRows(accessor => {
            //we use Y as the row index and X as the column index
            for (var y = 0; y < height; y++) {
                var span = accessor.GetRowSpan(y);
                for (var x = 0; x < width; x++) {
                    switch (result) {
                        case float[,] f:
                            f[x, y] = span[x].R / max * (span[x].A / max);
                            break;
                        case Vector2[,] f:
                            f[x, y] = new Vector2(span[x].R / max, span[x].G / max) * new Vector2(span[x].A / max);
                            break;
                        case Vector3[,] f:
                            f[x, y] = new Vector3(span[x].R / max, span[x].G / max, span[x].B / max) * new Vector3(span[x].A / max);
                            break;
                        case Vector4[,] f:
                            f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max, span[x].A / max);
                            break;
                    }
                }
            }
        });
        return result;
    }

    static T[,] ProcessPixelsRgb48<T>(this Image<Rgb48> image) where T : struct, IEquatable<T> {
        var max = 65535f;
        var width = image.Width;
        var height = image.Height;
        var result = new T[image.Width, image.Height];
        image.ProcessPixelRows(accessor => {
            //we use Y as the row index and X as the column index
            for (var y = 0; y < height; y++) {
                var span = accessor.GetRowSpan(y);
                for (var x = 0; x < width; x++) {
                    switch (result) {
                        case float[,] f:
                            f[x, y] = span[x].R / max;
                            break;
                        case Vector2[,] f:
                            f[x, y] = new(span[x].R / max, span[x].G / max);
                            break;
                        case Vector3[,] f:
                            f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max);
                            break;
                        case Vector4[,] f:
                            f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max, 1f);
                            break;
                    }
                }
            }
        });
        return result;
    }

    public static void SaveImage<T>(this T[,] array, string path) where T : struct, IEquatable<T> {
        var width = array.GetLength(0);
        var height = array.GetLength(1);
        uint channels = array switch {
            float[,] => 1,
            Vector2[,] => 2,
            Vector3[,] => 3,
            Vector4[,] => 4,
            _ => throw new NotSupportedException($"Type {typeof(T)} is not supported.")
        };
        Console.Write($"Writing image {path}...");
        using Image<Rgb48> image = new(width, height);
        image.ProcessPixelRows(accessor => {
            for (var y = 0; y < height; y++) {
                var span = accessor.GetRowSpan(y);
                for (var x = 0; x < width; x++) {
                    switch (array) {
                        case float[,] f:
                            span[x] = new((ushort)(f[x, y] * 65535), (ushort)(f[x, y] * 65535), (ushort)(f[x, y] * 65535));
                            break;
                        case Vector2[,] f:
                            span[x] = new((ushort)(f[x, y].X * 65535), (ushort)(f[x, y].Y * 65535), 0);
                            break;
                        case Vector3[,] f:
                            span[x] = new((ushort)(f[x, y].X * 65535), (ushort)(f[x, y].Y * 65535), (ushort)(f[x, y].Z * 65535));
                            break;
                        case Vector4[,] f:
                            span[x] = new((ushort)(f[x, y].X * 65535), (ushort)(f[x, y].Y * 65535), (ushort)(f[x, y].Z * 65535));
                            break;
                    }
                }
            }
        });
        Console.WriteLine($"Done!");
        image.Save(path);
    }

    static void ImageData(SixLabors.ImageSharp.Image image1, string path) {
        Console.WriteLine(
            $"""
             Image file: {path}
             Resolution: {image1.Width}x{image1.Height}
             Total Pixels: {image1.Width * image1.Height} |{"NaN"} channels, {image1.PixelType.BitsPerPixel / 4} bits per channel

             """);
    }
}