import { defined } from "../../Source/Cesium.js";
import { getTimestamp } from "../../Source/Cesium.js";
import { FrameRateMonitor } from "../../Source/Cesium.js";
import createScene from "../createScene.js";

describe(
  "Scene/FrameRateMonitor",
  function () {
    var scene;
    beforeAll(function () {
      scene = createScene();
    });

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

    var monitor;
    afterEach(function () {
      if (defined(monitor)) {
        monitor.destroy();
        monitor = undefined;
      }
    });

    function spinWait(milliseconds) {
      var endTime = getTimestamp() + milliseconds;
      /*eslint-disable no-empty*/
      while (getTimestamp() < endTime) {}
      /*eslint-enable no-empty*/
    }

    it("throws when constructed without a scene", function () {
      expect(function () {
        monitor = new FrameRateMonitor();
      }).toThrowDeveloperError();

      expect(function () {
        monitor = new FrameRateMonitor({});
      }).toThrowDeveloperError();
    });

    it("can be constructed with just a scene", function () {
      monitor = new FrameRateMonitor({
        scene: scene,
      });

      expect(monitor.samplingWindow).toBe(5.0);
      expect(monitor.quietPeriod).toBe(2.0);
      expect(monitor.warmupPeriod).toBe(5.0);
      expect(monitor.minimumFrameRateDuringWarmup).toBe(4);
      expect(monitor.minimumFrameRateAfterWarmup).toBe(8);
      expect(monitor.scene).toBe(scene);
      expect(monitor.lowFrameRate.numberOfListeners).toBe(0);
      expect(monitor.nominalFrameRate.numberOfListeners).toBe(0);
    });

    it("honors parameters to the constructor", function () {
      monitor = new FrameRateMonitor({
        scene: scene,
        samplingWindow: 3.0,
        quietPeriod: 1.0,
        warmupPeriod: 6.0,
        minimumFrameRateDuringWarmup: 1,
        minimumFrameRateAfterWarmup: 2,
      });

      expect(monitor.samplingWindow).toBe(3.0);
      expect(monitor.quietPeriod).toBe(1.0);
      expect(monitor.warmupPeriod).toBe(6.0);
      expect(monitor.minimumFrameRateDuringWarmup).toBe(1);
      expect(monitor.minimumFrameRateAfterWarmup).toBe(2);
      expect(monitor.scene).toBe(scene);
    });

    it("raises the lowFrameRate event on low frame rate", function () {
      monitor = new FrameRateMonitor({
        scene: scene,
        quietPeriod: 0.001,
        warmupPeriod: 0.001,
        samplingWindow: 0.001,
        minimumFrameRateDuringWarmup: 1000,
        minimumFrameRateAfterWarmup: 1000,
      });

      var spyListener = jasmine.createSpy("listener");
      monitor.lowFrameRate.addEventListener(spyListener);

      // Rendering once starts the quiet period
      scene.render();

      // Wait until we're well past the end of the quiet period.
      spinWait(2);

      // Rendering again records our first sample.
      scene.render();

      // Wait well over a millisecond, which is the maximum frame time allowed by this instance.
      spinWait(2);

      // Record our second sample.  The monitor should notice that our frame rate is too low.
      scene.render();

      expect(monitor.lastFramesPerSecond).toBeLessThan(1000);
      expect(spyListener).toHaveBeenCalled();
    });

    it("does not monitor frame rate while paused", function () {
      monitor = new FrameRateMonitor({
        scene: scene,
        quietPeriod: 0.001,
        warmupPeriod: 0.001,
        samplingWindow: 0.001,
        minimumFrameRateDuringWarmup: 1000,
        minimumFrameRateAfterWarmup: 1000,
      });

      var spyListener = jasmine.createSpy("listener");
      monitor.lowFrameRate.addEventListener(spyListener);

      // Rendering once starts the quiet period
      scene.render();

      // Wait until we're well past the end of the quiet period.
      spinWait(2);

      // Rendering again records our first sample.
      scene.render();

      monitor.pause();

      // Wait well over a millisecond, which is the maximum frame time allowed by this instance.
      spinWait(2);

      // Record our second sample.  The monitor would notice that our frame rate is too low,
      // but it's paused.
      scene.render();

      monitor.unpause();

      scene.render();

      expect(spyListener).not.toHaveBeenCalled();
    });

    it("pausing multiple times requires unpausing multiple times", function () {
      monitor = new FrameRateMonitor({
        scene: scene,
        quietPeriod: 0.001,
        warmupPeriod: 0.001,
        samplingWindow: 0.001,
        minimumFrameRateDuringWarmup: 1000,
        minimumFrameRateAfterWarmup: 1000,
      });

      var spyListener = jasmine.createSpy("listener");
      monitor.lowFrameRate.addEventListener(spyListener);

      monitor.pause();
      monitor.pause();
      monitor.unpause();

      // Rendering once starts the quiet period
      scene.render();

      // Wait until we're well past the end of the quiet period.
      spinWait(2);

      // Rendering again records our first sample.
      scene.render();

      // Wait well over a millisecond, which is the maximum frame time allowed by this instance.
      spinWait(2);

      // Record our second sample.  The monitor would notice that our frame rate is too low,
      // but it's paused.
      scene.render();

      monitor.unpause();

      scene.render();

      expect(spyListener).not.toHaveBeenCalled();
    });

    it("does not report a low frame rate during the quiet period", function () {
      monitor = new FrameRateMonitor({
        scene: scene,
        quietPeriod: 1.0,
        warmupPeriod: 0.001,
        samplingWindow: 0.001,
        minimumFrameRateDuringWarmup: 1000,
        minimumFrameRateAfterWarmup: 1000,
      });

      var spyListener = jasmine.createSpy("listener");
      monitor.lowFrameRate.addEventListener(spyListener);

      // Rendering once starts the quiet period
      scene.render();

      // Wait well over a millisecond, which is the maximum frame time allowed by this instance.
      spinWait(2);

      // Render again.  Even though our frame rate is too low, the monitor shouldn't raise the event because we're in the quiet period.
      scene.render();

      expect(spyListener).not.toHaveBeenCalled();
    });

    it("the nominalFrameRate event is raised after the warmup period if the frame rate returns to nominal", function () {
      monitor = new FrameRateMonitor({
        scene: scene,
        quietPeriod: 0.001,
        warmupPeriod: 0.001,
        samplingWindow: 0.001,
        minimumFrameRateDuringWarmup: 10,
        minimumFrameRateAfterWarmup: 10,
      });

      var lowListener = jasmine.createSpy("lowFrameRate");
      monitor.lowFrameRate.addEventListener(lowListener);

      var nominalListener = jasmine.createSpy("nominalFrameRate");
      monitor.nominalFrameRate.addEventListener(nominalListener);

      // Rendering once starts the quiet period
      scene.render();

      // Wait until we're well past the end of the quiet period.
      spinWait(2);

      // Rendering again records our first sample.
      scene.render();

      // Wait 120 millseconds, which is over the maximum frame time allowed by this instance.
      spinWait(120);

      // Record our second sample.  The monitor should notice that our frame rate is too low.
      scene.render();

      expect(monitor.lastFramesPerSecond).toBeLessThan(10);
      expect(lowListener).toHaveBeenCalled();

      // Render as fast as possible for a samplingWindow, quietPeriod, and warmupPeriod.
      var endTime = getTimestamp() + 50;
      while (getTimestamp() < endTime) {
        scene.render();
      }

      // The nominalFrameRate event should have been raised.
      expect(monitor.lastFramesPerSecond).toBeGreaterThanOrEqualTo(10);
      expect(nominalListener).toHaveBeenCalled();
    });
  },
  "WebGL"
);