#include <metal_stdlib>
using namespace metal;

struct Uniforms_GradientToAura {
    float2 display_size;
    
    float grain_noise_z;
    float grain_noise_scale;
    
    float3 background_color;
    float3 color_a;
    float3 color_b;
    float3 color_c;
    
    float mix_point_start;
    float mix_point_background;
    float mix_point_backgroundA;
    float mix_point_AB;
    float mix_point_BC;
    float mix_point_end;
};

struct VertexOut_GradientToAura {
    float4 position [[ position ]];
    float2 uv;
};

float3 gradient_to_aura_random3(float3 c) {
    float j = 4096.0 * sin(dot(c, float3(17.0, 59.4, 15.0)));
    float3 r;
    r.z = fract(512.0 * j);
    j *= 0.125;
    r.x = fract(512.0 * j);
    j *= 0.125;
    r.y = fract(512.0 * j);
    return r - 0.5;
}

constant float F3 = 0.3333333;
constant float G3 = 0.1666667;

float simplex_3d_gradient_to_aura(float3 p) {
    float3 s = floor(p + dot(p, float3(F3)));
    float3 x = p - s + dot(s, float3(G3));

    float3 e = step(float3(0.0), x - float3(x.y, x.z, x.x));
    float3 i1 = e * (1.0 - float3(e.z, e.x, e.y));
    float3 i2 = 1.0 - float3(e.z, e.x, e.y) * (1.0 - e);

    float3 x1 = x - i1 + G3;
    float3 x2 = x - i2 + 2.0 * G3;
    float3 x3 = x - 1.0 + 3.0 * G3;

    float4 w, d;

    w.x = dot(x, x);
    w.y = dot(x1, x1);
    w.z = dot(x2, x2);
    w.w = dot(x3, x3);

    w = max(0.6 - w, 0.0);

    d.x = dot(gradient_to_aura_random3(s), x);
    d.y = dot(gradient_to_aura_random3(s + i1), x1);
    d.z = dot(gradient_to_aura_random3(s + i2), x2);
    d.w = dot(gradient_to_aura_random3(s + 1.0), x3);

    w *= w;
    w *= w;
    d *= w;

    return dot(d, float4(52.0));
}

float3 gradient_to_aura_blend_colors(
        float mixPointStart,
        float mixPointBackground,
        float mixPointBackgroundA,
        float mixPointAB,
        float mixPointBC,
        float mixPointEnd,
        float3 backgroundColor,
        float3 colorA,
        float3 colorB,
        float3 colorC,
        float value) {

    float3 blendedColor;

    if (value <= mixPointBackground) {
        float t = smoothstep(mixPointStart, mixPointBackground, value);
        blendedColor = mix(backgroundColor, colorA, t);
        
    } else if (value <= mixPointAB) {
        float t = smoothstep(mixPointBackgroundA, mixPointAB, value);
        blendedColor = mix(colorA, colorB, t);
        
    } else {
        float t = smoothstep(mixPointBC, mixPointEnd, value);
        blendedColor = mix(colorB, colorC, t);
    }

    return blendedColor;
}

fragment float4 gradient_to_aura_frag(
    VertexOut_GradientToAura in [[ stage_in ]],
    constant Uniforms_GradientToAura &u [[ buffer(0) ]],
    float4 color [[ color(0) ]]) {
        
    float2 size = u.display_size;
    float grain_noise_z             = u.grain_noise_z;
    float grain_noise_scale         = u.grain_noise_scale;
        
    float mixPointStart             = u.mix_point_start;
    float mixPointBackground        = u.mix_point_background;
    float mixPointBackgroundA       = u.mix_point_backgroundA;
    float mixPointAB                = u.mix_point_AB;
    float mixPointBC                = u.mix_point_BC;
    float mixPointEnd               = u.mix_point_end;
        
    float3 backgroundColor = u.background_color;
    float3 colorA = u.color_a;
    float3 colorB = u.color_b;
    float3 colorC = u.color_c;
        
    bool isWidthBigger = size.x > size.y;
    
    float widthScalar = 1.0;
    float heightScalar = 1.0;
    
    if (isWidthBigger) {
        widthScalar = size.x / size.y;
    } else {
        heightScalar = size.y / size.x;
    }
    
    // Fractal Noise
    float fractal_value = color.x;
    
    // Grain Noise
    float2 grain_point2D = in.uv;
    grain_point2D.x *= widthScalar;
    grain_point2D.y *= heightScalar;
    grain_point2D *= grain_noise_scale;
        
    float3 grain_point3D = float3(grain_point2D.x, grain_point2D.y, grain_noise_z);
    float grain_value = simplex_3d_gradient_to_aura(grain_point3D * 32.0);
    
    // Apply Grain        
    grain_value             += 2.0 * fractal_value;
    fractal_value           *= grain_value;
        
    // Speech Circle
    // return float4(circle_value, circle_value, circle_value, 1.0);

    // UV Fractal 2D
    // return float4(fractal_point_2D.x, fractal_point_2D.y, 0.0, 1.0);
        
    // Grain Value
    // return float4(grain_value, grain_value, grain_value, 1.0);
        
    // Fractal Value
    // return float4(fractal_value, fractal_value, fractal_value, 1.0);
        
    float3 selected_color = gradient_to_aura_blend_colors(
                                mixPointStart,
                                mixPointBackground,
                                mixPointBackgroundA,
                                mixPointAB,
                                mixPointBC,
                                mixPointEnd,
                                backgroundColor,
                                colorB,
                                colorA,
                                colorC,
                                fractal_value);
        
    return float4(selected_color.x, selected_color.y, selected_color.z, 1.0);
}
