/**
 * @license
 * Copyright Google Inc. All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */

import {IdleScheduler} from '../src/idle';
import {SwTestHarness, SwTestHarnessBuilder} from '../testing/scope';
import {async_beforeEach, async_fit, async_it} from './async';

(function() {
  // Skip environments that don't support the minimum APIs needed to run the SW tests.
  if (!SwTestHarness.envIsSupported()) {
    return;
  }

  describe('IdleScheduler', () => {
    let scope: SwTestHarness;
    let idle: IdleScheduler;

    beforeEach(() => {
      scope = new SwTestHarnessBuilder().build();
      idle = new IdleScheduler(scope, 1000, {
        log: (v, context) => console.error(v, context),
      });
    });

    // Validate that a single idle task executes when trigger()
    // is called and the idle timeout passes.
    async_it('executes scheduled work on time', async() => {
      // Set up a single idle task to set the completed flag to true when it runs.
      let completed: boolean = false;
      idle.schedule('work', async() => { completed = true; });

      // Simply scheduling the task should not cause it to execute.
      expect(completed).toEqual(false);

      // Trigger the idle mechanism. This returns a Promise that should resolve
      // once the idle timeout has passed.
      const trigger = idle.trigger();

      // Advance the clock beyond the idle timeout, causing the idle tasks to run.
      scope.advance(1100);

      // It should now be possible to wait for the trigger, and for the idle queue
      // to be empty.
      await trigger;
      await idle.empty;

      // The task should now have run.
      expect(completed).toEqual(true);
    });

    async_it('waits for multiple tasks to complete serially', async() => {
      // Schedule several tasks that will increase a counter according to its
      // current value. If these tasks execute in parallel, the writes to the counter
      // will race, and the test will fail.
      let counter: number = 2;
      idle.schedule('double counter', async() => {
        let local = counter;
        await Promise.resolve();
        local *= 2;
        await Promise.resolve();
        counter = local * 2;
      });
      idle.schedule('triple counter', async() => {
        // If this expect fails, it comes out of the 'await trigger' below.
        expect(counter).toEqual(8);

        // Multiply the counter by 3 twice.
        let local = counter;
        await Promise.resolve();
        local *= 3;
        await Promise.resolve();
        counter = local * 3;
      });

      // Trigger the idle mechanism once.
      const trigger = idle.trigger();

      // Advance the clock beyond the idle timeout, causing the idle tasks to run, and
      // wait for them to complete.
      scope.advance(1100);
      await trigger;
      await idle.empty;

      // Assert that both tasks executed in the correct serial sequence by validating
      // that the counter reached the correct value.
      expect(counter).toEqual(2 * 2 * 2 * 3 * 3);
    });

    // Validate that a single idle task does not execute until trigger() has been called
    // and sufficient time passes without it being called again.
    async_it('does not execute work until timeout passes with no triggers', async() => {
      // Set up a single idle task to set the completed flag to true when it runs.
      let completed: boolean = false;
      idle.schedule('work', async() => { completed = true; });

      // Trigger the queue once. This trigger will start a timer for the idle timeout,
      // but another trigger() will be called before that timeout passes.
      const firstTrigger = idle.trigger();

      // Advance the clock a little, but not enough to actually cause tasks to execute.
      scope.advance(500);

      // Assert that the task has not yet run.
      expect(completed).toEqual(false);

      // Next, trigger the queue again.
      const secondTrigger = idle.trigger();

      // Advance the clock beyond the timeout for the first trigger, but not the second.
      // This should cause the first trigger to resolve, but without running the task.
      scope.advance(600);
      await firstTrigger;
      expect(completed).toEqual(false);

      // Schedule a third trigger. This is the one that will eventually resolve the task.
      const thirdTrigger = idle.trigger();

      // Again, advance beyond the second trigger and verify it didn't resolve the task.
      scope.advance(500);
      await secondTrigger;
      expect(completed).toEqual(false);

      // Finally, advance beyond the third trigger, which should cause the task to be
      // executed finally.
      scope.advance(600);
      await thirdTrigger;
      await idle.empty;

      // The task should have executed.
      expect(completed).toEqual(true);
    });
  });
})();
