import { Clock } from "../../Source/Cesium.js";
import { ClockRange } from "../../Source/Cesium.js";
import { ClockStep } from "../../Source/Cesium.js";
import { defined } from "../../Source/Cesium.js";
import { JulianDate } from "../../Source/Cesium.js";

describe("Core/Clock", function () {
  it("sets default parameters when constructed", function () {
    var clock = new Clock();
    expect(clock.stopTime).toEqual(
      JulianDate.addDays(clock.startTime, 1, new JulianDate())
    );
    expect(clock.startTime).toEqual(clock.currentTime);
    expect(clock.multiplier).toEqual(1.0);
    expect(clock.clockStep).toEqual(ClockStep.SYSTEM_CLOCK_MULTIPLIER);
    expect(clock.clockRange).toEqual(ClockRange.UNBOUNDED);
    expect(clock.canAnimate).toEqual(true);
    expect(clock.shouldAnimate).toEqual(false);
  });

  it("sets provided constructor parameters correctly", function () {
    var start = new JulianDate(12);
    var stop = new JulianDate(112);
    var currentTime = new JulianDate(13);
    var step = ClockStep.TICK_DEPENDENT;
    var range = ClockRange.LOOP_STOP;
    var multiplier = 1.5;
    var clock = new Clock({
      startTime: start,
      stopTime: stop,
      currentTime: currentTime,
      clockStep: step,
      multiplier: multiplier,
      clockRange: range,
    });

    expect(clock.startTime).toEqual(start);
    expect(clock.startTime).not.toBe(start);
    expect(clock.stopTime).toEqual(stop);
    expect(clock.stopTime).not.toBe(stop);
    expect(clock.currentTime).toEqual(currentTime);
    expect(clock.currentTime).not.toBe(currentTime);
    expect(clock.clockStep).toEqual(step);
    expect(clock.clockRange).toEqual(range);
    expect(clock.multiplier).toEqual(multiplier);
    expect(clock.canAnimate).toEqual(true);
    expect(clock.shouldAnimate).toEqual(false);

    clock = new Clock({
      canAnimate: false,
    });
    expect(clock.canAnimate).toEqual(false);

    clock = new Clock({
      shouldAnimate: true,
    });
    expect(clock.shouldAnimate).toEqual(true);
  });

  it("works when constructed with no currentTime parameter", function () {
    var start = new JulianDate(12);
    var stop = new JulianDate(112);
    var step = ClockStep.TICK_DEPENDENT;
    var range = ClockRange.LOOP_STOP;
    var multiplier = 1.5;
    var clock = new Clock({
      startTime: start,
      stopTime: stop,
      clockStep: step,
      multiplier: multiplier,
      clockRange: range,
    });
    expect(clock.startTime).toEqual(start);
    expect(clock.stopTime).toEqual(stop);
    expect(clock.currentTime).toEqual(start);
    expect(clock.clockStep).toEqual(step);
    expect(clock.clockRange).toEqual(range);
    expect(clock.multiplier).toEqual(multiplier);
    expect(clock.canAnimate).toEqual(true);
    expect(clock.shouldAnimate).toEqual(false);
  });

  it("works when constructed with no startTime parameter", function () {
    var stop = new JulianDate(112);
    var currentTime = new JulianDate(13);
    var step = ClockStep.TICK_DEPENDENT;
    var range = ClockRange.LOOP_STOP;
    var multiplier = 1.5;
    var clock = new Clock({
      stopTime: stop,
      currentTime: currentTime,
      clockStep: step,
      multiplier: multiplier,
      clockRange: range,
    });
    expect(clock.startTime).toEqual(currentTime);
    expect(clock.stopTime).toEqual(stop);
    expect(clock.currentTime).toEqual(currentTime);
    expect(clock.clockStep).toEqual(step);
    expect(clock.clockRange).toEqual(range);
    expect(clock.multiplier).toEqual(multiplier);
    expect(clock.canAnimate).toEqual(true);
    expect(clock.shouldAnimate).toEqual(false);
  });

  it("works when constructed with no startTime or stopTime", function () {
    var currentTime = new JulianDate(12);
    var step = ClockStep.TICK_DEPENDENT;
    var range = ClockRange.LOOP_STOP;
    var multiplier = 1.5;
    var clock = new Clock({
      currentTime: currentTime,
      clockStep: step,
      multiplier: multiplier,
      clockRange: range,
    });
    var expectedStop = JulianDate.addDays(currentTime, 1.0, new JulianDate());
    expect(clock.startTime).toEqual(currentTime);
    expect(clock.stopTime).toEqual(expectedStop);
    expect(clock.currentTime).toEqual(currentTime);
    expect(clock.clockStep).toEqual(step);
    expect(clock.clockRange).toEqual(range);
    expect(clock.multiplier).toEqual(multiplier);
    expect(clock.canAnimate).toEqual(true);
    expect(clock.shouldAnimate).toEqual(false);
  });

  it("works when constructed with no startTime or currentTime", function () {
    var stop = new JulianDate(13);
    var step = ClockStep.TICK_DEPENDENT;
    var range = ClockRange.LOOP_STOP;
    var multiplier = 1.5;
    var clock = new Clock({
      stopTime: stop,
      clockStep: step,
      multiplier: multiplier,
      clockRange: range,
    });
    var expectedStart = JulianDate.addDays(stop, -1.0, new JulianDate());
    expect(clock.startTime).toEqual(expectedStart);
    expect(clock.stopTime).toEqual(stop);
    expect(clock.currentTime).toEqual(expectedStart);
    expect(clock.clockStep).toEqual(step);
    expect(clock.clockRange).toEqual(range);
    expect(clock.multiplier).toEqual(multiplier);
    expect(clock.canAnimate).toEqual(true);
    expect(clock.shouldAnimate).toEqual(false);
  });

  it("works when constructed with no currentTime or stopTime", function () {
    var start = new JulianDate(12);
    var step = ClockStep.TICK_DEPENDENT;
    var range = ClockRange.LOOP_STOP;
    var multiplier = 1.5;
    var clock = new Clock({
      startTime: start,
      clockStep: step,
      multiplier: multiplier,
      clockRange: range,
    });
    var expectedStop = JulianDate.addDays(start, 1.0, new JulianDate());
    expect(clock.startTime).toEqual(start);
    expect(clock.stopTime).toEqual(expectedStop);
    expect(clock.currentTime).toEqual(start);
    expect(clock.clockStep).toEqual(step);
    expect(clock.clockRange).toEqual(range);
    expect(clock.multiplier).toEqual(multiplier);
    expect(clock.canAnimate).toEqual(true);
    expect(clock.shouldAnimate).toEqual(false);
  });

  it("works when constructed with no stopTime parameter", function () {
    var start = new JulianDate(12);
    var currentTime = new JulianDate(12);
    var step = ClockStep.TICK_DEPENDENT;
    var range = ClockRange.LOOP_STOP;
    var multiplier = 1.5;
    var clock = new Clock({
      startTime: start,
      currentTime: currentTime,
      clockStep: step,
      multiplier: multiplier,
      clockRange: range,
    });
    var expectedStop = JulianDate.addDays(start, 1.0, new JulianDate());
    expect(clock.startTime).toEqual(start);
    expect(clock.stopTime).toEqual(expectedStop);
    expect(clock.currentTime).toEqual(currentTime);
    expect(clock.clockStep).toEqual(step);
    expect(clock.clockRange).toEqual(range);
    expect(clock.multiplier).toEqual(multiplier);
    expect(clock.canAnimate).toEqual(true);
    expect(clock.shouldAnimate).toEqual(false);
  });

  it("throws when constructed if start time is after stop time", function () {
    var start = new JulianDate(1);
    var stop = new JulianDate(0);
    expect(function () {
      return new Clock({
        startTime: start,
        stopTime: stop,
      });
    }).toThrowDeveloperError();
  });

  it("animates forward in TICK_DEPENDENT mode", function () {
    var start = new JulianDate(0);
    var stop = new JulianDate(1);
    var currentTime = new JulianDate(0.5);
    var multiplier = 1.5;
    var clock = new Clock({
      startTime: start,
      stopTime: stop,
      currentTime: currentTime,
      clockStep: ClockStep.TICK_DEPENDENT,
      multiplier: multiplier,
      clockRange: ClockRange.LOOP_STOP,
      shouldAnimate: true,
    });
    expect(clock.currentTime).toEqual(currentTime);

    currentTime = JulianDate.addSeconds(
      currentTime,
      multiplier,
      new JulianDate()
    );
    expect(currentTime).toEqual(clock.tick());
    expect(clock.currentTime).toEqual(currentTime);

    currentTime = JulianDate.addSeconds(
      currentTime,
      multiplier,
      new JulianDate()
    );
    expect(currentTime).toEqual(clock.tick());
    expect(clock.currentTime).toEqual(currentTime);
  });

  it("animates backwards in TICK_DEPENDENT mode", function () {
    var start = new JulianDate(0);
    var stop = new JulianDate(1);
    var currentTime = new JulianDate(0.5);
    var multiplier = -1.5;
    var clock = new Clock({
      startTime: start,
      stopTime: stop,
      currentTime: currentTime,
      clockStep: ClockStep.TICK_DEPENDENT,
      multiplier: multiplier,
      clockRange: ClockRange.LOOP_STOP,
      shouldAnimate: true,
    });
    expect(clock.currentTime).toEqual(currentTime);

    currentTime = JulianDate.addSeconds(
      currentTime,
      multiplier,
      new JulianDate()
    );
    expect(currentTime).toEqual(clock.tick());
    expect(clock.currentTime).toEqual(currentTime);

    currentTime = JulianDate.addSeconds(
      currentTime,
      multiplier,
      new JulianDate()
    );
    expect(currentTime).toEqual(clock.tick());
    expect(clock.currentTime).toEqual(currentTime);
  });

  it("animates forwards past stop time in UNBOUNDED TICK_DEPENDENT mode", function () {
    var start = new JulianDate(0);
    var stop = new JulianDate(1);
    var currentTime = stop;
    var multiplier = 1.5;
    var clock = new Clock({
      startTime: start,
      stopTime: stop,
      currentTime: currentTime,
      clockStep: ClockStep.TICK_DEPENDENT,
      multiplier: multiplier,
      clockRange: ClockRange.UNBOUNDED,
      shouldAnimate: true,
    });
    expect(clock.currentTime).toEqual(currentTime);

    currentTime = JulianDate.addSeconds(
      currentTime,
      multiplier,
      new JulianDate()
    );
    expect(currentTime).toEqual(clock.tick());
    expect(clock.currentTime).toEqual(currentTime);

    currentTime = JulianDate.addSeconds(
      currentTime,
      multiplier,
      new JulianDate()
    );
    expect(currentTime).toEqual(clock.tick());
    expect(clock.currentTime).toEqual(currentTime);
  });

  it("animates backwards past start time in UNBOUNDED TICK_DEPENDENT mode", function () {
    var start = new JulianDate(0);
    var stop = new JulianDate(1);
    var currentTime = start;
    var multiplier = -1.5;
    var clock = new Clock({
      startTime: start,
      stopTime: stop,
      currentTime: currentTime,
      clockStep: ClockStep.TICK_DEPENDENT,
      multiplier: multiplier,
      clockRange: ClockRange.UNBOUNDED,
      shouldAnimate: true,
    });
    expect(clock.currentTime).toEqual(currentTime);

    currentTime = JulianDate.addSeconds(
      currentTime,
      multiplier,
      new JulianDate()
    );
    expect(currentTime).toEqual(clock.tick());
    expect(clock.currentTime).toEqual(currentTime);

    currentTime = JulianDate.addSeconds(
      currentTime,
      multiplier,
      new JulianDate()
    );
    expect(currentTime).toEqual(clock.tick());
    expect(clock.currentTime).toEqual(currentTime);
  });

  it("loops back to start time when animating forward past stop time in LOOP_STOP TICK_DEPENDENT mode", function () {
    var start = new JulianDate(0);
    var stop = new JulianDate(1);
    var currentTime = stop;
    var multiplier = 1.5;
    var clock = new Clock({
      startTime: start,
      stopTime: stop,
      currentTime: currentTime,
      clockStep: ClockStep.TICK_DEPENDENT,
      multiplier: multiplier,
      clockRange: ClockRange.LOOP_STOP,
      shouldAnimate: true,
    });
    expect(clock.currentTime).toEqual(currentTime);

    currentTime = JulianDate.addSeconds(start, multiplier, new JulianDate());
    expect(currentTime).toEqual(clock.tick());
    expect(clock.currentTime).toEqual(currentTime);

    currentTime = JulianDate.addSeconds(
      currentTime,
      multiplier,
      new JulianDate()
    );
    expect(currentTime).toEqual(clock.tick());
    expect(clock.currentTime).toEqual(currentTime);
  });

  it("stops at start when animating backwards past start time in LOOP_STOP TICK_DEPENDENT mode", function () {
    var start = new JulianDate(0);
    var stop = new JulianDate(1);
    var currentTime = start;
    var multiplier = -100.0;
    var clock = new Clock({
      startTime: start,
      stopTime: stop,
      currentTime: currentTime,
      clockStep: ClockStep.TICK_DEPENDENT,
      multiplier: multiplier,
      clockRange: ClockRange.LOOP_STOP,
      shouldAnimate: true,
    });

    expect(clock.currentTime).toEqual(currentTime);
    expect(start).toEqual(clock.tick());
    expect(start).toEqual(clock.currentTime);
  });

  it("stops at stop time when animating forwards past stop time in CLAMPED TICK_DEPENDENT mode", function () {
    var start = new JulianDate(0);
    var stop = new JulianDate(1);
    var currentTime = stop;
    var multiplier = 100.0;
    var clock = new Clock({
      startTime: start,
      stopTime: stop,
      currentTime: currentTime,
      clockStep: ClockStep.TICK_DEPENDENT,
      multiplier: multiplier,
      clockRange: ClockRange.CLAMPED,
      shouldAnimate: true,
    });

    expect(clock.currentTime).toEqual(currentTime);
    expect(stop).toEqual(clock.tick());
    expect(stop).toEqual(clock.currentTime);
  });

  it("stops at start time when animating backwards past start time in CLAMPED TICK_DEPENDENT mode", function () {
    var start = new JulianDate(0);
    var stop = new JulianDate(1);
    var currentTime = start;
    var multiplier = -100.0;
    var clock = new Clock({
      startTime: start,
      stopTime: stop,
      currentTime: currentTime,
      clockStep: ClockStep.TICK_DEPENDENT,
      multiplier: multiplier,
      clockRange: ClockRange.CLAMPED,
      shouldAnimate: true,
    });

    expect(clock.currentTime).toEqual(currentTime);
    expect(start).toEqual(clock.tick());
    expect(start).toEqual(clock.currentTime);
  });

  it("fires onEnd event when endTime is reached and clock range is CLAMPED", function () {
    var start = new JulianDate(0);
    var stop = new JulianDate(1);
    var currentTime = stop;
    var multiplier = 100.0;
    var clock = new Clock({
      startTime: start,
      stopTime: stop,
      currentTime: currentTime,
      clockStep: ClockStep.TICK_DEPENDENT,
      multiplier: multiplier,
      clockRange: ClockRange.CLAMPED,
      shouldAnimate: true,
    });
    var onStopSpy = jasmine.createSpy("event");
    clock.onStop.addEventListener(onStopSpy);
    clock.tick();
    expect(onStopSpy).toHaveBeenCalled();
  });

  it("fires onEnd event when endTime is reached and clock range is LOOP_STOP", function () {
    var start = new JulianDate(0);
    var stop = new JulianDate(1);
    var currentTime = stop;
    var multiplier = 100.0;
    var clock = new Clock({
      startTime: start,
      stopTime: stop,
      currentTime: currentTime,
      clockStep: ClockStep.TICK_DEPENDENT,
      multiplier: multiplier,
      clockRange: ClockRange.LOOP_STOP,
      shouldAnimate: true,
    });
    var onStopSpy = jasmine.createSpy("event");
    clock.onStop.addEventListener(onStopSpy);
    clock.tick();
    expect(onStopSpy).toHaveBeenCalled();
  });

  describe("SYSTEM_CLOCK modes", function () {
    var baseDate = new Date(2016, 6, 7);

    beforeEach(function () {
      jasmine.clock().install();
      jasmine.clock().mockDate(baseDate);

      if (typeof performance !== "undefined" && defined(performance.now)) {
        spyOn(performance, "now").and.callFake(function () {
          return Date.now();
        });
      }
    });

    afterEach(function () {
      jasmine.clock().uninstall();
    });

    it("uses current time in SYSTEM_CLOCK mode (real-time mode)", function () {
      var clock = new Clock({
        clockStep: ClockStep.SYSTEM_CLOCK,
      });

      expect(clock.currentTime).toEqual(JulianDate.fromDate(baseDate));
      expect(clock.multiplier).toEqual(1.0);
      expect(clock.shouldAnimate).toEqual(true);

      var time1 = clock.tick();
      expect(time1).toEqual(JulianDate.fromDate(baseDate));

      jasmine.clock().tick(1000);

      var time2 = clock.tick();
      expect(time2).toEqual(
        JulianDate.addSeconds(
          JulianDate.fromDate(baseDate),
          1.0,
          new JulianDate()
        )
      );
    });

    it("switches out of SYSTEM_CLOCK mode when changing currentTime", function () {
      var clock = new Clock({
        clockStep: ClockStep.SYSTEM_CLOCK,
      });
      expect(clock.clockStep).toEqual(ClockStep.SYSTEM_CLOCK);

      clock.currentTime = clock.currentTime;
      expect(clock.clockStep).toEqual(ClockStep.SYSTEM_CLOCK);

      clock.currentTime = new JulianDate(1);
      expect(clock.clockStep).toEqual(ClockStep.SYSTEM_CLOCK_MULTIPLIER);
    });

    it("switches out of SYSTEM_CLOCK mode when changing multiplier", function () {
      var clock = new Clock({
        clockStep: ClockStep.SYSTEM_CLOCK,
      });
      expect(clock.clockStep).toEqual(ClockStep.SYSTEM_CLOCK);

      clock.multiplier = clock.multiplier;
      expect(clock.clockStep).toEqual(ClockStep.SYSTEM_CLOCK);

      clock.multiplier = 1.5;
      expect(clock.clockStep).toEqual(ClockStep.SYSTEM_CLOCK_MULTIPLIER);
    });

    it("switches out of SYSTEM_CLOCK mode when changing shouldAnimate", function () {
      var clock = new Clock({
        clockStep: ClockStep.SYSTEM_CLOCK,
      });
      expect(clock.clockStep).toEqual(ClockStep.SYSTEM_CLOCK);

      clock.shouldAnimate = clock.shouldAnimate;
      expect(clock.clockStep).toEqual(ClockStep.SYSTEM_CLOCK);

      clock.shouldAnimate = false;
      expect(clock.clockStep).toEqual(ClockStep.SYSTEM_CLOCK_MULTIPLIER);
    });

    it("sets currentTime, multiplier and shouldAnimate when switching to SYSTEM_CLOCK mode", function () {
      var clock = new Clock({
        currentTime: new JulianDate(1),
        clockStep: ClockStep.SYSTEM_CLOCK_MULTIPLIER,
        multiplier: 1.5,
      });

      clock.clockStep = ClockStep.SYSTEM_CLOCK;
      expect(clock.clockStep).toEqual(ClockStep.SYSTEM_CLOCK);
      expect(clock.currentTime).toEqual(JulianDate.fromDate(baseDate));
      expect(clock.multiplier).toEqual(1.0);
      expect(clock.shouldAnimate).toEqual(true);
    });

    it("stays in SYSTEM_CLOCK mode when changing other unrelated properties", function () {
      var clock = new Clock({
        clockStep: ClockStep.SYSTEM_CLOCK,
      });

      clock.startTime = new JulianDate(1);
      expect(clock.clockStep).toEqual(ClockStep.SYSTEM_CLOCK);

      clock.stopTime = new JulianDate(2);
      expect(clock.clockStep).toEqual(ClockStep.SYSTEM_CLOCK);

      clock.clockRange = ClockRange.CLAMP;
      expect(clock.clockStep).toEqual(ClockStep.SYSTEM_CLOCK);

      clock.canAnimate = false;
      expect(clock.clockStep).toEqual(ClockStep.SYSTEM_CLOCK);
    });

    it("uses multiplier in SYSTEM_CLOCK_MULTIPLIER mode", function () {
      var clock = new Clock({
        clockStep: ClockStep.SYSTEM_CLOCK_MULTIPLIER,
        multiplier: 2.0,
        shouldAnimate: true,
      });

      var time1 = clock.tick();
      expect(time1).toEqual(JulianDate.fromDate(baseDate));

      jasmine.clock().tick(1000);

      var time2 = clock.tick();
      expect(time2).toEqual(
        JulianDate.addSeconds(
          JulianDate.fromDate(baseDate),
          2.0,
          new JulianDate()
        )
      );
    });

    it("does not advance if shouldAnimate is false and does advance if true", function () {
      var start = JulianDate.fromDate(baseDate);

      var clock = new Clock({
        startTime: start,
        clockStep: ClockStep.SYSTEM_CLOCK_MULTIPLIER,
      });

      expect(clock.currentTime).toEqual(start);
      clock.shouldAnimate = false;

      jasmine.clock().tick(1000);

      var time1 = clock.tick();
      expect(time1).toEqual(start);
      expect(clock.currentTime).toEqual(start);

      clock.shouldAnimate = true;

      jasmine.clock().tick(1000);

      time1 = clock.tick();

      expect(time1).toEqual(
        JulianDate.addSeconds(
          JulianDate.fromDate(baseDate),
          1.0,
          new JulianDate()
        )
      );

      jasmine.clock().tick(1000);

      var time2 = clock.tick();

      expect(time2).toEqual(
        JulianDate.addSeconds(
          JulianDate.fromDate(baseDate),
          2.0,
          new JulianDate()
        )
      );

      clock.currentTime = start;
      clock.clockStep = ClockStep.TICK_DEPENDENT;

      clock.shouldAnimate = false;

      time1 = clock.tick();
      expect(time1).toEqual(start);
      expect(clock.currentTime).toEqual(start);

      clock.shouldAnimate = true;
      time1 = clock.tick();

      expect(time1).toEqual(
        JulianDate.addSeconds(
          JulianDate.fromDate(baseDate),
          1.0,
          new JulianDate()
        )
      );
    });
  });
});