describe('Runtime shader effects', () => {
    let container;

    beforeEach(async () => {
        await EverythingLoaded;
        container = document.createElement('div');
        container.innerHTML = `
            <canvas width=600 height=600 id=test></canvas>
            <canvas width=600 height=600 id=report></canvas>`;
        document.body.appendChild(container);
    });

    afterEach(() => {
        document.body.removeChild(container);
    });

    const spiralSkSL = `
uniform float rad_scale;
uniform int2   in_center;
uniform float4 in_colors0;
uniform float4 in_colors1;

half4 main(float2 p) {
    float2 pp = p - float2(in_center);
    float radius = sqrt(dot(pp, pp));
    radius = sqrt(radius);
    float angle = atan(pp.y / pp.x);
    float t = (angle + 3.1415926/2) / (3.1415926);
    t += radius * rad_scale;
    t = fract(t);
    return half4(mix(in_colors0, in_colors1, t));
}`;

    // TODO(kjlubick) rewrite testRTShader and callers to use gm.
    const testRTShader = (name, done, localMatrix) => {
        const surface = CanvasKit.MakeCanvasSurface('test');
        expect(surface).toBeTruthy('Could not make surface');
        if (!surface) {
            return;
        }
        const spiral = CanvasKit.RuntimeEffect.Make(spiralSkSL);
        expect(spiral).toBeTruthy('could not compile program');

        expect(spiral.getUniformCount()     ).toEqual(4);
        expect(spiral.getUniformFloatCount()).toEqual(11);
        const center = spiral.getUniform(1);
        expect(center).toBeTruthy('could not fetch numbered uniform');
        expect(center.slot     ).toEqual(1);
        expect(center.columns  ).toEqual(2);
        expect(center.rows     ).toEqual(1);
        expect(center.isInteger).toEqual(true);
        const color_0 = spiral.getUniform(2);
        expect(color_0).toBeTruthy('could not fetch numbered uniform');
        expect(color_0.slot     ).toEqual(3);
        expect(color_0.columns  ).toEqual(4);
        expect(color_0.rows     ).toEqual(1);
        expect(color_0.isInteger).toEqual(false);
        expect(spiral.getUniformName(2)).toEqual('in_colors0');

        const canvas = surface.getCanvas();
        const paint = new CanvasKit.Paint();
        canvas.clear(CanvasKit.BLACK); // black should not be visible
        const shader = spiral.makeShader([
            0.3,
            CANVAS_WIDTH/2, CANVAS_HEIGHT/2,
            1, 0, 0, 1, // solid red
            0, 1, 0, 1], // solid green
            localMatrix);
        paint.setShader(shader);
        canvas.drawRect(CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), paint);

        paint.delete();
        shader.delete();
        spiral.delete();

        reportSurface(surface, name, done);
    };

    it('can compile custom shader code', (done) => {
        testRTShader('rtshader_spiral', done);
    });

    it('can apply a matrix to the shader', (done) => {
        testRTShader('rtshader_spiral_translated', done, CanvasKit.Matrix.translated(-200, 100));
    });

    it('can provide a error handler for compilation errors', () => {
        let error = '';
        const spiral = CanvasKit.RuntimeEffect.Make(`invalid sksl code, I hope`, (e) => {
            error = e;
        });
        expect(spiral).toBeFalsy();
        expect(error).toContain('error');
    });

    it('can generate a debug trace', () => {
        // We don't support debug tracing on GPU, so we always request a software canvas here.
        const surface = CanvasKit.MakeSWCanvasSurface('test');
        expect(surface).toBeTruthy('Could not make surface');
        if (!surface) {
            return;
        }
        const spiral = CanvasKit.RuntimeEffect.Make(spiralSkSL);
        expect(spiral).toBeTruthy('could not compile program');

        const canvas = surface.getCanvas();
        const paint = new CanvasKit.Paint();
        const shader = spiral.makeShader([
            0.3,
            CANVAS_WIDTH/2, CANVAS_HEIGHT/2,
            1, 0, 0, 1,   // solid red
            0, 1, 0, 1]); // solid green

        const traced = CanvasKit.RuntimeEffect.MakeTraced(shader, CANVAS_WIDTH/2, CANVAS_HEIGHT/2);
        paint.setShader(traced.shader);
        canvas.drawRect(CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), paint);

        const traceData = traced.debugTrace.writeTrace();
        paint.delete();
        shader.delete();
        spiral.delete();
        traced.shader.delete();
        traced.debugTrace.delete();
        surface.delete();

        const parsedTrace = JSON.parse(traceData);
        expect(parsedTrace).toBeTruthy('could not parse trace JSON');
        expect(parsedTrace.functions).toBeTruthy('debug trace does not include function list');
        expect(parsedTrace.slots).toBeTruthy('debug trace does not include slot list');
        expect(parsedTrace.trace).toBeTruthy('debug trace does not include trace data');
        expect(parsedTrace.nonsense).toBeFalsy('debug trace includes a nonsense key');
        expect(parsedTrace.mystery).toBeFalsy('debug trace includes a mystery key');
        expect(parsedTrace.source).toEqual([
            "",
            "uniform float rad_scale;",
            "uniform int2   in_center;",
            "uniform float4 in_colors0;",
            "uniform float4 in_colors1;",
            "",
            "half4 main(float2 p) {",
            "    float2 pp = p - float2(in_center);",
            "    float radius = sqrt(dot(pp, pp));",
            "    radius = sqrt(radius);",
            "    float angle = atan(pp.y / pp.x);",
            "    float t = (angle + 3.1415926/2) / (3.1415926);",
            "    t += radius * rad_scale;",
            "    t = fract(t);",
            "    return half4(mix(in_colors0, in_colors1, t));",
            "}"
        ]);
    });

    const loadBrick = fetch(
        '/assets/brickwork-texture.jpg')
        .then((response) => response.arrayBuffer());
    const loadMandrill = fetch(
        '/assets/mandrill_512.png')
        .then((response) => response.arrayBuffer());

    const thresholdSkSL = `
uniform shader before_map;
uniform shader after_map;
uniform shader threshold_map;

uniform float cutoff;
uniform float slope;

float smooth_cutoff(float x) {
    x = x * slope + (0.5 - slope * cutoff);
    return clamp(x, 0, 1);
}

half4 main(float2 xy) {
    half4 before = before_map.eval(xy);
    half4 after = after_map.eval(xy);

    float m = smooth_cutoff(threshold_map.eval(xy).r);
    return mix(before, after, half(m));
}`;

    // TODO(kjlubick) rewrite testChildrenShader and callers to use gm.
    const testChildrenShader = (name, done, localMatrix) => {
        Promise.all([loadBrick, loadMandrill]).then((values) => {
            catchException(done, () => {
                const [brickData, mandrillData] = values;
                const brickImg = CanvasKit.MakeImageFromEncoded(brickData);
                expect(brickImg).toBeTruthy('brick image could not be loaded');
                const mandrillImg = CanvasKit.MakeImageFromEncoded(mandrillData);
                expect(mandrillImg).toBeTruthy('mandrill image could not be loaded');

                const thresholdEffect = CanvasKit.RuntimeEffect.Make(thresholdSkSL);
                expect(thresholdEffect).toBeTruthy('threshold did not compile');
                const spiralEffect = CanvasKit.RuntimeEffect.Make(spiralSkSL);
                expect(spiralEffect).toBeTruthy('spiral did not compile');

                const brickShader = brickImg.makeShaderCubic(
                    CanvasKit.TileMode.Decal, CanvasKit.TileMode.Decal,
                    1/3 /*B*/, 1/3 /*C*/,
                    CanvasKit.Matrix.scaled(CANVAS_WIDTH/brickImg.width(),
                                            CANVAS_HEIGHT/brickImg.height()));
                const mandrillShader = mandrillImg.makeShaderCubic(
                    CanvasKit.TileMode.Decal, CanvasKit.TileMode.Decal,
                    1/3 /*B*/, 1/3 /*C*/,
                    CanvasKit.Matrix.scaled(CANVAS_WIDTH/mandrillImg.width(),
                                            CANVAS_HEIGHT/mandrillImg.height()));
                const spiralShader = spiralEffect.makeShader([
                    0.8,
                    CANVAS_WIDTH/2, CANVAS_HEIGHT/2,
                    1, 1, 1, 1,
                    0, 0, 0, 1]);

                const blendShader = thresholdEffect.makeShaderWithChildren(
                    [0.5, 5],
                    [brickShader, mandrillShader, spiralShader], localMatrix);

                const surface = CanvasKit.MakeCanvasSurface('test');
                expect(surface).toBeTruthy('Could not make surface');
                const canvas = surface.getCanvas();
                const paint = new CanvasKit.Paint();

                paint.setShader(blendShader);
                canvas.drawRect(CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), paint);

                brickImg.delete();
                mandrillImg.delete();
                thresholdEffect.delete();
                spiralEffect.delete();
                brickShader.delete();
                mandrillShader.delete();
                spiralShader.delete();
                blendShader.delete();
                paint.delete();

                reportSurface(surface, name, done);
            })();
        });
    }

    it('take other shaders as fragment processors', (done) => {
        testChildrenShader('rtshader_children', done);
    });

    it('apply a local matrix to the children-based shader', (done) => {
        testChildrenShader('rtshader_children_rotated', done, CanvasKit.Matrix.rotated(Math.PI/12));
    });

    it('can generate runtime blender', (done) => {
        const loadBrick = fetch(
            '/assets/brickwork-texture.jpg')
            .then((response) => response.arrayBuffer());
        const loadMandrill = fetch(
            '/assets/mandrill_512.png')
            .then((response) => response.arrayBuffer());
        Promise.all([loadBrick, loadMandrill]).then((values) => {
            catchException(done, () => {
                const screenSkSL = `
                    vec4 main(vec4 src, vec4 dst) {
                        return src + dst - src * dst;
                    }
                `;

                const [brickData, mandrillData] = values;
                const brickImg = CanvasKit.MakeImageFromEncoded(brickData);
                expect(brickImg)
                    .withContext('brick image could not be loaded')
                    .toBeTruthy();
                const mandrillImg = CanvasKit.MakeImageFromEncoded(mandrillData);
                expect(mandrillImg)
                    .withContext('mandrill image could not be loaded')
                    .toBeTruthy();

                const brickShader = brickImg.makeShaderCubic(
                    CanvasKit.TileMode.Decal, CanvasKit.TileMode.Decal,
                    1/3 /*B*/, 1/3 /*C*/,
                    CanvasKit.Matrix.scaled(CANVAS_WIDTH/brickImg.width(),
                                            CANVAS_HEIGHT/brickImg.height()));
                const mandrillShader = mandrillImg.makeShaderCubic(
                    CanvasKit.TileMode.Decal, CanvasKit.TileMode.Decal,
                    1/3 /*B*/, 1/3 /*C*/,
                    CanvasKit.Matrix.scaled(CANVAS_WIDTH/mandrillImg.width(),
                                            CANVAS_HEIGHT/mandrillImg.height()));

                const surface = CanvasKit.MakeCanvasSurface('test');
                expect(surface)
                    .withContext('Could not make surface')
                    .toBeTruthy();
                const canvas = surface.getCanvas();
                const paint = new CanvasKit.Paint();

                const screenEffect = CanvasKit.RuntimeEffect.MakeForBlender(screenSkSL);
                expect(screenEffect)
                    .withContext('could not compile program')
                    .toBeTruthy();
                expect(screenEffect.getUniformCount()     ).toEqual(0);
                expect(screenEffect.getUniformFloatCount()).toEqual(0);
                const screenBlender = screenEffect.makeBlender([]);

                paint.setShader(brickShader);
                canvas.drawRect(CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), paint);
                paint.setShader(mandrillShader);
                paint.setBlender(screenBlender);
                canvas.drawRect(CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), paint);

                brickImg.delete();
                mandrillImg.delete();
                brickShader.delete();
                mandrillShader.delete();
                paint.delete();
                screenBlender.delete();
                screenEffect.delete();

                reportSurface(surface, 'rtblender', done);
            })();
        });
    });
});
