#version 330 core
#define PI 3.14159265358979323846

in vec2 vUV;
out vec4 FragColor;

uniform sampler2D texA;
uniform sampler2D texB;
uniform float progress; // 0..1
uniform float startDelay; // fraction of progress where B should start (0..1)
uniform int effectId; // 0 fade,1 wipe,2 bands,3 curtains,4 horizontal wipe,5 dissolve blocks,9 wavy,10 crosswarp,11 burning,12 pixellated
uniform float softness; // for wipe/curtain
uniform int bands; // for bands
uniform float rotationDeg; // for bands rotation
uniform float blockSizePx; // for dissolve
uniform float noiseScale; // for noise transitions
uniform float noiseSpeed;
uniform float noiseThreshold;
uniform float waveAmplitude; // for wavy transition
uniform vec2 screenSize; // viewport size in pixels
uniform float dotFrequency; // for dots effect (default ~200.0)
uniform float dotPower; // for dots effect (default ~10.0)

vec4 texAcol(vec2 uv){ return texture(texA, uv); }
vec4 texBcol(vec2 uv){ return texture(texB, uv); }

vec2 wavyOffset(float prog, float x, float theta) {
    float phase = prog * prog + prog + theta;
    float shifty = waveAmplitude * prog * cos(10.0 * (prog + x));
    return vec2(0.0, shifty);
}

float smoothEdge(float x, float w){ return smoothstep(0.0, w, x); }

float hash12(vec2 p)
{
    return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}

// simple 2D value noise
float noise(vec2 p)
{
    vec2 i = floor(p);
    vec2 f = fract(p);
    // four corners
    float a = hash12(i + vec2(0.0,0.0));
    float b = hash12(i + vec2(1.0,0.0));
    float c = hash12(i + vec2(0.0,1.0));
    float d = hash12(i + vec2(1.0,1.0));
    vec2 u = f*f*(3.0-2.0*f);
    return mix(a, b, u.x) + (c - a)*u.y*(1.0 - u.x) + (d - b)*u.x*u.y;
}

void main(){
    vec2 uv = vUV;
    // remap progress so that mixing starts at startDelay and completes at 1.0
    float pMix = clamp((progress - startDelay) / max(1e-6, 1.0 - startDelay), 0.0, 1.0);
    // If we haven't reached startDelay, show A only (no animation, no mixing)
    if (progress < startDelay) {
        FragColor = texAcol(uv);
        return;
    }
    if(effectId == 0){
        // Fade
        FragColor = mix(texAcol(uv), texBcol(uv), progress);
        return;
    }
    else if(effectId == 6){
        // Noise - Smoke style (smooth organic)
        float s = max(0.0001, noiseScale);
        float n = noise(uv * s + vec2(noiseSpeed * progress));
        float m = smoothstep(noiseThreshold - 0.05, noiseThreshold + 0.05, n);
        FragColor = mix(texAcol(uv), texBcol(uv), m);
        return;
    }
    else if(effectId == 7){
        // Noise - Organic (fbm-like)
        float s = max(0.0001, noiseScale);
        float ttime = noiseSpeed * progress;
        float n = 0.0;
        float amp = 1.0; float freq = 1.0;
        for(int i=0;i<4;i++){
            n += amp * noise(uv * s * freq + vec2(ttime * freq));
            freq *= 2.0; amp *= 0.5;
        }
        n = n / (1.0 + 0.5 + 0.25 + 0.125);
        float m = smoothstep(noiseThreshold - 0.08, noiseThreshold + 0.08, n);
        FragColor = mix(texAcol(uv), texBcol(uv), m);
        return;
    }
    else if(effectId == 8){
        // Noise - Glitch (hard threshold)
        float s = max(0.0001, noiseScale);
        float n = noise(uv * s + vec2(noiseSpeed * progress));
        float m = step(noiseThreshold, n); // hard
        FragColor = mix(texAcol(uv), texBcol(uv), m);
        return;
    }
    else if(effectId == 1){
        // Vertical wipe (left->right)
        // use pMix so the wipe starts from 0 when startDelay elapses
        float edge = pMix;
        float w = max(0.0001, softness);
        // show B on the left as progress increases: invert the smoothstep result
        float t = 1.0 - smoothstep(edge - w, edge + w, uv.x);
        FragColor = mix(texAcol(uv), texBcol(uv), t);
        return;
    }
    else if(effectId == 2){
        // Bands (venetian): compute coordinate along rotated axis
        float angle = radians(rotationDeg);
        vec2 dir = vec2(cos(angle), sin(angle));
        // map uv [0,1] to centered coordinate [-0.5,0.5]
        vec2 centered = uv - 0.5;
        float coord = dot(centered, dir) + 0.5; // 0..1 along dir
        float bandPos = fract(coord * float(bands));
        // decide fill based on progress and band position
        // for each band, if bandPos < progress then show B
        float t = step(bandPos, progress);
        FragColor = mix(texAcol(uv), texBcol(uv), t);
        return;
    }
    else if(effectId == 3){
        // Curtains: two halves open from center outward
        float midpoint = 0.5;
        // use pMix so curtains don't appear until startDelay elapses
        float gap = pMix * 0.5; // max half-width
        float w = max(0.0001, softness);
        // If gap is very small (<= softness) the two smoothstep transitions overlap and
        // produce a non-zero mask at progress==0. Handle small-gap case with hard steps
        // to avoid showing B prematurely.
        float t;
        if (gap <= w)
        {
            float left = step(midpoint - gap, uv.x);
            float right = step(midpoint + gap, uv.x);
            t = left * (1.0 - right);
        }
        else
        {
            // left curtain visible region: uv.x < midpoint - gap
            // right curtain: uv.x > midpoint + gap
            float leftT = smoothstep(midpoint - gap - w, midpoint - gap + w, uv.x);
            float rightSmooth = smoothstep(midpoint + gap - w, midpoint + gap + w, uv.x);
            // combine: areas between left and right show B.
            float inside = leftT * (1.0 - rightSmooth);
            t = clamp(inside, 0.0, 1.0);
        }
        FragColor = mix(texAcol(uv), texBcol(uv), t);
        return;
    }
    else if(effectId == 4){
        // Horizontal wipe (bottom->top)
        // use pMix so the wipe starts from 0 when startDelay elapses
        float edge = pMix;
        float w = max(0.0001, softness);
        // horizontal wipe: show B at bottom->top as progress increases (invert)
        float t = 1.0 - smoothstep(edge - w, edge + w, uv.y);
        FragColor = mix(texAcol(uv), texBcol(uv), t);
        return;
    }
    else if(effectId == 5){
        // Dissolve blocks: B appears in random square blocks over A
        float sizePx = max(2.0, blockSizePx);
        vec2 px = uv * screenSize;
        vec2 cell = floor(px / sizePx);
        float r = hash12(cell);
        // use pMix so dissolve doesn't start until after startDelay
        // quick guard: avoid tiny pMix causing sparse early pixels — require minimal start threshold
        float startThresh = 0.02; // quick fix: small deadzone at start
        float t = (pMix <= startThresh) ? 0.0 : step(r, pMix);
        FragColor = mix(texAcol(uv), texBcol(uv), t);
        return;
    }
    else if(effectId == 9){
        // Wavy transition - use pMix so startDelay is respected
        vec2 uvA = uv + wavyOffset(pMix, uv.x, 0.0);
        vec2 uvB = uv + wavyOffset(1.0 - pMix, uv.x, 3.14);
        FragColor = mix(texAcol(uvA), texBcol(uvB), pMix);
        return;
    }
    else if(effectId == 10){
        // Crosswarp transition - use pMix so it starts from zero after startDelay
        vec2 p = uv;
        float x = pMix;
        x = smoothstep(0.0, 1.0, (x * 2.0 + p.x - 1.0));
        vec2 uvA = (p - 0.5) * (1.0 - x) + 0.5;
        vec2 uvB = (p - 0.5) * x + 0.5;
        FragColor = mix(texAcol(uvA), texBcol(uvB), x);
        return;
    }
    else if(effectId == 11){
        // Burning transition (based on http://glslsandbox.com/e#35642.0)
        vec2 p = uv;

        // Hash function
        vec3 p3 = vec3(p.xy, 1.0);
        // noise helpers (local)
        vec2 i2 = floor(p * 15.1);
        vec2 f2 = fract(p * 15.1);
        f2 *= f2 * (3.0 - 2.0 * f2);
        float hA = fract(sin(dot(vec3(i2 + vec2(0.,0.), 1.0), vec3(37.1,61.7,12.4)))*3758.5453123);
        float hB = fract(sin(dot(vec3(i2 + vec2(1.,0.), 1.0), vec3(37.1,61.7,12.4)))*3758.5453123);
        float hC = fract(sin(dot(vec3(i2 + vec2(0.,1.), 1.0), vec3(37.1,61.7,12.4)))*3758.5453123);
        float hD = fract(sin(dot(vec3(i2 + vec2(1.,1.), 1.0), vec3(37.1,61.7,12.4)))*3758.5453123);
        float n1 = mix(mix(hA, hB, f2.x), mix(hC, hD, f2.x), f2.y);

        // second octave
        vec2 i3 = floor(p * 30.2);
        vec2 f3 = fract(p * 30.2);
        f3 *= f3 * (3.0 - 2.0 * f3);
        float hA2 = fract(sin(dot(vec3(i3 + vec2(0.,0.), 1.0), vec3(37.1,61.7,12.4)))*3758.5453123);
        float hB2 = fract(sin(dot(vec3(i3 + vec2(1.,0.), 1.0), vec3(37.1,61.7,12.4)))*3758.5453123);
        float hC2 = fract(sin(dot(vec3(i3 + vec2(0.,1.), 1.0), vec3(37.1,61.7,12.4)))*3758.5453123);
        float hD2 = fract(sin(dot(vec3(i3 + vec2(1.,1.), 1.0), vec3(37.1,61.7,12.4)))*3758.5453123);
        float n2 = mix(mix(hA2, hB2, f3.x), mix(hC2, hD2, f3.x), f3.y);

        // third octave
        vec2 i4 = floor(p * 60.4);
        vec2 f4 = fract(p * 60.4);
        f4 *= f4 * (3.0 - 2.0 * f4);
        float hA3 = fract(sin(dot(vec3(i4 + vec2(0.,0.), 1.0), vec3(37.1,61.7,12.4)))*3758.5453123);
        float hB3 = fract(sin(dot(vec3(i4 + vec2(1.,0.), 1.0), vec3(37.1,61.7,12.4)))*3758.5453123);
        float hC3 = fract(sin(dot(vec3(i4 + vec2(0.,1.), 1.0), vec3(37.1,61.7,12.4)))*3758.5453123);
        float hD3 = fract(sin(dot(vec3(i4 + vec2(1.,1.), 1.0), vec3(37.1,61.7,12.4)))*3758.5453123);
        float n3 = mix(mix(hA3, hB3, f4.x), mix(hC3, hD3, f4.x), f4.y);

        // fbm from three octaves
        float fbmVal = n1 * 0.5 + n2 * 0.25 + n3 * 0.125;

        // flame noise for ember glow
        vec2 i5 = floor((p * 100.0 + vec2(0.0, 0.0)));
        vec2 f5 = fract(p * 100.0);
        f5 *= f5 * (3.0 - 2.0 * f5);
        float hE = fract(sin(dot(vec3(i5 + vec2(0.,0.), 1.0), vec3(37.1,61.7,12.4)))*3758.5453123);
        float hF = fract(sin(dot(vec3(i5 + vec2(1.,0.), 1.0), vec3(37.1,61.7,12.4)))*3758.5453123);
        float hG = fract(sin(dot(vec3(i5 + vec2(0.,1.), 1.0), vec3(37.1,61.7,12.4)))*3758.5453123);
        float hH = fract(sin(dot(vec3(i5 + vec2(1.,1.), 1.0), vec3(37.1,61.7,12.4)))*3758.5453123);
        float nFlame = mix(mix(hE, hF, f5.x), mix(hG, hH, f5.x), f5.y);

        vec3 src = texAcol(p).rgb;
        vec3 tgt = texBcol(p).rgb;
        vec3 col = src;

        // Map pMix [0,1] to ctime [0,2.5] so burning animation starts from zero after startDelay
        float ctime = pMix * 2.5;

        vec2 uvBurn = p;
        uvBurn.x -= 1.5;
        float d = uvBurn.x + uvBurn.y * 0.5 + 0.5 * fbmVal + ctime * 1.3;

        if (d > 0.35) col = clamp(col - (d - 0.35) * 10.0, 0.0, 1.0);
        if (d > 0.47) {
            if (d < 0.5)
                col += (d - 0.4) * 33.0 * 0.5 * (0.0 + nFlame) * vec3(1.5, 0.5, 0.0);
            else
                col += tgt;
        }

        FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
        return;
    }
    else if(effectId == 12){
        // Pixellated transition: pixel size grows to peak at mid-point then shrinks
        float sizePx = max(1.0, blockSizePx);
        // scale factor: peaks at pMix=0.5 (max pixelation), smooth in/out
        // use pMix so the pixelation starts from 0 when startDelay elapses
        float t = 1.0 - abs(pMix * 2.0 - 1.0);          // triangle 0->1->0
        float curSize = max(1.0, sizePx * t);
        vec2 cellSz = curSize / screenSize;
        vec2 pixUV = floor(uv / cellSz) * cellSz + cellSz * 0.5;
        // blend from A to B using a hard cut at mid-point to avoid ghosting
        vec4 colA = texAcol(pixUV);
        vec4 colB = texBcol(pixUV);
        FragColor = mix(colA, colB, step(0.5, pMix));
        return;
    }
    else if(effectId == 13){
        // RadialWipe: clock-hand angular wipe (CC BY-SA 3.0)
        // rotationDeg controls the wipe start direction (0 = right, 90 = up, etc.)
        vec2 uvn = vUV * 2.0 - 1.0;                          // map [0,1] -> [-1,1]
        vec2 normalized = (length(uvn) < 1e-6) ? vec2(0.0, 1.0) : normalize(uvn);
        float rad = radians(rotationDeg);
        vec2 forward = vec2(cos(rad), sin(rad));
        float uvDot   = clamp(dot(normalized, forward), -1.0, 1.0);
        float uvDot90 = dot(vec2(normalized.y, -normalized.x), forward); // rotated 90 deg
        // angle between this pixel and the slice direction, normalised to [0,1]
        float angle  = acos(uvDot) / PI;          // PI is now defined at top of file
        float angle1 = (1.0 - angle) * 0.5;       // left-hand side  [0, 0.5]
        float angle2 = angle * 0.5 + 0.5;         // right-hand side [0.5, 1.0]
        // mix between both sides based on which half of the wipe we are on
        float spiral = mix(angle1, angle2, floor(uvDot90) + 1.0);
        // wipe: pixels whose spiral value < pMix reveal texture B
        // use pMix so radial wipe starts from zero after startDelay
        // floor(pMix - spiral) + 1.0 is 1 when pMix > spiral, 0 otherwise
        float mask = clamp(floor(pMix - spiral) + 1.0, 0.0, 1.0);
        FragColor = mix(texAcol(vUV), texBcol(vUV), mask);
        return;
    }
    else if(effectId == 14){
        // Dots: animated dot grid transition
        vec2 p = vUV * 2.0 - 1.0;                            // map [0,1] -> [-1,1]
        float aspectRatio = screenSize.y / screenSize.x;
        float freq = max(1.0, dotFrequency);
        float power = max(1.0, dotPower);
        // oscillate between -1 and 1 based on pMix so dots start from zero after startDelay
        float oscillation = sin(pMix * PI) * 2.2;
        // dot pattern: intersecting cosine waves
        float a = clamp(cos(p.x * freq) - cos(p.y * freq * aspectRatio) + oscillation + 1.0, 0.0, 1.0);
        a = pow(a, power);
        FragColor = mix(texAcol(vUV), texBcol(vUV), a);
        return;
    }
    else{
        FragColor = mix(texAcol(uv), texBcol(uv), progress);
    }
}
