import { Cartesian3 } from "../../Source/Cesium.js";
import { HeadingPitchRange } from "../../Source/Cesium.js";
import { HeadingPitchRoll } from "../../Source/Cesium.js";
import { Matrix4 } from "../../Source/Cesium.js";
import { Transforms } from "../../Source/Cesium.js";
import { Model } from "../../Source/Cesium.js";
import { PostProcessStageLibrary } from "../../Source/Cesium.js";
import createCanvas from "../createCanvas.js";
import createScene from "../createScene.js";
import pollToPromise from "../pollToPromise.js";
import ViewportPrimitive from "../ViewportPrimitive.js";
import { when } from "../../Source/Cesium.js";

describe(
  "Scene/PostProcessStageLibrary",
  function () {
    var scene;

    beforeAll(function () {
      scene = createScene({
        canvas: createCanvas(3, 3),
      });
    });

    afterAll(function () {
      scene.destroyForSpecs();
    });

    afterEach(function () {
      scene.postProcessStages.removeAll();
      scene.primitives.removeAll();

      scene.postProcessStages.fxaa.enabled = false;
      scene.postProcessStages.bloom.enabled = false;
      scene.postProcessStages.ambientOcclusion.enabled = false;
      scene.renderForSpecs();
    });

    var model;

    function loadModel(url) {
      model = scene.primitives.add(
        Model.fromGltf({
          modelMatrix: Transforms.eastNorthUpToFixedFrame(
            Cartesian3.fromDegrees(0.0, 0.0, 100.0)
          ),
          url: url,
        })
      );
      model.zoomTo = function () {
        var camera = scene.camera;
        var center = Matrix4.multiplyByPoint(
          model.modelMatrix,
          model.boundingSphere.center,
          new Cartesian3()
        );
        var r =
          4.0 * Math.max(model.boundingSphere.radius, camera.frustum.near);
        camera.lookAt(center, new HeadingPitchRange(0.0, 0.0, r));
      };

      return pollToPromise(
        function () {
          // Render scene to progressively load the model
          scene.renderForSpecs();
          return model.ready;
        },
        { timeout: 10000 }
      )
        .then(function () {
          return model;
        })
        .otherwise(function () {
          return when.reject(model);
        });
    }

    it("black and white", function () {
      var fs =
        "void main() { \n" +
        "    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); \n" +
        "} \n";
      scene.primitives.add(new ViewportPrimitive(fs));

      expect(scene).toRenderAndCall(function (rgba) {
        for (var i = 0; i < 3; ++i) {
          for (var j = 0; j < 3; ++j) {
            var k = i * 4 * 3 + 4 * j;
            expect(rgba[k]).toEqual(255);
            expect(rgba[k + 1]).toEqual(0);
            expect(rgba[k + 2]).toEqual(0);
            expect(rgba[k + 3]).toEqual(255);
          }
        }
      });

      scene.postProcessStages.add(
        PostProcessStageLibrary.createBlackAndWhiteStage()
      );
      scene.renderForSpecs();
      expect(scene).toRenderAndCall(function (rgba) {
        expect(rgba[0]).toBeGreaterThan(0);
        expect(rgba[1]).toEqual(rgba[0]);
        expect(rgba[2]).toEqual(rgba[0]);
        expect(rgba[3]).toEqual(255);

        for (var i = 0; i < 3; ++i) {
          for (var j = 0; j < 3; ++j) {
            var k = i * 4 * 3 + 4 * j;
            expect(rgba[k]).toEqual(rgba[0]);
            expect(rgba[k + 1]).toEqual(rgba[1]);
            expect(rgba[k + 2]).toEqual(rgba[2]);
            expect(rgba[k + 3]).toEqual(rgba[3]);
          }
        }
      });
    });

    it("per-feature black and white", function () {
      return loadModel("./Data/Models/Box/CesiumBoxTest.gltf").then(
        function () {
          model.zoomTo();

          var stage = scene.postProcessStages.add(
            PostProcessStageLibrary.createBlackAndWhiteStage()
          );
          stage.selected = [];

          return pollToPromise(function () {
            scene.renderForSpecs();
            return stage.ready;
          }).then(function () {
            var color;
            expect(scene).toRenderAndCall(function (rgba) {
              color = rgba;
              expect(rgba[1]).toEqual(rgba[0]);
              expect(rgba[2]).toEqual(rgba[0]);
              expect(rgba[3]).toEqual(255);
            });

            stage.selected = [model];
            expect(scene).toRenderAndCall(function (rgba) {
              expect(rgba).not.toEqual(color);
            });
          });
        }
      );
    });

    it("brightness", function () {
      var fs =
        "void main() { \n" +
        "    gl_FragColor = vec4(vec3(0.25), 1.0); \n" +
        "} \n";
      scene.primitives.add(new ViewportPrimitive(fs));

      var red;
      var green;
      var blue;
      expect(scene).toRenderAndCall(function (rgba) {
        for (var i = 0; i < 3; ++i) {
          for (var j = 0; j < 3; ++j) {
            var k = i * 4 * 3 + 4 * j;
            expect(rgba[k]).toEqualEpsilon(Math.floor(255 * 0.25), 1.0);
            expect(rgba[k + 1]).toEqual(rgba[k]);
            expect(rgba[k + 2]).toEqual(rgba[k]);
            expect(rgba[k + 3]).toEqual(255);
          }
        }

        red = rgba[0];
        green = rgba[1];
        blue = rgba[2];
      });

      scene.postProcessStages.add(
        PostProcessStageLibrary.createBrightnessStage()
      );
      scene.renderForSpecs();
      expect(scene).toRenderAndCall(function (rgba) {
        expect(rgba[0]).not.toEqual(red);
        expect(rgba[1]).not.toEqual(green);
        expect(rgba[2]).not.toEqual(blue);
        expect(rgba[3]).toEqual(255);

        for (var i = 0; i < 3; ++i) {
          for (var j = 0; j < 3; ++j) {
            var k = i * 4 * 3 + 4 * j;
            expect(rgba[k]).toEqual(rgba[0]);
            expect(rgba[k + 1]).toEqual(rgba[1]);
            expect(rgba[k + 2]).toEqual(rgba[2]);
            expect(rgba[k + 3]).toEqual(rgba[3]);
          }
        }
      });
    });

    it("night vision", function () {
      var fs =
        "void main() { \n" +
        "    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); \n" +
        "} \n";
      scene.primitives.add(new ViewportPrimitive(fs));

      expect(scene).toRenderAndCall(function (rgba) {
        for (var i = 0; i < 3; ++i) {
          for (var j = 0; j < 3; ++j) {
            var k = i * 4 * 3 + 4 * j;
            expect(rgba[k]).toEqual(255);
            expect(rgba[k + 1]).toEqual(0);
            expect(rgba[k + 2]).toEqual(0);
            expect(rgba[k + 3]).toEqual(255);
          }
        }
      });

      scene.postProcessStages.add(
        PostProcessStageLibrary.createNightVisionStage()
      );
      scene.renderForSpecs();
      expect(scene).toRenderAndCall(function (rgba) {
        for (var i = 0; i < 3; ++i) {
          for (var j = 0; j < 3; ++j) {
            var k = i * 4 * 3 + 4 * j;
            expect(rgba[k]).toEqual(0);
            expect(rgba[k + 1]).toBeGreaterThan(0);
            expect(rgba[k + 2]).toEqual(0);
            expect(rgba[k + 3]).toEqual(255);
          }
        }
      });
    });

    it("depth view", function () {
      if (!scene.context.depthTexture) {
        return;
      }

      var fs =
        "void main() { \n" +
        "    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); \n" +
        "} \n";
      scene.primitives.add(new ViewportPrimitive(fs));

      expect(scene).toRenderAndCall(function (rgba) {
        for (var i = 0; i < 3; ++i) {
          for (var j = 0; j < 3; ++j) {
            var k = i * 4 * 3 + 4 * j;
            expect(rgba[k]).toEqual(255);
            expect(rgba[k + 1]).toEqual(0);
            expect(rgba[k + 2]).toEqual(0);
            expect(rgba[k + 3]).toEqual(255);
          }
        }
      });

      scene.postProcessStages.add(
        PostProcessStageLibrary.createDepthViewStage()
      );
      scene.renderForSpecs();
      expect(scene).toRenderAndCall(function (rgba) {
        for (var i = 0; i < 3; ++i) {
          for (var j = 0; j < 3; ++j) {
            var k = i * 4 * 3 + 4 * j;
            expect(rgba[k]).toEqual(255);
            expect(rgba[k + 1]).toEqual(255);
            expect(rgba[k + 2]).toEqual(255);
            expect(rgba[k + 3]).toEqual(255);
          }
        }
      });
    });

    it("blur", function () {
      var fs =
        "void main() { \n" +
        "    gl_FragColor = all(equal(floor(gl_FragCoord.xy), vec2(1.0, 1.0))) ? vec4(1.0, 0.0, 0.0, 1.0) : vec4(0.0, 0.0, 1.0, 1.0); \n" +
        "} \n";
      scene.primitives.add(new ViewportPrimitive(fs));

      expect(scene).toRenderAndCall(function (rgba) {
        for (var i = 0; i < 3; ++i) {
          for (var j = 0; j < 3; ++j) {
            if (i === 1 && j === 1) {
              continue;
            }
            var k = i * 4 * 3 + 4 * j;
            expect(rgba[k]).toEqual(0);
            expect(rgba[k + 1]).toEqual(0);
            expect(rgba[k + 2]).toEqual(255);
            expect(rgba[k + 3]).toEqual(255);
          }
        }

        expect(rgba[16]).toEqual(255);
        expect(rgba[17]).toEqual(0);
        expect(rgba[18]).toEqual(0);
        expect(rgba[19]).toEqual(255);
      });

      scene.postProcessStages.add(PostProcessStageLibrary.createBlurStage());
      scene.renderForSpecs();
      expect(scene).toRenderAndCall(function (rgba) {
        expect(rgba[16]).toBeGreaterThan(0);
        expect(rgba[17]).toEqual(0);
        expect(rgba[18]).toBeGreaterThan(0);
        expect(rgba[19]).toEqual(255);
      });
    });

    it("blur uniforms", function () {
      var blur = PostProcessStageLibrary.createBlurStage();
      expect(blur.uniforms.delta).toEqual(1.0);
      expect(blur.uniforms.sigma).toEqual(2.0);
      expect(blur.uniforms.stepSize).toEqual(1.0);

      blur.uniforms.delta = 2.0;
      blur.uniforms.sigma = 3.0;
      blur.uniforms.stepSize = 2.0;

      expect(blur.uniforms.delta).toEqual(2.0);
      expect(blur.uniforms.sigma).toEqual(3.0);
      expect(blur.uniforms.stepSize).toEqual(2.0);
    });

    it("depth of field", function () {
      if (!scene.context.depthTexture) {
        return;
      }

      var origin = Cartesian3.fromDegrees(-123.0744619, 44.0503706, 100.0);
      var modelMatrix = Transforms.headingPitchRollToFixedFrame(
        origin,
        new HeadingPitchRoll()
      );

      var model = scene.primitives.add(
        Model.fromGltf({
          url: "./Data/Models/Box/CesiumBoxTest.gltf",
          modelMatrix: modelMatrix,
          scale: 40.0,
        })
      );

      var ready = false;
      model.readyPromise.then(function () {
        ready = true;
      });

      var offset = new Cartesian3(
        -37.048378684557974,
        -24.852967044804245,
        4.352023653686047
      );
      scene.camera.lookAt(origin, offset);

      return pollToPromise(function () {
        scene.render();
        return ready;
      }).then(function () {
        expect(scene).toRenderAndCall(function (rgba) {
          for (var i = 0; i < rgba.length; i += 4) {
            expect(rgba[i]).toBeGreaterThan(0);
            expect(rgba[i + 1]).toEqual(0);
            expect(rgba[i + 2]).toEqual(0);
            expect(rgba[i + 3]).toEqual(255);
          }

          scene.postProcessStages.add(
            PostProcessStageLibrary.createDepthOfFieldStage()
          );
          scene.renderForSpecs();
          expect(scene).toRenderAndCall(function (rgba2) {
            for (var i = 0; i < rgba.length; i += 4) {
              expect(rgba2[i]).toBeGreaterThan(0);
              expect(rgba2[i + 1]).toEqual(0);
              expect(rgba2[i + 2]).toEqual(0);
              expect(rgba2[i + 3]).toEqual(255);

              expect(rgba2[i]).not.toEqual(rgba[i]);
            }
          });
        });
      });
    });

    it("depth of field uniforms", function () {
      var dof = PostProcessStageLibrary.createDepthOfFieldStage();
      expect(dof.uniforms.focalDistance).toEqual(5.0);
      expect(dof.uniforms.delta).toEqual(1.0);
      expect(dof.uniforms.sigma).toEqual(2.0);
      expect(dof.uniforms.stepSize).toEqual(1.0);

      dof.uniforms.focalDistance = 6.0;
      dof.uniforms.delta = 2.0;
      dof.uniforms.sigma = 3.0;
      dof.uniforms.stepSize = 2.0;

      expect(dof.uniforms.focalDistance).toEqual(6.0);
      expect(dof.uniforms.delta).toEqual(2.0);
      expect(dof.uniforms.sigma).toEqual(3.0);
      expect(dof.uniforms.stepSize).toEqual(2.0);
    });

    it("ambient occlusion", function () {
      if (!scene.context.depthTexture) {
        return;
      }

      scene.postProcessStages.ambientOcclusion.enabled = true;
      scene.postProcessStages.ambientOcclusion.uniforms.ambientOcclusionOnly = true;
      scene.renderForSpecs();
      expect(scene).toRenderAndCall(function (rgba) {
        for (var i = 0; i < rgba.length; i += 4) {
          expect(rgba[i]).toEqual(255);
          expect(rgba[i + 1]).toEqual(255);
          expect(rgba[i + 2]).toEqual(255);
          expect(rgba[i + 3]).toEqual(255);
        }
      });
      scene.postProcessStages.ambientOcclusion.enabled = false;
    });

    it("ambient occlusion uniforms", function () {
      var ao = PostProcessStageLibrary.createAmbientOcclusionStage();
      expect(ao.uniforms.ambientOcclusionOnly).toEqual(false);
      expect(ao.uniforms.intensity).toEqual(3.0);
      expect(ao.uniforms.bias).toEqual(0.1);
      expect(ao.uniforms.lengthCap).toEqual(0.26);
      expect(ao.uniforms.stepSize).toEqual(1.95);
      expect(ao.uniforms.frustumLength).toEqual(1000.0);
      expect(ao.uniforms.randomTexture).not.toBeDefined();
      expect(ao.uniforms.delta).toEqual(1.0);
      expect(ao.uniforms.sigma).toEqual(2.0);
      expect(ao.uniforms.blurStepSize).toEqual(0.86);

      ao.uniforms.ambientOcclusionOnly = true;
      ao.uniforms.intensity = 4.0;
      ao.uniforms.bias = 0.2;
      ao.uniforms.lengthCap = 0.3;
      ao.uniforms.stepSize = 2.0;
      ao.uniforms.frustumLength = 1001.0;
      ao.uniforms.delta = 2.0;
      ao.uniforms.sigma = 3.0;
      ao.uniforms.blurStepSize = 2.0;

      expect(ao.uniforms.ambientOcclusionOnly).toEqual(true);
      expect(ao.uniforms.intensity).toEqual(4.0);
      expect(ao.uniforms.bias).toEqual(0.2);
      expect(ao.uniforms.lengthCap).toEqual(0.3);
      expect(ao.uniforms.stepSize).toEqual(2.0);
      expect(ao.uniforms.frustumLength).toEqual(1001.0);
      expect(ao.uniforms.delta).toEqual(2.0);
      expect(ao.uniforms.sigma).toEqual(3.0);
      expect(ao.uniforms.blurStepSize).toEqual(2.0);
    });

    it("bloom", function () {
      var origin = Cartesian3.fromDegrees(-123.0744619, 44.0503706, 100.0);
      var modelMatrix = Transforms.headingPitchRollToFixedFrame(
        origin,
        new HeadingPitchRoll()
      );

      var model = scene.primitives.add(
        Model.fromGltf({
          url: "./Data/Models/Box/CesiumBoxTest.gltf",
          modelMatrix: modelMatrix,
          scale: 40.0,
        })
      );

      var ready = false;
      model.readyPromise.then(function () {
        ready = true;
      });

      var offset = new Cartesian3(
        -37.048378684557974,
        -24.852967044804245,
        4.352023653686047
      );
      scene.camera.lookAt(origin, offset);

      return pollToPromise(function () {
        scene.render();
        return ready;
      }).then(function () {
        expect(scene).toRenderAndCall(function (rgba) {
          for (var i = 0; i < rgba.length; i += 4) {
            expect(rgba[i]).toBeGreaterThan(0);
            expect(rgba[i + 1]).toEqual(0);
            expect(rgba[i + 2]).toEqual(0);
            expect(rgba[i + 3]).toEqual(255);
          }

          scene.postProcessStages.bloom.enabled = true;
          scene.renderForSpecs();
          expect(scene).toRenderAndCall(function (rgba2) {
            for (var i = 0; i < rgba.length; i += 4) {
              expect(rgba2[i]).toBeGreaterThan(0);
              expect(rgba2[i + 1]).toEqual(0);
              expect(rgba2[i + 2]).toEqual(0);
              expect(rgba2[i + 3]).toEqual(255);

              expect(rgba2[i]).not.toEqual(rgba[i]);
            }
          });
          scene.postProcessStages.bloom.enabled = false;
        });
      });
    });

    it("bloom uniforms", function () {
      var bloom = PostProcessStageLibrary.createBloomStage();
      expect(bloom.uniforms.glowOnly).toEqual(false);
      expect(bloom.uniforms.contrast).toEqual(128.0);
      expect(bloom.uniforms.brightness).toEqual(-0.3);
      expect(bloom.uniforms.delta).toEqual(1.0);
      expect(bloom.uniforms.sigma).toEqual(2.0);
      expect(bloom.uniforms.stepSize).toEqual(1.0);

      bloom.uniforms.glowOnly = true;
      bloom.uniforms.contrast = 0.0;
      bloom.uniforms.brightness = 3.0;
      bloom.uniforms.delta = 2.0;
      bloom.uniforms.sigma = 3.0;
      bloom.uniforms.stepSize = 2.0;

      expect(bloom.uniforms.glowOnly).toEqual(true);
      expect(bloom.uniforms.contrast).toEqual(0.0);
      expect(bloom.uniforms.brightness).toEqual(3.0);
      expect(bloom.uniforms.delta).toEqual(2.0);
      expect(bloom.uniforms.sigma).toEqual(3.0);
      expect(bloom.uniforms.stepSize).toEqual(2.0);
    });
  },
  "WebGL"
);
