/* eslint-env qunit */
import TestHelpers from './test-helpers.js';
import sinon from 'sinon';
import window from 'global/window';
import * as middleware from '../../src/js/tech/middleware.js';
import {merge} from '../../src/js/utils/obj';

const middleWareTerminations = ['terminates', 'does not-terminate'];
const playReturnValues = ['non-promise', 'promise'];

const mainModule = function(playReturnValue, middlewareTermination, subhooks) {
  subhooks.beforeEach(function(assert) {
    this.clock = sinon.useFakeTimers();
    this.techPlayCalls = 0;
    this.playsTerminated = 0;
    this.playTests = [];
    this.terminate = false;

    if (middlewareTermination === 'terminates') {
      this.terminate = true;
    }
    this.techPlay = () => {
      this.techPlayCalls++;

      if (playReturnValue === 'promise') {
        return window.Promise.resolve('foo');
      }
      return 'foo';
    };

    this.finish = function() {
      const done = assert.async(this.playTests.length);

      const singleFinish = (playValue, assertName) => {
        assert.equal(playValue, 'foo', `play call from - ${assertName} - is correct`);
        done();
      };

      this.playTests.forEach(function(test) {
        const playRetval = test.playRetval;
        const testName = test.assertName;

        if (typeof playRetval === 'string') {
          singleFinish(playRetval, testName);
        } else {
          playRetval.then((v) => {
            singleFinish(v, testName);
          });
        }
      });
    };

    this.checkState = (assertName, options = {}) => {
      const expectedState = merge({
        playCalls: 0,
        techLoaded: false,
        techReady: false,
        playerReady: false,
        changingSrc: false,
        playsTerminated: 0
      }, options);

      if (typeof options.techLoaded === 'undefined' && typeof options.techReady !== 'undefined') {
        expectedState.techLoaded = options.techReady;
      }

      const currentState = {
        playCalls: this.techPlayCalls,
        techLoaded: Boolean(this.player.tech_),
        techReady: Boolean((this.player.tech_ || {}).isReady_),
        playerReady: Boolean(this.player.isReady_),
        changingSrc: Boolean(this.player.changingSrc_),
        playsTerminated: Number(this.playsTerminated)
      };

      assert.deepEqual(currentState, expectedState, assertName);
    };

    this.playTerminatedQueue = () => this.playsTerminated++;

    this.playTest = (assertName, options = {}) => {
      if (this.player.playTerminatedQueue_ !== this.playTerminatedQueue) {
        this.player.runPlayTerminatedQueue_ = this.playTerminatedQueue;
      }
      if (this.player && this.player.tech_ && this.player.tech_.play !== this.techPlay) {
        this.player.tech_.play = this.techPlay;
      }
      this.playTests.push({assertName, playRetval: this.player.play()});
      this.checkState(assertName, options);
    };

    this.middleware = () => {
      return {
        // pass along source
        setSource(srcObj, next) {
          next(null, srcObj);
        },
        callPlay: () => {
          if (this.terminate) {
            return middleware.TERMINATOR;
          }
        }
      };
    };

    middleware.use('*', this.middleware);
  });

  subhooks.afterEach(function() {
    // remove added middleware
    const middlewareList = middleware.getMiddleware('*');

    for (let i = 0; i < middlewareList.length; i++) {
      if (middlewareList[i] === this.middleware) {
        middlewareList.splice(i, 1);
      }
    }
    if (this.player) {
      this.player.dispose();
    }
    this.clock.restore();
  });

  QUnit.test('Player#play() resolves correctly with dom sources and async tech ready', function(assert) {
    // turn of mediaLoader to prevent setting a tech right away
    // similar to settings sources in the DOM
    // turn off autoReady to prevent synchronous ready from the tech
    this.player = TestHelpers.makePlayer({mediaLoader: false, techFaker: {autoReady: false}});

    this.playTest('before anything is ready');

    this.player.src({
      src: 'http://example.com/video.mp4',
      type: 'video/mp4'
    });

    this.playTest('only changingSrc', {
      changingSrc: true
    });

    this.clock.tick(1);

    this.playTest('still changingSrc, tech loaded', {
      techLoaded: true,
      changingSrc: true
    });

    this.player.tech_.triggerReady();
    this.playTest('still changingSrc, tech loaded and ready', {
      techReady: true,
      changingSrc: true
    });
    this.clock.tick(1);

    this.playTest('done changingSrc, tech/player ready', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 1,
      playsTerminated: this.terminate ? 1 : 0
    });

    this.clock.tick(1);

    this.checkState('state stays the same', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 1,
      playsTerminated: this.terminate ? 1 : 0
    });

    this.playTest('future calls hit tech#play directly, unless terminated', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 2,
      playsTerminated: this.terminate ? 2 : 0
    });

    if (this.terminate) {
      this.terminate = false;

      this.playTest('play works if not terminated', {
        playerReady: true,
        techReady: true,
        playCalls: 1,
        playsTerminated: 2
      });

      this.playTest('future calls hit tech#play directly', {
        playerReady: true,
        techReady: true,
        playCalls: 2,
        playsTerminated: 2
      });
    }

    this.finish(assert);
  });

  QUnit.test('Player#play() resolves correctly with dom sources', function(assert) {
    this.player = TestHelpers.makePlayer({mediaLoader: false});

    this.playTest('before anything is ready');

    this.player.src({
      src: 'http://example.com/video.mp4',
      type: 'video/mp4'
    });

    this.playTest('only changingSrc', {
      changingSrc: true
    });

    this.clock.tick(1);

    this.playTest('still changingSrc, tech/player ready', {
      techLoaded: true,
      changingSrc: true,
      playerReady: true,
      techReady: true
    });

    this.clock.tick(1);

    this.playTest('done changingSrc, tech#play is called', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 1,
      playsTerminated: this.terminate ? 1 : 0
    });

    this.clock.tick(1);

    this.checkState('state stays the same', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 1,
      playsTerminated: this.terminate ? 1 : 0
    });

    this.playTest('future calls hit tech#play directly', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 2,
      playsTerminated: this.terminate ? 2 : 0
    });

    if (this.terminate) {
      this.terminate = false;

      this.playTest('play works if not terminated', {
        playerReady: true,
        techReady: true,
        playCalls: 1,
        playsTerminated: 2
      });

      this.playTest('future calls hit tech#play directly', {
        playerReady: true,
        techReady: true,
        playCalls: 2,
        playsTerminated: 2
      });
    }

    this.finish(assert);
  });

  QUnit.test('Player#play() resolves correctly with async tech ready', function(assert) {
    this.player = TestHelpers.makePlayer({techFaker: {autoReady: false}});

    this.playTest('before anything is ready', {
      techLoaded: true
    });

    this.player.src({
      src: 'http://example.com/video.mp4',
      type: 'video/mp4'
    });

    this.playTest('tech loaded changingSrc', {
      techLoaded: true,
      changingSrc: true
    });

    this.clock.tick(1);

    this.playTest('still changingSrc, tech loaded', {
      techLoaded: true,
      changingSrc: true
    });

    this.clock.tick(1);
    this.playTest('still changingSrc, tech loaded again', {
      techLoaded: true,
      changingSrc: true
    });

    this.player.tech_.triggerReady();
    this.playTest('still changingSrc, tech loaded and ready', {
      techReady: true,
      changingSrc: true
    });
    this.clock.tick(1);

    this.playTest('still changingSrc tech/player ready', {
      changingSrc: true,
      playerReady: true,
      techReady: true
    });

    // player ready calls fire now
    // which sets changingSrc_ to false
    this.clock.tick(1);

    this.checkState('play was called on ready', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 1,
      playsTerminated: this.terminate ? 1 : 0
    });

    this.playTest('future calls hit tech#play directly', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 2,
      playsTerminated: this.terminate ? 2 : 0
    });

    if (this.terminate) {
      this.terminate = false;

      this.playTest('play works if not terminated', {
        playerReady: true,
        techReady: true,
        playCalls: 1,
        playsTerminated: 2
      });

      this.playTest('future calls hit tech#play directly', {
        playerReady: true,
        techReady: true,
        playCalls: 2,
        playsTerminated: 2
      });
    }

    this.finish(assert);
  });

  QUnit.test('Player#play() resolves correctly', function(assert) {
    this.player = TestHelpers.makePlayer();

    this.playTest('player/tech start out ready', {
      techReady: true,
      playerReady: true
    });

    this.player.src({
      src: 'http://example.com/video.mp4',
      type: 'video/mp4'
    });

    this.playTest('now changingSrc', {
      techReady: true,
      playerReady: true,
      changingSrc: true
    });

    this.clock.tick(1);

    this.playTest('done changingSrc, play called if not terminated', {
      techReady: true,
      playerReady: true,
      playCalls: this.terminate ? 0 : 1,
      playsTerminated: this.terminate ? 1 : 0
    });

    this.clock.tick(2);

    this.checkState('state stays the same', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 1,
      playsTerminated: this.terminate ? 1 : 0
    });

    this.playTest('future calls hit tech#play directly', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 2,
      playsTerminated: this.terminate ? 2 : 0
    });

    if (this.terminate) {
      this.terminate = false;

      this.playTest('play works if not terminated', {
        playerReady: true,
        techReady: true,
        playCalls: 1,
        playsTerminated: 2
      });

      this.playTest('future calls hit tech#play directly', {
        playerReady: true,
        techReady: true,
        playCalls: 2,
        playsTerminated: 2
      });
    }

    this.clock.tick(1000);

    this.finish(assert);
  });

  // without enableSourceset this test will fail.
  QUnit.test('Player#play() resolves correctly on tech el src', function(assert) {
    this.player = TestHelpers.makePlayer({techOrder: ['html5'], enableSourceset: true});

    this.playTest('player/tech start out ready', {
      techReady: true,
      playerReady: true
    });

    this.player.tech_.el_.src = 'http://vjs.zencdn.net/v/oceans.mp4';

    this.player.on('loadstart', () => {
      this.checkState('play should have been called', {
        techReady: true,
        playerReady: true,
        playCalls: 1
      });

      this.finish(assert);
    });
  });
};

QUnit.module('Player#play()', (hooks) => {
  playReturnValues.forEach((playReturnValue) => {
    middleWareTerminations.forEach((middlewareTermination) => {
      QUnit.module(`tech#play() => ${playReturnValue}, middleware ${middlewareTermination}`, (subhooks) => {
        mainModule(playReturnValue, middlewareTermination, subhooks);
      });
    });
  });
});
