#version 330 core
out vec4 FragColor;
in vec2 vUV;

uniform vec3 iResolution;
uniform float iTime;
uniform float uAlpha;

const float PI = 3.14159265358979323846;
const int AA = 1;
const int MAXITER_POT = 300;
const int MAXITER_NORMAL = 500;
const float ER_POT = 100000.0;
const float ER_NORMAL = 100.0;
const int NUMBER_OF_POINTS = 8;
const float CENTER_DURATION = 31.0;
const float ROTATION_DURATION = 53.0;

const vec3 coordinates[NUMBER_OF_POINTS] = vec3[NUMBER_OF_POINTS](
    vec3(-0.774693, 0.1242263647, 14.0),
    vec3(-0.58013, 0.48874, 14.0),
    vec3(-1.77, 0.0, 5.0),
    vec3(-0.744166858, 0.13150536, 13.0),
    vec3(0.41646, -0.210156433, 16.0),
    vec3(-0.7455, 0.1126, 10.0),
    vec3(-1.1604872, 0.2706806, 12.0),
    vec3(-0.735805, 0.196726496, 15.0)
);

const vec2 defaultCenter = vec2(-0.6, 0.0);
const vec4 insideColor = vec4(0.0, 0.0, 0.0, 1.0);

vec3 palette(float t, vec3 a, vec3 b, vec3 c, vec3 d)
{
    return a + b * cos(2.0 * PI * (c * t + d));
}

vec3 awesomePalette(float t)
{
    return palette(t, vec3(0.5), vec3(0.5), vec3(1.0), vec3(0.0, 0.1, 0.2));
}

vec2 cmul(vec2 a, vec2 b)
{
    return vec2(a.x * b.x - a.y * b.y, a.x * b.y + b.x * a.y);
}

vec2 cpow2(vec2 c)
{
    return vec2(c.x * c.x - c.y * c.y, 2.0 * c.x * c.y);
}

vec2 cdiv(vec2 a, vec2 b)
{
    float denom = max(dot(b, b), 1e-8);
    return vec2(
        (a.x * b.x + a.y * b.y) / denom,
        (a.y * b.x - a.x * b.y) / denom
    );
}

mat2 rotate2d(float theta)
{
    float s = sin(theta);
    float c = cos(theta);
    return mat2(c, -s, s, c);
}

float potential(vec2 c)
{
    vec2 z = vec2(0.0);

    for (int iter = 0; iter < MAXITER_POT; ++iter)
    {
        z = cpow2(z) + c;
        float absZ = length(z);
        if (absZ > ER_POT)
        {
            return abs(log(log2(absZ)) - (float(iter) + 1.0) * log(2.0));
        }
    }

    return -1.0;
}

float reflection(vec2 c, float localTime)
{
    vec2 z = vec2(0.0);
    vec2 dc = vec2(0.0);
    const float h2 = 1.5;
    vec2 lightDir = rotate2d(localTime / ROTATION_DURATION) * normalize(vec2(-1.0, 1.0));

    for (int i = 0; i < MAXITER_NORMAL; ++i)
    {
        dc = 2.0 * cmul(dc, z) + vec2(1.0, 0.0);
        z = cpow2(z) + c;

        if (length(z) > ER_NORMAL)
        {
            vec2 slope = normalize(cdiv(z, dc));
            float lit = dot(slope, lightDir) + h2;
            lit /= (1.0 + h2);
            return max(lit, 0.0);
        }
    }

    return -1.0;
}

void render(out vec4 fragColor, in vec2 fragCoord, float localTime, vec2 currentCenter, float currentZoom)
{
    vec2 uv = (fragCoord - iResolution.xy * 0.5) / min(iResolution.x, iResolution.y) * 2.0;

    float mixFactor = 1.0 - (0.5 + 0.5 * cos(localTime / CENTER_DURATION * 2.0 * PI));
    float maxZoom = exp2(-currentZoom);
    float zoom = exp2(-currentZoom * mixFactor);
    float denom = max(1.0 - maxZoom, 1e-5);
    vec2 centerMix = mix(currentCenter, defaultCenter, clamp(zoom / denom - maxZoom, 0.0, 1.0));
    vec2 c = centerMix + (rotate2d(localTime / ROTATION_DURATION) * uv) * zoom;

    float pot = potential(c);
    float ref = reflection(c, localTime);

    if (ref < 0.0 || pot < 0.0)
    {
        fragColor = insideColor;
        return;
    }

    float intensity = 1.0 - sqrt(fract(pot));
    intensity = mix(intensity, ref, 0.5);

    vec3 color = awesomePalette(localTime / 50.0 + pot / 40.0);
    vec3 shaded =
        color * intensity +
        vec3(intensity) * 0.3 +
        clamp(ref - 0.5, 0.0, 1.0) * pow(1.0 - fract(pot), 30.0);

    fragColor = vec4(clamp(shaded, 0.0, 1.0), 1.0);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    float localTime = iTime + CENTER_DURATION * 0.5 - 7.0;
    int centerIndex = int(floor(localTime / CENTER_DURATION)) % NUMBER_OF_POINTS;
    vec2 currentCenter = coordinates[centerIndex].xy;
    float currentZoom = coordinates[centerIndex].z;

    fragColor = vec4(0.0);

    float fraction = 1.0 / float(AA);
    float fraction2 = fraction / float(AA);
    for (int i = 0; i < AA; ++i)
    {
        for (int j = 0; j < AA; ++j)
        {
            vec2 shift = vec2(
                float(i) * fraction + float(AA - j - 1) * fraction2,
                float(j) * fraction + float(i) * fraction2
            );

            vec4 color = vec4(0.0);
            render(color, fragCoord + shift, localTime, currentCenter, currentZoom);
            fragColor += clamp(color, 0.0, 1.0);
        }
    }

    fragColor /= float(AA * AA);
}

void main()
{
    vec4 color;
    mainImage(color, gl_FragCoord.xy);
    color.a *= uAlpha;
    FragColor = color;
}
