// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import { gl } from '../core/gl';
import { Shader } from '../core/shader';
import { randomShaderFunc } from '../shaders/random-shader-func';
import { simpleShader } from '../core/simple-shader';
import { BB } from '../../bb/bb';

/**
 * @filter           Lens Blur
 * @description      Imitates a camera capturing the image out of focus by using a blur that generates
 *                   the large shapes known as bokeh. The polygonal shape of real bokeh is due to the
 *                   blades of the aperture diaphragm when it isn't fully open. This blur renders
 *                   bokeh from a 6-bladed diaphragm because the computation is more efficient. It
 *                   can be separated into three rhombi, each of which is just a skewed box blur.
 *                   This filter makes use of the floating point texture WebGL extension to implement
 *                   the brightness parameter, so there will be severe visual artifacts if brightness
 *                   is non-zero and the floating point texture extension is not available. The
 *                   idea was from John White's SIGGRAPH 2011 talk but this effect has an additional
 *                   brightness parameter that fakes what would otherwise come from a HDR source.
 * @param radius     the radius of the hexagonal disk convolved with the image
 * @param brightness -1 to 1 (the brightness of the bokeh, negative values will create dark bokeh)
 * @param angle      the rotation of the bokeh in radians
 */
export function lensBlur(radius, brightness, angle) {
    // All averaging is done on values raised to a power to make more obvious bokeh
    // (we will raise the average to the inverse power at the end to compensate).
    // Without this the image looks almost like a normal blurred image. This hack is
    // obviously not realistic, but to accurately simulate this we would need a high
    // dynamic range source photograph which we don't have.
    gl.lensBlurPrePass =
        gl.lensBlurPrePass ||
        new Shader(
            null,
            '\
        uniform sampler2D texture;\
        uniform float power;\
        varying vec2 texCoord;\
        void main() {\
            vec4 color = texture2D(texture, texCoord);\
            color = pow(color, vec4(power));\
            gl_FragColor = vec4(color);\
        }\
    ',
            'lensBlurPrePass',
        );

    const common =
        '\
        uniform sampler2D texture0;\
        uniform sampler2D texture1;\
        uniform vec2 delta0;\
        uniform vec2 delta1;\
        uniform float power;\
        varying vec2 texCoord;\
        ' +
        randomShaderFunc +
        '\
        vec4 sample(vec2 delta) {\
            /* randomize the lookup values to hide the fixed number of samples */\
            float offset = random(vec3(delta, 151.7182), 0.0);\
            \
            vec4 color = vec4(0.0);\
            float total = 0.0;\
            for (float t = 0.0; t <= 30.0; t++) {\
                float percent = (t + offset) / 30.0;\
                color += texture2D(texture0, texCoord + delta * percent);\
                total += 1.0;\
            }\
            return color / total;\
        }\
    ';

    gl.lensBlur0 =
        gl.lensBlur0 ||
        new Shader(
            null,
            common +
                '\
        void main() {\
            gl_FragColor = sample(delta0);\
        }\
    ',
            'lensBlur0',
        );
    gl.lensBlur1 =
        gl.lensBlur1 ||
        new Shader(
            null,
            common +
                '\
        void main() {\
            gl_FragColor = (sample(delta0) + sample(delta1)) * 0.5;\
        }\
    ',
            'lensBlur1',
        );
    gl.lensBlur2 =
        gl.lensBlur2 ||
        new Shader(
            null,
            common +
                '\
        void main() {\
            vec4 color = (sample(delta0) + 2.0 * texture2D(texture1, texCoord)) / 3.0;\
            gl_FragColor = pow(color, vec4(power));\
        }\
    ',
            'lensBlur2',
        ).textures({ texture1: 1 });

    // Generate
    const dir = [];
    for (let i = 0; i < 3; i++) {
        const a = angle + (i * Math.PI * 2) / 3;
        dir.push([(radius * Math.sin(a)) / this.width, (radius * Math.cos(a)) / this.height]);
    }
    const power = Math.pow(10, BB.clamp(brightness, -1, 1));

    // Remap the texture values, which will help make the bokeh effect
    simpleShader.call(this, gl.lensBlurPrePass, {
        power: power,
    });

    // Blur two rhombi in parallel into extraTexture
    this._.extraTexture.ensureFormatViaTexture(this._.texture);
    simpleShader.call(
        this,
        gl.lensBlur0,
        {
            delta0: dir[0],
        },
        this._.texture,
        this._.extraTexture,
    );
    simpleShader.call(
        this,
        gl.lensBlur1,
        {
            delta0: dir[1],
            delta1: dir[2],
        },
        this._.extraTexture,
        this._.extraTexture,
    );

    // Blur the last rhombus and combine with extraTexture
    simpleShader.call(this, gl.lensBlur0, {
        delta0: dir[1],
    });
    this._.extraTexture.use(1);
    simpleShader.call(this, gl.lensBlur2, {
        power: 1 / power,
        delta0: dir[2],
    });

    return this;
}
