// TruVisuals CRT Glitch — digital glitch / datamosh clip effect.
//   Splits the frame into horizontal blocks. A block "glitches" when its
//   per-frame random value falls under Frequency, then it is horizontally
//   displaced (source sampled at an X offset) and sometimes RGB-split for a
//   chromatic tear. Which blocks fire is reseeded every frame by
//   TIMELINE_FRAME_INDEX, so most frames stay calm and glitches burst.
//   Block selection uses an exact integer hash (lowbias32) so the look is
//   deterministic and matches the Python-verified preview bit-for-bit.
//
// Keyframeable controls: Intensity (displacement), Frequency (how often),
// Block Size (px). Defaults are the subtle, verified look — not the max.

DEFINE_UI_PARAMS(intensity, Intensity,  DCTLUI_SLIDER_FLOAT, 0.5,  0.0, 1.0,  0.01)
DEFINE_UI_PARAMS(frequency, Frequency,  DCTLUI_SLIDER_FLOAT, 0.10, 0.0, 1.0,  0.01)
DEFINE_UI_PARAMS(blockSize, Block Size, DCTLUI_SLIDER_INT,   16,   4,   80,   1)

// Baked constants (match the verified preview exactly).
#define MAXSHIFT_FRAC 0.10f   // max horizontal displacement / width
#define CHROMA_FRAC   0.006f  // chroma split / width
#define CHROMA_PROB   0.55f   // fraction of active blocks that also colour-split
#define MAG_MIN       0.35f   // active blocks always displace at least this much

// Chris Wellons "lowbias32" integer hash — identical in Python (numpy uint32).
__DEVICE__ unsigned int lowbias32(unsigned int x)
{
    x ^= x >> 16;
    x *= 0x7feb352du;
    x ^= x >> 15;
    x *= 0x846ca68bu;
    x ^= x >> 16;
    return x;
}

// Top 24 bits -> exactly representable in float32: gives [0,1) matching numpy.
__DEVICE__ float randf(unsigned int s)
{
    return (float)(lowbias32(s) >> 8) / 16777216.0f;
}

__DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y, __TEXTURE__ p_TexR, __TEXTURE__ p_TexG, __TEXTURE__ p_TexB)
{
    float Wf = (float)p_Width;
    float maxX = Wf - 1.0f;

    int bs = blockSize < 1 ? 1 : blockSize;
    unsigned int bi = (unsigned int)(p_Y / bs);
    unsigned int frame = (unsigned int)TIMELINE_FRAME_INDEX;

    // Per-block, per-frame seed. Multiplies wrap mod 2^32 (same as numpy uint32).
    unsigned int base = bi * 2654435761u + frame * 40503u;

    float aActive = randf(base + 0u);
    float aMag    = randf(base + 101u);
    float aSign   = randf(base + 202u);
    float aChroma = randf(base + 303u);

    int active = (aActive < frequency) ? 1 : 0;

    // Horizontal displacement (rounded to whole pixels for clean nearest sampling).
    float mag  = MAG_MIN + (1.0f - MAG_MIN) * aMag;   // [MAG_MIN, 1]
    float sign = (aSign < 0.5f) ? -1.0f : 1.0f;
    float shift = active ? (sign * mag * intensity * MAXSHIFT_FRAC * Wf) : 0.0f;
    shift = _floorf(shift + 0.5f);

    // Occasional chromatic split on active blocks.
    int doChroma = (active && aChroma < CHROMA_PROB) ? 1 : 0;
    float chroma = doChroma ? _floorf(intensity * CHROMA_FRAC * Wf + 0.5f) : 0.0f;

    float baseX = (float)p_X + shift;
    float yy    = (float)p_Y;

    float xR = _fminf(_fmaxf(baseX + chroma, 0.0f), maxX);
    float xG = _fminf(_fmaxf(baseX,          0.0f), maxX);
    float xB = _fminf(_fmaxf(baseX - chroma, 0.0f), maxX);

    float r = _tex2D(p_TexR, xR, yy);
    float g = _tex2D(p_TexG, xG, yy);
    float b = _tex2D(p_TexB, xB, yy);

    return make_float3(r, g, b);
}
