import { useEffect, useRef } from "react";
import { generateRandomRGBABytes } from "./randomTextureForShader";

const buffer_1 = `#version 300 es
precision mediump float;
uniform vec3      iResolution;           // viewport resolution (in pixels)
uniform float     iTime;                 // shader playback time (in seconds)
uniform float     iTimeDelta;            // render time (in seconds)
uniform float     iFrameRate;            // shader frame rate
uniform int       iFrame;                // shader playback frame
uniform float     iChannelTime[4];       // channel playback time (in seconds)
uniform vec3      iChannelResolution[4]; // channel resolution (in pixels)
uniform vec4      iMouse;                // mouse pixel coords. xy: current (if MLB down), zw: click
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform sampler2D iChannel2;
uniform sampler2D iChannel3;

out vec4 FragColor;

/*  
Main render. 

Temporal AA for a smooth image. Temporal accumulation is disabled while moving the view to prevent ghosting.
*/

#define ITERATIONS 200          //Increase for less grainy result
#define TEMPORAL_AA


const vec3 MainColor = vec3(1.0);

//noise code by iq
float noise( in vec3 x )
{
    vec3 p = floor(x);
    vec3 f = fract(x);
    f = f*f*(3.0-2.0*f);
    vec2 uv = (p.xy+vec2(37.0,17.0)*p.z) + f.xy;
    vec2 rg = textureLod( iChannel0, (uv+ 0.5)/256.0, 0.0 ).yx;
    return -1.0+2.0*mix( rg.x, rg.y, f.z );
}

float saturate(float x)
{
    return clamp(x, 0.0, 1.0);
}

vec3 saturate(vec3 x)
{
    return clamp(x, vec3(0.0), vec3(1.0));
}

float rand(vec2 coord)
{
    return saturate(fract(sin(dot(coord, vec2(12.9898, 78.223))) * 43758.5453));
}

float pcurve( float x, float a, float b )
{
    float k = pow(a+b,a+b) / (pow(a,a)*pow(b,b));
    return k * pow( x, a ) * pow( 1.0-x, b );
}

const float pi = 3.14159265;

float atan2(float y, float x)
{
    if (x > 0.0)
    {
        return atan(y / x);
    }
    else if (x == 0.0)
    {
        if (y > 0.0)
        {
            return pi / 2.0;
        }
        else if (y < 0.0)
        {
            return -(pi / 2.0);
        }
        else
        {
            return 0.0;
        }
    }
    else //(x < 0.0)
    {
        if (y >= 0.0)
        {
            return atan(y / x) + pi;
        }
        else
        {
            return atan(y / x) - pi;
        }
    }
}

float sdTorus(vec3 p, vec2 t)
{
    vec2 q = vec2(length(p.xz) - t.x, p.y);
    return length(q)-t.y;
}

float sdSphere(vec3 p, float r)
{
  return length(p)-r;
}

void Haze(inout vec3 color, vec3 pos, float alpha)
{
    vec2 t = vec2(1.0, 0.01);

    float torusDist = length(sdTorus(pos + vec3(0.0, -0.05, 0.0), t));

    float bloomDisc = 1.0 / (pow(torusDist, 2.0) + 0.001);
    vec3 col = MainColor;
    bloomDisc *= length(pos) < 0.5 ? 0.0 : 1.0;

    color += col * bloomDisc * (2.9 / float(ITERATIONS)) * (1.0 - alpha * 1.0);
}

void GasDisc(inout vec3 color, inout float alpha, vec3 pos)
{
    float discRadius = 3.2;
    float discWidth = 5.3;
    float discInner = discRadius - discWidth * 0.5;
    float discOuter = discRadius + discWidth * 0.5;
    
    vec3 origin = vec3(0.0, 0.0, 0.0);
    float mouseZ = iMouse.y / iResolution.y;
    vec3 discNormal = normalize(vec3(0.0, 1.0, 0.0));
    float discThickness = 0.1;

    float distFromCenter = distance(pos, origin);
    float distFromDisc = dot(discNormal, pos - origin);
    
    float radialGradient = 1.0 - saturate((distFromCenter - discInner) / discWidth * 0.5);

    float coverage = pcurve(radialGradient, 4.0, 0.9);

    discThickness *= radialGradient;
    coverage *= saturate(1.0 - abs(distFromDisc) / discThickness);

    vec3 dustColorLit = MainColor;
    vec3 dustColorDark = vec3(0.0, 0.0, 0.0);

    float dustGlow = 1.0 / (pow(1.0 - radialGradient, 2.0) * 290.0 + 0.002);
    vec3 dustColor = dustColorLit * dustGlow * 8.2;

    coverage = saturate(coverage * 0.7);


    float fade = pow((abs(distFromCenter - discInner) + 0.4), 4.0) * 0.04;
    float bloomFactor = 1.0 / (pow(distFromDisc, 2.0) * 40.0 + fade + 0.00002);
    vec3 b = dustColorLit * pow(bloomFactor, 1.5);
    
    b *= mix(vec3(1.7, 1.1, 1.0), vec3(0.5, 0.6, 1.0), vec3(pow(radialGradient, 2.0)));
    b *= mix(vec3(1.7, 0.5, 0.1), vec3(1.0), vec3(pow(radialGradient, 0.5)));

    dustColor = mix(dustColor, b * 150.0, saturate(1.0 - coverage * 1.0));
    coverage = saturate(coverage + bloomFactor * bloomFactor * 0.1);
    
    if (coverage < 0.01)
    {
        return;   
    }
    
    
    vec3 radialCoords;
    radialCoords.x = distFromCenter * 1.5 + 0.55;
    radialCoords.y = atan2(-pos.x, -pos.z) * 1.5;
    radialCoords.z = distFromDisc * 1.5;

    radialCoords *= 0.95;
    
    float speed = 0.06;
    
    float noise1 = 1.0;
    vec3 rc = radialCoords + 0.0;               rc.y += iTime * speed;
    noise1 *= noise(rc * 3.0) * 0.5 + 0.5;      rc.y -= iTime * speed;
    noise1 *= noise(rc * 6.0) * 0.5 + 0.5;      rc.y += iTime * speed;
    noise1 *= noise(rc * 12.0) * 0.5 + 0.5;     rc.y -= iTime * speed;
    noise1 *= noise(rc * 24.0) * 0.5 + 0.5;     rc.y += iTime * speed;

    float noise2 = 2.0;
    rc = radialCoords + 30.0;
    noise2 *= noise(rc * 3.0) * 0.5 + 0.5;      rc.y += iTime * speed;
    noise2 *= noise(rc * 6.0) * 0.5 + 0.5;      rc.y -= iTime * speed;
    noise2 *= noise(rc * 12.0) * 0.5 + 0.5;     rc.y += iTime * speed;
    noise2 *= noise(rc * 24.0) * 0.5 + 0.5;     rc.y -= iTime * speed;
    noise2 *= noise(rc * 48.0) * 0.5 + 0.5;     rc.y += iTime * speed;
    noise2 *= noise(rc * 92.0) * 0.5 + 0.5;     rc.y -= iTime * speed;

    dustColor *= noise1 * 0.998 + 0.002;
    coverage *= noise2;
    
    radialCoords.y += iTime * speed * 0.5;
    
    dustColor *= pow(texture(iChannel1, radialCoords.yx * vec2(0.15, 0.27)).rgb, vec3(2.0)) * 4.0;

    coverage = saturate(coverage * 1200.0 / float(ITERATIONS));
    dustColor = max(vec3(0.0), dustColor);

    coverage *= pcurve(radialGradient, 4.0, 0.9);

    color = (1.0 - alpha) * dustColor * coverage + color;

    alpha = (1.0 - alpha) * coverage + alpha;
}



vec3 rotate(vec3 p, float x, float y, float z)
{
    mat3 matx = mat3(1.0, 0.0, 0.0,
                     0.0, cos(x), sin(x),
                     0.0, -sin(x), cos(x));

    mat3 maty = mat3(cos(y), 0.0, -sin(y),
                     0.0, 1.0, 0.0,
                     sin(y), 0.0, cos(y));

    mat3 matz = mat3(cos(z), sin(z), 0.0,
                     -sin(z), cos(z), 0.0,
                     0.0, 0.0, 1.0);

    p = matx * p;
    p = matz * p;
    p = maty * p;

    return p;
}

void RotateCamera(inout vec3 eyevec, inout vec3 eyepos)
{
    float mousePosY = iMouse.y / iResolution.y;
    float mousePosX = iMouse.x / iResolution.x;

    vec3 angle = vec3(mousePosY * 0.05 + 0.05, 1.0 + mousePosX * 1.0, -0.45);

    eyevec = rotate(eyevec, angle.x, angle.y, angle.z);
    eyepos = rotate(eyepos, angle.x, angle.y, angle.z);
}

void WarpSpace(inout vec3 eyevec, inout vec3 raypos)
{
    vec3 origin = vec3(0.0, 0.0, 0.0);

    float singularityDist = distance(raypos, origin);
    float warpFactor = 1.0 / (pow(singularityDist, 2.0) + 0.000001);

    vec3 singularityVector = normalize(origin - raypos);
    
    float warpAmount = 5.0;

    eyevec = normalize(eyevec + singularityVector * warpFactor * warpAmount / float(ITERATIONS));
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    
    vec2 uv = fragCoord.xy / iResolution.xy;
    
    float aspect = iResolution.x / iResolution.y;

    vec2 uveye = uv;
    
    #ifdef TEMPORAL_AA
    uveye.x += (rand(uv + sin(iTime * 1.0)) / iResolution.x) * (iMouse.z > 1.0 ? 0.0 : 1.0);
    uveye.y += (rand(uv + 1.0 + sin(iTime * 1.0)) / iResolution.y) * (iMouse.z > 1.0 ? 0.0 : 1.0);
    #endif
    
    vec3 eyevec = normalize(vec3((uveye * 2.0 - 1.0) * vec2(aspect, 1.0), 6.0));
    vec3 eyepos = vec3(0.0, -0.0, -10.0);
    
    vec2 mousepos = iMouse.xy / iResolution.xy;
    if (mousepos.x == 0.0)
    {
        mousepos.x = 0.35;
    }
    eyepos.x += mousepos.x * 3.0 - 1.5;
    
    const float far = 15.0;

    RotateCamera(eyevec, eyepos);

    vec3 color = vec3(0.0, 0.0, 0.0);
    
    float dither = rand(uv 
                        #ifdef TEMPORAL_AA
                        + sin(iTime * 1.0) * (iMouse.z > 1.0 ? 0.0 : 1.0)
                        #endif
                       ) * 2.0;


    float alpha = 0.0;
    vec3 raypos = eyepos + eyevec * dither * far / float(ITERATIONS);
    for (int i = 0; i < ITERATIONS; i++)
    {        
        WarpSpace(eyevec, raypos);
        raypos += eyevec * far / float(ITERATIONS);
        GasDisc(color, alpha, raypos);
        Haze(color, raypos, alpha);
    }
    
    color *= 0.0001;

    
    #ifdef TEMPORAL_AA
    const float p = 1.0;
    vec3 previous = pow(texture(iChannel2, uv).rgb, vec3(1.0 / p));
    
    color = pow(color, vec3(1.0 / p));
    
    float blendWeight = 0.9 * (iMouse.z > 1.0 ? 0.0 : 1.0);
    
    color = mix(color, previous, blendWeight);
    
    color = pow(color, vec3(p));
    #endif
    
    fragColor = vec4(saturate(color), 1.0);
}

void main()
{
    // gl_FragCoord.xy is pixel coordinates (1..width, 1..height)
    mainImage(FragColor, gl_FragCoord.xy);
}
`;

const buffer_2 = `#version 300 es
precision mediump float;
uniform vec3      iResolution;           // viewport resolution (in pixels)
uniform float     iTime;                 // shader playback time (in seconds)
uniform float     iTimeDelta;            // render time (in seconds)
uniform float     iFrameRate;            // shader frame rate
uniform int       iFrame;                // shader playback frame
uniform float     iChannelTime[4];       // channel playback time (in seconds)
uniform vec3      iChannelResolution[4]; // channel resolution (in pixels)
uniform vec4      iMouse;                // mouse pixel coords. xy: current (if MLB down), zw: click
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform sampler2D iChannel2;
uniform sampler2D iChannel3;

out vec4 FragColor;
//First bloom pass, mipmap tree thing

vec3 ColorFetch(vec2 coord)
{
 	return texture(iChannel0, coord).rgb;   
}

vec3 Grab1(vec2 coord, const float octave, const vec2 offset)
{
 	float scale = exp2(octave);
    
    coord += offset;
    coord *= scale;

   	if (coord.x < 0.0 || coord.x > 1.0 || coord.y < 0.0 || coord.y > 1.0)
    {
     	return vec3(0.0);   
    }
    
    vec3 color = ColorFetch(coord);

    return color;
}

vec3 Grab4(vec2 coord, const float octave, const vec2 offset)
{
 	float scale = exp2(octave);
    
    coord += offset;
    coord *= scale;

   	if (coord.x < 0.0 || coord.x > 1.0 || coord.y < 0.0 || coord.y > 1.0)
    {
     	return vec3(0.0);   
    }
    
    vec3 color = vec3(0.0);
    float weights = 0.0;
    
    const int oversampling = 4;
    
    for (int i = 0; i < oversampling; i++)
    {    	    
        for (int j = 0; j < oversampling; j++)
        {
			vec2 off = (vec2(i, j) / iResolution.xy + vec2(0.0) / iResolution.xy) * scale / float(oversampling);
            color += ColorFetch(coord + off);
            

            weights += 1.0;
        }
    }
    
    color /= weights;
    
    return color;
}

vec3 Grab8(vec2 coord, const float octave, const vec2 offset)
{
 	float scale = exp2(octave);
    
    coord += offset;
    coord *= scale;

   	if (coord.x < 0.0 || coord.x > 1.0 || coord.y < 0.0 || coord.y > 1.0)
    {
     	return vec3(0.0);   
    }
    
    vec3 color = vec3(0.0);
    float weights = 0.0;
    
    const int oversampling = 8;
    
    for (int i = 0; i < oversampling; i++)
    {    	    
        for (int j = 0; j < oversampling; j++)
        {
			vec2 off = (vec2(i, j) / iResolution.xy + vec2(0.0) / iResolution.xy) * scale / float(oversampling);
            color += ColorFetch(coord + off);
            

            weights += 1.0;
        }
    }
    
    color /= weights;
    
    return color;
}

vec3 Grab16(vec2 coord, const float octave, const vec2 offset)
{
 	float scale = exp2(octave);
    
    coord += offset;
    coord *= scale;

   	if (coord.x < 0.0 || coord.x > 1.0 || coord.y < 0.0 || coord.y > 1.0)
    {
     	return vec3(0.0);   
    }
    
    vec3 color = vec3(0.0);
    float weights = 0.0;
    
    const int oversampling = 16;
    
    for (int i = 0; i < oversampling; i++)
    {    	    
        for (int j = 0; j < oversampling; j++)
        {
			vec2 off = (vec2(i, j) / iResolution.xy + vec2(0.0) / iResolution.xy) * scale / float(oversampling);
            color += ColorFetch(coord + off);
            

            weights += 1.0;
        }
    }
    
    color /= weights;
    
    return color;
}

vec2 CalcOffset(float octave)
{
    vec2 offset = vec2(0.0);
    
    vec2 padding = vec2(10.0) / iResolution.xy;
    
    offset.x = -min(1.0, floor(octave / 3.0)) * (0.25 + padding.x);
    
    offset.y = -(1.0 - (1.0 / exp2(octave))) - padding.y * octave;

	offset.y += min(1.0, floor(octave / 3.0)) * 0.35;
    
 	return offset;   
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord.xy / iResolution.xy;
    
    
    vec3 color = vec3(0.0);
    
    /*
    Create a mipmap tree thingy with padding to prevent leaking bloom
   	
	Since there's no mipmaps for the previous buffer and the reduction process has to be done in one pass,
    oversampling is required for a proper result
	*/
    color += Grab1(uv, 1.0, vec2(0.0,  0.0)   );
    color += Grab4(uv, 2.0, vec2(CalcOffset(1.0))   );
    color += Grab8(uv, 3.0, vec2(CalcOffset(2.0))   );
    color += Grab16(uv, 4.0, vec2(CalcOffset(3.0))   );
    color += Grab16(uv, 5.0, vec2(CalcOffset(4.0))   );
    color += Grab16(uv, 6.0, vec2(CalcOffset(5.0))   );
    color += Grab16(uv, 7.0, vec2(CalcOffset(6.0))   );
    color += Grab16(uv, 8.0, vec2(CalcOffset(7.0))   );


    fragColor = vec4(color, 1.0);
}

void main()
{
    // gl_FragCoord.xy is pixel coordinates (1..width, 1..height)
    mainImage(FragColor, gl_FragCoord.xy);
}
`;

const buffer_3 = `#version 300 es
precision mediump float;
uniform vec3      iResolution;           // viewport resolution (in pixels)
uniform float     iTime;                 // shader playback time (in seconds)
uniform float     iTimeDelta;            // render time (in seconds)
uniform float     iFrameRate;            // shader frame rate
uniform int       iFrame;                // shader playback frame
uniform float     iChannelTime[4];       // channel playback time (in seconds)
uniform vec3      iChannelResolution[4]; // channel resolution (in pixels)
uniform vec4      iMouse;                // mouse pixel coords. xy: current (if MLB down), zw: click
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform sampler2D iChannel2;
uniform sampler2D iChannel3;

out vec4 FragColor;
//Horizontal gaussian blur leveraging hardware filtering for fewer texture lookups.

vec3 ColorFetch(vec2 coord)
{
 	return texture(iChannel0, coord).rgb;   
}

float weights[5];
float offsets[5];


void mainImage( out vec4 fragColor, in vec2 fragCoord )
{    
    
    weights[0] = 0.19638062;
    weights[1] = 0.29675293;
    weights[2] = 0.09442139;
    weights[3] = 0.01037598;
    weights[4] = 0.00025940;
    
    offsets[0] = 0.00000000;
    offsets[1] = 1.41176471;
    offsets[2] = 3.29411765;
    offsets[3] = 5.17647059;
    offsets[4] = 7.05882353;
    
    vec2 uv = fragCoord.xy / iResolution.xy;
    
    vec3 color = vec3(0.0);
    float weightSum = 0.0;
    
    if (uv.x < 0.52)
    {
        color += ColorFetch(uv) * weights[0];
        weightSum += weights[0];

        for(int i = 1; i < 5; i++)
        {
            vec2 offset = vec2(offsets[i]) / iResolution.xy;
            color += ColorFetch(uv + offset * vec2(0.5, 0.0)) * weights[i];
            color += ColorFetch(uv - offset * vec2(0.5, 0.0)) * weights[i];
            weightSum += weights[i] * 2.0;
        }

        color /= weightSum;
    }

    fragColor = vec4(color,1.0);
}

void main()
{
    // gl_FragCoord.xy is pixel coordinates (1..width, 1..height)
    mainImage(FragColor, gl_FragCoord.xy);
}
`;

const buffer_4 = `#version 300 es
precision mediump float;
uniform vec3      iResolution;           // viewport resolution (in pixels)
uniform float     iTime;                 // shader playback time (in seconds)
uniform float     iTimeDelta;            // render time (in seconds)
uniform float     iFrameRate;            // shader frame rate
uniform int       iFrame;                // shader playback frame
uniform float     iChannelTime[4];       // channel playback time (in seconds)
uniform vec3      iChannelResolution[4]; // channel resolution (in pixels)
uniform vec4      iMouse;                // mouse pixel coords. xy: current (if MLB down), zw: click
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform sampler2D iChannel2;
uniform sampler2D iChannel3;

out vec4 FragColor;

//Vertical gaussian blur leveraging hardware filtering for fewer texture lookups.

vec3 ColorFetch(vec2 coord)
{
 	return texture(iChannel0, coord).rgb;   
}

float weights[5];
float offsets[5];


void mainImage( out vec4 fragColor, in vec2 fragCoord )
{    
    
    weights[0] = 0.19638062;
    weights[1] = 0.29675293;
    weights[2] = 0.09442139;
    weights[3] = 0.01037598;
    weights[4] = 0.00025940;
    
    offsets[0] = 0.00000000;
    offsets[1] = 1.41176471;
    offsets[2] = 3.29411765;
    offsets[3] = 5.17647059;
    offsets[4] = 7.05882353;
    
    vec2 uv = fragCoord.xy / iResolution.xy;
    
    vec3 color = vec3(0.0);
    float weightSum = 0.0;
    
    if (uv.x < 0.52)
    {
        color += ColorFetch(uv) * weights[0];
        weightSum += weights[0];

        for(int i = 1; i < 5; i++)
        {
            vec2 offset = vec2(offsets[i]) / iResolution.xy;
            color += ColorFetch(uv + offset * vec2(0.0, 0.5)) * weights[i];
            color += ColorFetch(uv - offset * vec2(0.0, 0.5)) * weights[i];
            weightSum += weights[i] * 2.0;
        }

        color /= weightSum;
    }

    fragColor = vec4(color,1.0);
}

void main()
{
    // gl_FragCoord.xy is pixel coordinates (1..width, 1..height)
    mainImage(FragColor, gl_FragCoord.xy);
}
`;

const image_1 = `#version 300 es
precision mediump float;
uniform vec3      iResolution;           // viewport resolution (in pixels)
uniform float     iTime;                 // shader playback time (in seconds)
uniform float     iTimeDelta;            // render time (in seconds)
uniform float     iFrameRate;            // shader frame rate
uniform int       iFrame;                // shader playback frame
uniform float     iChannelTime[4];       // channel playback time (in seconds)
uniform vec3      iChannelResolution[4]; // channel resolution (in pixels)
uniform vec4      iMouse;                // mouse pixel coords. xy: current (if MLB down), zw: click
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform sampler2D iChannel2;
uniform sampler2D iChannel3;

out vec4 FragColor;
vec3 saturate(vec3 x)
{
    return clamp(x, vec3(0.0), vec3(1.0));
}

vec4 cubic(float x)
{
    float x2 = x * x;
    float x3 = x2 * x;
    vec4 w;
    w.x =   -x3 + 3.0*x2 - 3.0*x + 1.0;
    w.y =  3.0*x3 - 6.0*x2       + 4.0;
    w.z = -3.0*x3 + 3.0*x2 + 3.0*x + 1.0;
    w.w =  x3;
    return w / 6.0;
}

vec4 BicubicTexture(in sampler2D tex, in vec2 coord)
{
	vec2 resolution = iResolution.xy;

	coord *= resolution;

	float fx = fract(coord.x);
    float fy = fract(coord.y);
    coord.x -= fx;
    coord.y -= fy;

    fx -= 0.5;
    fy -= 0.5;

    vec4 xcubic = cubic(fx);
    vec4 ycubic = cubic(fy);

    vec4 c = vec4(coord.x - 0.5, coord.x + 1.5, coord.y - 0.5, coord.y + 1.5);
    vec4 s = vec4(xcubic.x + xcubic.y, xcubic.z + xcubic.w, ycubic.x + ycubic.y, ycubic.z + ycubic.w);
    vec4 offset = c + vec4(xcubic.y, xcubic.w, ycubic.y, ycubic.w) / s;

    vec4 sample0 = texture(tex, vec2(offset.x, offset.z) / resolution);
    vec4 sample1 = texture(tex, vec2(offset.y, offset.z) / resolution);
    vec4 sample2 = texture(tex, vec2(offset.x, offset.w) / resolution);
    vec4 sample3 = texture(tex, vec2(offset.y, offset.w) / resolution);

    float sx = s.x / (s.x + s.y);
    float sy = s.z / (s.z + s.w);

    return mix( mix(sample3, sample2, sx), mix(sample1, sample0, sx), sy);
}

vec3 ColorFetch(vec2 coord)
{
 	return texture(iChannel0, coord).rgb;   
}

vec3 BloomFetch(vec2 coord)
{
 	return BicubicTexture(iChannel3, coord).rgb;   
}

vec3 Grab(vec2 coord, const float octave, const vec2 offset)
{
 	float scale = exp2(octave);
    
    coord /= scale;
    coord -= offset;

    return BloomFetch(coord);
}

vec2 CalcOffset(float octave)
{
    vec2 offset = vec2(0.0);
    
    vec2 padding = vec2(10.0) / iResolution.xy;
    
    offset.x = -min(1.0, floor(octave / 3.0)) * (0.25 + padding.x);
    
    offset.y = -(1.0 - (1.0 / exp2(octave))) - padding.y * octave;

	offset.y += min(1.0, floor(octave / 3.0)) * 0.35;
    
 	return offset;   
}

vec3 GetBloom(vec2 coord)
{
 	vec3 bloom = vec3(0.0);
    
    //Reconstruct bloom from multiple blurred images
    bloom += Grab(coord, 1.0, vec2(CalcOffset(0.0))) * 1.0;
    bloom += Grab(coord, 2.0, vec2(CalcOffset(1.0))) * 1.5;
	bloom += Grab(coord, 3.0, vec2(CalcOffset(2.0))) * 1.0;
    bloom += Grab(coord, 4.0, vec2(CalcOffset(3.0))) * 1.5;
    bloom += Grab(coord, 5.0, vec2(CalcOffset(4.0))) * 1.8;
    bloom += Grab(coord, 6.0, vec2(CalcOffset(5.0))) * 1.0;
    bloom += Grab(coord, 7.0, vec2(CalcOffset(6.0))) * 1.0;
    bloom += Grab(coord, 8.0, vec2(CalcOffset(7.0))) * 1.0;

	return bloom;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    
    vec2 uv = fragCoord.xy / iResolution.xy;
    
    vec3 color = ColorFetch(uv);
    
    
    color += GetBloom(uv) * 0.08;
    
    color *= 200.0;
    

    //Tonemapping and color grading
    color = pow(color, vec3(1.5));
    color = color / (1.0 + color);
    color = pow(color, vec3(1.0 / 1.5));

    
    color = mix(color, color * color * (3.0 - 2.0 * color), vec3(1.0));
    color = pow(color, vec3(1.3, 1.20, 1.0));    

	color = saturate(color * 1.01);
    
    color = pow(color, vec3(0.7 / 2.2));

    fragColor = vec4(color, 1.0);

}

void main()
{
    // gl_FragCoord.xy is pixel coordinates (1..width, 1..height)
    mainImage(FragColor, gl_FragCoord.xy);
}
`;

function buildProgram(
  ctx: WebGL2RenderingContext,
  fragShader: WebGLShader,
  vertexShader: WebGLShader
) {
  const program = ctx.createProgram();
  if (!program) return null;

  ctx.attachShader(program, fragShader);
  ctx.attachShader(program, vertexShader);

  ctx.linkProgram(program);

  const status = ctx.getProgramParameter(program, ctx.LINK_STATUS);

  if (status) {
    return program;
  }

  console.error(ctx.getProgramInfoLog(program));
  ctx.deleteProgram(program);
  return null;
}

function buildShader(
  ctx: WebGL2RenderingContext,
  type: number,
  source: string
) {
  const shader = ctx.createShader(type);
  if (!shader) return null;

  ctx.shaderSource(shader, source);
  ctx.compileShader(shader);

  const status = ctx.getShaderParameter(shader, ctx.COMPILE_STATUS);

  if (status) {
    return shader;
  }

  console.error(ctx.getShaderInfoLog(shader));
  ctx.deleteShader(shader);
  return null;
}

export function ShaderBackground() {
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const gl = canvas.getContext("webgl2");
    if (!gl) return;

    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);

    const buffer_1_shader = buildShader(gl, gl.FRAGMENT_SHADER, buffer_1);
    const buffer_2_shader = buildShader(gl, gl.FRAGMENT_SHADER, buffer_2);
    const buffer_3_shader = buildShader(gl, gl.FRAGMENT_SHADER, buffer_3);
    const buffer_4_shader = buildShader(gl, gl.FRAGMENT_SHADER, buffer_4);
    const fragmentShader = buildShader(gl, gl.FRAGMENT_SHADER, image_1);

    const vertexShader = buildShader(
      gl,
      gl.VERTEX_SHADER,
      `#version 300 es
    precision mediump float;
    
    in vec2 a_position;
    
    void main() {
        gl_Position = vec4(a_position, 0.0, 1.0);
    }`
    );

    if (
      !buffer_1_shader ||
      !buffer_2_shader ||
      !buffer_3_shader ||
      !buffer_4_shader ||
      !fragmentShader ||
      !vertexShader
    )
      return;

    const buffer_1_program = buildProgram(gl, buffer_1_shader, vertexShader);
    const buffer_2_program = buildProgram(gl, buffer_2_shader, vertexShader);
    const buffer_3_program = buildProgram(gl, buffer_3_shader, vertexShader);
    const buffer_4_program = buildProgram(gl, buffer_4_shader, vertexShader);
    const finalProgram = buildProgram(gl, fragmentShader, vertexShader);

    if (
      !buffer_1_program ||
      !buffer_2_program ||
      !buffer_3_program ||
      !buffer_4_program ||
      !finalProgram
    )
      return;

    const posBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
    gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
      gl.STATIC_DRAW
    );

    {
      gl.useProgram(buffer_1_program);
      const posAttrLocation = gl.getAttribLocation(
        buffer_1_program,
        "a_position"
      );
      gl.vertexAttribPointer(posAttrLocation, 2, gl.FLOAT, false, 0, 0);
      gl.enableVertexAttribArray(posAttrLocation);
    }

    {
      gl.useProgram(buffer_2_program);
      const posAttrLocation = gl.getAttribLocation(
        buffer_2_program,
        "a_position"
      );
      gl.vertexAttribPointer(posAttrLocation, 2, gl.FLOAT, false, 0, 0);
      gl.enableVertexAttribArray(posAttrLocation);
    }

    {
      gl.useProgram(buffer_3_program);
      const posAttrLocation = gl.getAttribLocation(
        buffer_3_program,
        "a_position"
      );
      gl.vertexAttribPointer(posAttrLocation, 2, gl.FLOAT, false, 0, 0);
      gl.enableVertexAttribArray(posAttrLocation);
    }

    {
      gl.useProgram(buffer_4_program);
      const posAttrLocation = gl.getAttribLocation(
        buffer_4_program,
        "a_position"
      );
      gl.vertexAttribPointer(posAttrLocation, 2, gl.FLOAT, false, 0, 0);
      gl.enableVertexAttribArray(posAttrLocation);
    }

    {
      gl.useProgram(finalProgram);
      const posAttrLocation = gl.getAttribLocation(finalProgram, "a_position");
      gl.vertexAttribPointer(posAttrLocation, 2, gl.FLOAT, false, 0, 0);
      gl.enableVertexAttribArray(posAttrLocation);
    }

    function setResolution(
      program: WebGLProgram,
      canvas: HTMLCanvasElement,
      gl: WebGL2RenderingContext
    ) {
      const loc = gl.getUniformLocation(program, "iResolution");
      if (loc === null) {
        console.warn(
          "iResolution uniform not found (maybe not used by shader)."
        );
      } else {
        const w = canvas.width;
        const h = canvas.height;

        gl.uniform3fv(loc, [w, h, 1]);
      }
    }

    function setTime(
      program: WebGLProgram,
      now: number,
      gl: WebGL2RenderingContext
    ) {
      const loc = gl.getUniformLocation(program, "iTime");
      if (loc === null) {
        console.warn(
          "iTime uniform not found (maybe not used by shader).",
          program
        );
      } else {
        gl.uniform1f(loc, now);
      }
    }

    const frameBufferTexture = gl.createTexture();
    if (!frameBufferTexture) return;

    gl.bindTexture(gl.TEXTURE_2D, frameBufferTexture);
    gl.texImage2D(
      gl.TEXTURE_2D,
      0,
      gl.RGBA,
      canvas.width,
      canvas.height,
      0,
      gl.RGBA,
      gl.UNSIGNED_BYTE,
      null
    );
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

    const frameBufferTexture2 = gl.createTexture();
    if (!frameBufferTexture2) return;

    gl.bindTexture(gl.TEXTURE_2D, frameBufferTexture2);
    gl.texImage2D(
      gl.TEXTURE_2D,
      0,
      gl.RGBA,
      canvas.width,
      canvas.height,
      0,
      gl.RGBA,
      gl.UNSIGNED_BYTE,
      null
    );
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

    const frameBuffer = gl.createFramebuffer();
    if (!frameBuffer) return;

    gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, frameBuffer);

    gl.framebufferTexture2D(
      gl.DRAW_FRAMEBUFFER,
      gl.COLOR_ATTACHMENT0,
      gl.TEXTURE_2D,
      frameBufferTexture,
      0
    );

    const status = gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER);
    switch (status) {
      case gl.FRAMEBUFFER_COMPLETE:
        console.log("Framebuffer is complete");
        break;
      case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
        console.log("Framebuffer is incomplete attachment");
        break;
      case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
        console.log("Framebuffer is incomplete missing attachment");
        break;
      case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
        console.log("Framebuffer is incomplete dimensions");
        break;
      case gl.FRAMEBUFFER_UNSUPPORTED:
        console.log("Framebuffer is unsupported");
        break;
      default:
        console.log("Framebuffer is unknown");
    }

    const ichannel0_location_buffer_1 = gl.getUniformLocation(
      buffer_1_program as WebGLProgram,
      "iChannel0"
    );

    const ichannel0_location_buffer_2 = gl.getUniformLocation(
      buffer_2_program as WebGLProgram,
      "iChannel0"
    );

    const ichannel0_location_buffer_3 = gl.getUniformLocation(
      buffer_3_program as WebGLProgram,
      "iChannel0"
    );

    const ichannel0_location_buffer_4 = gl.getUniformLocation(
      buffer_4_program as WebGLProgram,
      "iChannel0"
    );

    const ichannel0_location_final = gl.getUniformLocation(
      finalProgram as WebGLProgram,
      "iChannel0"
    );

    function render(
      gl: WebGL2RenderingContext,
      canvas: HTMLCanvasElement,
      now: number
    ) {
      gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, frameBuffer);

      //Clear with Clear color
      gl.clear(gl.COLOR_BUFFER_BIT);

      gl.useProgram(buffer_1_program);
      setResolution(buffer_1_program as WebGLProgram, canvas, gl);
      setTime(buffer_1_program as WebGLProgram, now, gl);
      const bytes = generateRandomRGBABytes(canvas.width, canvas.height);

      const tex = gl.createTexture();
      if (!tex) throw new Error("Failed to create texture");

      // Activate texture unit 0 and bind the texture
      gl.activeTexture(gl.TEXTURE0);
      gl.bindTexture(gl.TEXTURE_2D, tex);

      // Texture parameters suitable for data textures
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

      // Upload pixel data as RGBA UNSIGNED_BYTE
      gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
      gl.texImage2D(
        gl.TEXTURE_2D,
        0,
        gl.RGBA,
        canvas.width,
        canvas.height,
        0,
        gl.RGBA,
        gl.UNSIGNED_BYTE,
        bytes
      );

      // Set sampler uniform to texture unit 0
      gl.uniform1i(ichannel0_location_buffer_1, 0);

      // RENDER 1
      gl.framebufferTexture2D(
        gl.DRAW_FRAMEBUFFER,
        gl.COLOR_ATTACHMENT0,
        gl.TEXTURE_2D,
        frameBufferTexture,
        0
      );
      gl.drawArrays(gl.TRIANGLES, 0, 6);

      gl.useProgram(buffer_2_program);
      setResolution(buffer_2_program as WebGLProgram, canvas, gl);

      // RENDER 2
      gl.activeTexture(gl.TEXTURE1);
      gl.bindTexture(gl.TEXTURE_2D, frameBufferTexture);
      gl.uniform1i(ichannel0_location_buffer_2, 1);
      gl.framebufferTexture2D(
        gl.DRAW_FRAMEBUFFER,
        gl.COLOR_ATTACHMENT0,
        gl.TEXTURE_2D,
        frameBufferTexture2,
        0
      );
      gl.drawArrays(gl.TRIANGLES, 0, 6);

      gl.useProgram(buffer_3_program);
      setResolution(buffer_3_program as WebGLProgram, canvas, gl);

      // RENDER 3
      gl.activeTexture(gl.TEXTURE1);
      gl.bindTexture(gl.TEXTURE_2D, frameBufferTexture2);
      gl.uniform1i(ichannel0_location_buffer_3, 1);
      gl.framebufferTexture2D(
        gl.DRAW_FRAMEBUFFER,
        gl.COLOR_ATTACHMENT0,
        gl.TEXTURE_2D,
        frameBufferTexture,
        0
      );
      gl.drawArrays(gl.TRIANGLES, 0, 6);

      gl.useProgram(buffer_4_program);
      setResolution(buffer_4_program as WebGLProgram, canvas, gl);

      // RENDER 4
      gl.activeTexture(gl.TEXTURE1);
      gl.bindTexture(gl.TEXTURE_2D, frameBufferTexture);
      gl.uniform1i(ichannel0_location_buffer_4, 1);
      gl.framebufferTexture2D(
        gl.DRAW_FRAMEBUFFER,
        gl.COLOR_ATTACHMENT0,
        gl.TEXTURE_2D,
        frameBufferTexture2,
        0
      );
      gl.drawArrays(gl.TRIANGLES, 0, 6);

      gl.useProgram(finalProgram);
      setResolution(finalProgram as WebGLProgram, canvas, gl);

      // FINAL RENDER
      gl.activeTexture(gl.TEXTURE1);
      gl.bindTexture(gl.TEXTURE_2D, frameBufferTexture2);
      gl.uniform1i(ichannel0_location_final, 1);
      gl.framebufferTexture2D(
        gl.DRAW_FRAMEBUFFER,
        gl.COLOR_ATTACHMENT0,
        gl.TEXTURE_2D,
        frameBufferTexture,
        0
      );
      gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
      gl.drawArrays(gl.TRIANGLES, 0, 6);
    }

    const started = Date.now();

    function update(now: number) {
      const time = (now - started) / 1000;
      render(gl as WebGL2RenderingContext, canvas as HTMLCanvasElement, time);

      requestAnimationFrame(update);
    }

    requestAnimationFrame(update);
  }, []);

  return (
    <canvas ref={canvasRef} id="blackhole_canvas" width="800" height="450" />
  );
}
