using System.Numerics;
using ILGPU;
using ILGPU.Runtime;

namespace SDFMapCreator;

public partial class SdfKernels {
    
    private const float LUMA_THRESHOLD = 0.0f;
    static void SelfMaskKernel(Index2D index, ArrayView2D<Vector3, Stride2D.DenseX> input, ArrayView2D<Vector3, Stride2D.DenseX> mask) {
        var x = index.X;
        var y = index.Y;
        var value = input[x, y];
        var lumaA = value.X;
        var r = lumaA > LUMA_THRESHOLD ? 1f : 0f;
        mask[x, y] = new(r, 0f, 0f);
    }

    static void ABMaskKernel(Index2D index,
        ArrayView2D<Vector3, Stride2D.DenseX> A,
        ArrayView2D<Vector3, Stride2D.DenseX> B,
        ArrayView2D<Vector3, Stride2D.DenseX> mask
    ) {
        var x = index.X;
        var y = index.Y;
        var valueA = A[x, y];
        var valueB = B[x, y];
        var lumaA = valueA.X;
        var lumaB = valueB.X;
        var r = lumaA > LUMA_THRESHOLD || lumaB > LUMA_THRESHOLD ? 1f : 0f;
        mask[x, y] = new(r, 0f, 0f);
    }

    static void EdgeKernel(Index2D index,
        ArrayView2D<Vector3, Stride2D.DenseX> mask,
        uint width, uint height
    ) { // early exit if not on mask
        if (mask[index].X == 0f) return;
        var x = index.X;
        var y = index.Y;
        //if we are on the edge of the image, return false
        if (x == 0 || y == 0 || x == width - 1 || y == height - 1) return;

        //check the 3x3 kernel
        for (var xi = x - 1; xi <= x + 1; xi++) {
            for (var yi = y - 1; yi <= y + 1; yi++) {
                if (xi < 0 || xi >= width || yi < 0 || yi >= height)
                    continue; //skip out of bounds pixels
                if (mask[xi, yi].X == 0f)
                    mask[index].Y = 1f; //if we find a black pixel, return true
            }
        }
    }

    static void SdfKernel(Index2D index,
        ArrayView2D<Vector3, Stride2D.DenseX> sdf,
        ArrayView1D<Vector2, Stride1D.Dense> edges,
        int width, int height
    ) {
        Vector2 pos = new((float)index.X / width, (float)index.Y / height);
        var minDist = 2f;
        var count = edges.IntExtent.Size;

        for (var i = 0; i < count; i++) {
            Vector2 edgeNrm = new(edges[i].X / width, edges[i].Y / height);
            var dist = Vector2.Distance(pos, edgeNrm);
            if (dist < minDist) minDist = dist;
        }

        if (minDist > 1f) minDist = 1f;

        sdf[index] = new(minDist);
    }

    static void GradientKernel(Index2D index,
        ArrayView2D<Vector3, Stride2D.DenseX> mask,
        ArrayView2D<Vector3, Stride2D.DenseX> sdfa,
        ArrayView2D<Vector3, Stride2D.DenseX> sdfb,
        ArrayView2D<Vector3, Stride2D.DenseX> gradient
    ) { //early exit if not on mask
        if (mask[index].X == 0f || mask[index].Y > 0f) return;
        var a = sdfa[index].X;
        var b = sdfb[index].X;
        gradient[index] = new(a / (a + b));
    }

    static void DirectionalBlurKernel(Index2D index,
        ArrayView2D<Vector3, Stride2D.DenseX> image,
        ArrayView2D<Vector3, Stride2D.DenseX> mask,
        ArrayView2D<Vector3, Stride2D.DenseX> output,
        float radius, float step, float sigma,
        int width, int height
    ) {
        var x = index.X;
        var y = index.Y;
        var value = image[x, y];
        var maskValue = mask[x, y];
        if (maskValue.X == 0f) {
            output[x, y] = value;
            return;
        }

        var gradient = Vector2.Zero;

        for (var dx = -1; dx <= 1; dx++) {
            for (var dy = -1; dy <= 1; dy++) {
                if (x + dx < 0 || x + dx >= width || y + dy < 0 || y + dy >= height) continue;
                gradient += new Vector2(dx, dy) * image[x + dx, y + dy].X;
            }
        }

        if (gradient == Vector2.Zero) {
            output[x, y] = value;
            return;
        }

        gradient = Vector2.Normalize(gradient);
        float sum = 0;

        // now we follow the direction line and sample the image for length;
        for (var l = -radius; l <= radius; l += step) {
            var xOffset = (int)(gradient.X * l);
            var yOffset = (int)(gradient.Y * l);
            var xSample = x + xOffset;
            var ySample = y + yOffset;

            if (xSample < 0 || xSample >= width || ySample < 0 || ySample >= height) continue;

            var sampleValue = image[xSample, ySample];
            var weight = MathF.Exp(-l * l / (2f * sigma * sigma));
            output[x, y] += sampleValue * weight;
            sum += weight;
        }

        output[x, y] /= sum;
    }
}