/* eslint-env qunit */
import EventTarget from '../../src/js/event-target.js';
import VolumeControl from '../../src/js/control-bar/volume-control/volume-control.js';
import MuteToggle from '../../src/js/control-bar/mute-toggle.js';
import VolumeBar from '../../src/js/control-bar/volume-control/volume-bar.js';
import PlayToggle from '../../src/js/control-bar/play-toggle.js';
import PlaybackRateMenuButton from '../../src/js/control-bar/playback-rate-menu/playback-rate-menu-button.js';
import Slider from '../../src/js/slider/slider.js';
import PictureInPictureToggle from '../../src/js/control-bar/picture-in-picture-toggle.js';
import FullscreenToggle from '../../src/js/control-bar/fullscreen-toggle.js';
import ControlBar from '../../src/js/control-bar/control-bar.js';
import SeekBar from '../../src/js/control-bar/progress-control/seek-bar.js';
import RemainingTimeDisplay from '../../src/js/control-bar/time-controls/remaining-time-display.js';
import TestHelpers from './test-helpers.js';
import document from 'global/document';
import window from 'global/window';
import sinon from 'sinon';

QUnit.module('Controls', {
  beforeEach(assert) {
    this.clock = sinon.useFakeTimers();
  },
  afterEach(assert) {
    this.clock.restore();
  }
});

QUnit.test('should hide volume and mute toggle control if it\'s not supported', function(assert) {
  assert.expect(2);

  const player = TestHelpers.makePlayer();

  player.tech_.featuresVolumeControl = false;
  player.tech_.featuresMuteControl = false;

  const volumeControl = new VolumeControl(player);
  const muteToggle = new MuteToggle(player);

  assert.ok(volumeControl.hasClass('vjs-hidden'), 'volumeControl is not hidden');
  assert.ok(muteToggle.hasClass('vjs-hidden'), 'muteToggle is not hidden');

  player.dispose();
  volumeControl.dispose();
  muteToggle.dispose();
});

QUnit.test('should show replay icon when video playback ended', function(assert) {
  assert.expect(1);

  const player = TestHelpers.makePlayer();

  const playToggle = new PlayToggle(player);

  player.trigger('ended');

  assert.ok(playToggle.hasClass('vjs-ended'), 'playToogle is in the ended state');

  player.dispose();
  playToggle.dispose();
});

QUnit.test('should show replay icon when video playback ended and replay option is set to true', function(assert) {
  assert.expect(1);

  const player = TestHelpers.makePlayer();

  const playToggle = new PlayToggle(player, {replay: true});

  player.trigger('ended');

  assert.ok(playToggle.hasClass('vjs-ended'), 'playToogle is in the ended state');

  player.dispose();
  playToggle.dispose();
});

QUnit.test('should not show the replay icon when video playback ended', function(assert) {
  assert.expect(1);

  const player = TestHelpers.makePlayer();

  const playToggle = new PlayToggle(player, {replay: false});

  player.trigger('ended');

  assert.equal(playToggle.hasClass('vjs-ended'), false, 'playToogle is not in the ended state');

  player.dispose();
  playToggle.dispose();
});

QUnit.test('should test and toggle volume control on `loadstart`', function(assert) {
  const player = TestHelpers.makePlayer();

  player.tech_.featuresVolumeControl = true;
  player.tech_.featuresMuteControl = true;

  const volumeControl = new VolumeControl(player);
  const muteToggle = new MuteToggle(player);

  assert.equal(volumeControl.hasClass('vjs-hidden'), false, 'volumeControl is hidden initially');
  assert.equal(muteToggle.hasClass('vjs-hidden'), false, 'muteToggle is hidden initially');

  player.tech_.featuresVolumeControl = false;
  player.tech_.featuresMuteControl = false;
  player.trigger('loadstart');

  assert.equal(volumeControl.hasClass('vjs-hidden'), true, 'volumeControl does not hide itself');
  assert.equal(muteToggle.hasClass('vjs-hidden'), true, 'muteToggle does not hide itself');

  player.tech_.featuresVolumeControl = true;
  player.tech_.featuresMuteControl = true;
  player.trigger('loadstart');

  assert.equal(volumeControl.hasClass('vjs-hidden'), false, 'volumeControl does not show itself');
  assert.equal(muteToggle.hasClass('vjs-hidden'), false, 'muteToggle does not show itself');

  player.dispose();
  volumeControl.dispose();
  muteToggle.dispose();
});

QUnit.test('calculateDistance should use changedTouches, if available', function(assert) {
  const player = TestHelpers.makePlayer();

  player.tech_.featuresVolumeControl = true;

  const slider = new Slider(player);

  document.body.appendChild(slider.el_);
  slider.el_.style.position = 'absolute';
  slider.el_.style.width = '200px';
  slider.el_.style.left = '0px';

  const event = {
    pageX: 10,
    changedTouches: [{
      pageX: 100
    }]
  };

  assert.equal(slider.calculateDistance(event), 0.5, 'we should have touched exactly in the center, so, the ratio should be half');

  player.dispose();
  slider.dispose();
});

QUnit.test("SeekBar doesn't set scrubbing on mouse down, only on mouse move", function(assert) {
  const player = TestHelpers.makePlayer();
  const scrubbingSpy = sinon.spy(player, 'scrubbing');
  const seekBar = new SeekBar(player);
  const doc = new EventTarget();

  player.duration(0);

  // mousemove is listened to on the document.
  // Specifically, we check the ownerDocument of the seekBar's bar.
  // Therefore, we want to mock it out to be able to trigger mousemove
  seekBar.bar.dispose();
  seekBar.bar.el_ = new EventTarget();
  seekBar.bar.el_.ownerDocument = doc;

  seekBar.trigger('mousedown');
  assert.ok(scrubbingSpy.calledWith(), 'called scrubbing as a getter');
  assert.notOk(scrubbingSpy.calledWith(true), 'did not set scrubbing true');

  player.scrubbing(false);

  scrubbingSpy.resetHistory();

  doc.trigger('mousemove');
  assert.ok(scrubbingSpy.calledWith(), 'called scrubbing as a getter');
  assert.ok(scrubbingSpy.calledWith(true), 'did set scrubbing true');

  seekBar.dispose();
  player.dispose();
});

QUnit.test('SeekBar should be filled on 100% when the video/audio ends', function(assert) {
  const player = TestHelpers.makePlayer();
  const seekBar = player.controlBar.progressControl.seekBar;
  const oldRAF = window.requestAnimationFrame;
  const oldCAF = window.cancelAnimationFrame;

  window.requestAnimationFrame = (fn) => window.setTimeout(fn, 1);
  window.cancelAnimationFrame = (id) => window.clearTimeout(id);

  player.triggerReady();
  player.duration(1.5);

  this.clock.tick(30);
  player.trigger('timeupdate');
  this.clock.tick(1);

  assert.equal(seekBar.duration_, 1.5, 'SeekBar duration should equal player duration');
  assert.equal(seekBar.currentTime_, 0, 'SeekBar current time should be zero on start');
  assert.equal(seekBar.getPercent(), 0, 'SeekBar percent should be zero on start');

  this.clock.tick(30);
  player.currentTime(0.75);
  player.trigger('timeupdate');
  this.clock.tick(1);

  assert.equal(seekBar.currentTime_, 0.75, 'SeekBar currentTime should equal player currentTime');
  assert.equal(seekBar.getPercent(), 0.5, 'SeekBar percent equal to 50%');

  this.clock.tick(30);
  player.currentTime(1.495);
  player.trigger('timeupdate');
  this.clock.tick(1);
  player.currentTime(1.5);
  // The following 'timeupdate' should be wiped out by the throttle function!
  player.trigger('timeupdate');
  // The following 'ended' shouldn't be wiped out by the throttle function!
  player.trigger('ended');
  this.clock.tick(1);

  assert.equal(seekBar.currentTime_, 1.5, 'SeekBar currentTime should equal player currentTime');
  assert.equal(seekBar.getPercent(), 1, 'SeekBar percent equal to 100%');
  player.dispose();

  window.requestAnimationFrame = oldRAF;
  window.cancelAnimationFrame = oldCAF;
});

QUnit.test('playback rate button is hidden by default', function(assert) {
  assert.expect(1);

  const player = TestHelpers.makePlayer();
  const playbackRate = new PlaybackRateMenuButton(player);

  assert.ok(playbackRate.el().className.indexOf('vjs-hidden') >= 0, 'playbackRate is hidden');

  player.dispose();
  playbackRate.dispose();
});

QUnit.test('playback rate button is not hidden if playback rates are set', function(assert) {
  assert.expect(1);

  const player = TestHelpers.makePlayer({
    playbackRates: [1, 2, 3]
  });
  const playbackRate = new PlaybackRateMenuButton(player);

  assert.ok(playbackRate.el().className.indexOf('vjs-hidden') === -1, 'playbackRate is not hidden');

  player.dispose();
  playbackRate.dispose();
});

QUnit.test('should show or hide playback rate menu button on playback rates change', function(assert) {
  const rates = [1, 2, 3];
  const norates = [];
  let playbackRatesReturnValue = rates;
  const player = TestHelpers.makePlayer();

  player.playbackRates = () => playbackRatesReturnValue;

  const playbackRate = new PlaybackRateMenuButton(player);

  assert.ok(playbackRate.el().className.indexOf('vjs-hidden') === -1, 'playbackRate is not hidden');

  playbackRatesReturnValue = norates;

  player.trigger('playbackrateschange');

  assert.ok(playbackRate.el().className.indexOf('vjs-hidden') >= 0, 'playbackRate is hidden');

  player.dispose();
  playbackRate.dispose();
});

QUnit.test('Picture-in-Picture control text should be correct when enterpictureinpicture and leavepictureinpicture are triggered', function(assert) {
  const player = TestHelpers.makePlayer();
  const pictureInPictureToggle = new PictureInPictureToggle(player);

  player.isInPictureInPicture(true);
  player.trigger('enterpictureinpicture');
  assert.equal(pictureInPictureToggle.controlText(), 'Exit Picture-in-Picture', 'Control Text is correct while switching to Picture-in-Picture mode');

  player.isInPictureInPicture(false);
  player.trigger('leavepictureinpicture');
  assert.equal(pictureInPictureToggle.controlText(), 'Picture-in-Picture', 'Control Text is correct while switching back to normal mode');

  player.dispose();
  pictureInPictureToggle.dispose();
});

QUnit.test('Picture-in-Picture control enabled property value should be correct when enterpictureinpicture and leavepictureinpicture are triggered', function(assert) {
  const player = TestHelpers.makePlayer();
  const pictureInPictureToggle = new PictureInPictureToggle(player);

  assert.equal(pictureInPictureToggle.enabled_, false, 'pictureInPictureToggle button should be disabled after creation');

  if ('pictureInPictureEnabled' in document && player.disablePictureInPicture() === false) {
    player.isInPictureInPicture(true);
    player.trigger('enterpictureinpicture');
    assert.equal(pictureInPictureToggle.enabled_, true, 'pictureInPictureToggle button should be enabled after triggering an enterpictureinpicture event');

    player.isInPictureInPicture(false);
    player.trigger('leavepictureinpicture');
    assert.equal(pictureInPictureToggle.enabled_, true, 'pictureInPictureToggle button should be enabled after triggering an leavepictureinpicture event');
  } else {
    player.isInPictureInPicture(true);
    player.trigger('enterpictureinpicture');
    assert.equal(pictureInPictureToggle.enabled_, false, 'pictureInPictureToggle button should be disabled after triggering an enterpictureinpicture event');

    player.isInPictureInPicture(false);
    player.trigger('leavepictureinpicture');
    assert.equal(pictureInPictureToggle.enabled_, false, 'pictureInPictureToggle button should be disabled after triggering an leavepictureinpicture event');
  }

  player.dispose();
  pictureInPictureToggle.dispose();
});

QUnit.test('Picture-in-Picture control enabled property value should be correct when loadedmetadata is triggered', function(assert) {
  const player = TestHelpers.makePlayer();
  const pictureInPictureToggle = new PictureInPictureToggle(player);

  assert.equal(pictureInPictureToggle.enabled_, false, 'pictureInPictureToggle button should be disabled after creation');

  if ('pictureInPictureEnabled' in document && player.disablePictureInPicture() === false) {
    player.trigger('loadedmetadata');
    assert.equal(pictureInPictureToggle.enabled_, true, 'pictureInPictureToggle button should be enabled after triggering an loadedmetadata event');
  } else {
    player.trigger('loadedmetadata');
    assert.equal(pictureInPictureToggle.enabled_, false, 'pictureInPictureToggle button should be disabled after triggering an loadedmetadata event');
  }

  player.dispose();
  pictureInPictureToggle.dispose();
});

QUnit.test('Picture-in-Picture control is hidden when the source is audio', function(assert) {
  const player = TestHelpers.makePlayer({});
  const pictureInPictureToggle = new PictureInPictureToggle(player);

  player.src({src: 'example.mp4', type: 'video/mp4'});
  player.trigger('loadedmetadata');

  if (document.exitPictureInPicture) {
    assert.notOk(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is not hidden initially');
  } else {
    assert.ok(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is hidden if PiP is not supported');
  }

  player.src({src: 'example1.mp3', type: 'audio/mp3'});
  player.trigger('loadedmetadata');
  assert.ok(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is hidden whenh the source is audio');

  player.dispose();
  pictureInPictureToggle.dispose();
});

QUnit.test('Picture-in-Picture control is displayed if docPiP is enabled', function(assert) {
  const player = TestHelpers.makePlayer({
    disablePictureInPicture: true,
    enableDocumentPictureInPicture: true
  });
  const pictureInPictureToggle = new PictureInPictureToggle(player);
  const testPiPObj = {};

  if (!window.documentPictureInPicture) {
    window.documentPictureInPicture = testPiPObj;
  }

  player.src({src: 'example.mp4', type: 'video/mp4'});
  player.trigger('loadedmetadata');

  if (document.exitPictureInPicture) {
    assert.notOk(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is not hidden');
  } else {
    assert.ok(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is hidden if PiP is not supported');
  }

  player.dispose();
  pictureInPictureToggle.dispose();
  if (window.documentPictureInPicture === testPiPObj) {
    delete window.documentPictureInPicture;
  }
});

QUnit.test('Picture-in-Picture control should only be displayed if the browser supports it', function(assert) {
  const player = TestHelpers.makePlayer();
  const pictureInPictureToggle = new PictureInPictureToggle(player);

  player.trigger('loadedmetadata');

  if (document.exitPictureInPicture) {
    // Browser that does support PiP
    assert.false(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is not hidden');
  } else {
    // Browser that does not support PiP
    assert.true(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is hidden');
  }

  player.dispose();
  pictureInPictureToggle.dispose();
});

QUnit.test('Fullscreen control text should be correct when fullscreenchange is triggered', function(assert) {
  const player = TestHelpers.makePlayer({controlBar: false});
  const fullscreentoggle = new FullscreenToggle(player);

  // make the fullscreenchange handler doesn't trigger
  player.off(player.fsApi_.fullscreenchange, player.boundDocumentFullscreenChange_);

  player.isFullscreen(true);
  player.trigger('fullscreenchange');
  assert.equal(fullscreentoggle.controlText(), 'Exit Fullscreen', 'Control Text is correct while switching to fullscreen mode');

  player.isFullscreen(false);
  player.trigger('fullscreenchange');
  assert.equal(fullscreentoggle.controlText(), 'Fullscreen', 'Control Text is correct while switching back to normal mode');

  player.dispose();
  fullscreentoggle.dispose();
});

QUnit.test('Clicking MuteToggle when volume is above 0 should toggle muted property and not change volume', function(assert) {
  const player = TestHelpers.makePlayer({ techOrder: ['html5'] });
  const muteToggle = new MuteToggle(player);

  assert.equal(player.volume(), 1, 'volume is above 0');
  assert.equal(player.muted(), false, 'player is not muted');

  muteToggle.handleClick();

  assert.equal(player.volume(), 1, 'volume is same');
  assert.equal(player.muted(), true, 'player is muted');

  player.dispose();
  muteToggle.dispose();
});

QUnit.test('Clicking MuteToggle when volume is 0 and muted is false should set volume to lastVolume and keep muted false', function(assert) {
  const player = TestHelpers.makePlayer({ techOrder: ['html5'] });
  const muteToggle = new MuteToggle(player);

  player.volume(0);
  assert.equal(player.lastVolume_(), 1, 'lastVolume is set');
  assert.equal(player.muted(), false, 'player is muted');

  muteToggle.handleClick();

  assert.equal(player.volume(), 1, 'volume is set to lastVolume');
  assert.equal(player.muted(), false, 'muted remains false');

  player.dispose();
  muteToggle.dispose();
});

QUnit.test('Clicking MuteToggle when volume is 0 and muted is true should set volume to lastVolume and sets muted to false', function(assert) {
  const player = TestHelpers.makePlayer({ techOrder: ['html5'] });
  const muteToggle = new MuteToggle(player);

  player.volume(0);
  player.muted(true);
  player.lastVolume_(0.5);

  muteToggle.handleClick();

  assert.equal(player.volume(), 0.5, 'volume is set to lastVolume');
  assert.equal(player.muted(), false, 'muted is set to false');

  player.dispose();
  muteToggle.dispose();
});

QUnit.test('Clicking MuteToggle when volume is 0, lastVolume is less than 0.1, and muted is true sets volume to 0.1 and muted to false', function(assert) {
  const player = TestHelpers.makePlayer({ techOrder: ['html5'] });
  const muteToggle = new MuteToggle(player);

  player.volume(0);
  player.muted(true);
  player.lastVolume_(0.05);

  muteToggle.handleClick();

  // `Number.prototype.toFixed()` is used here to circumvent rounding issues
  assert.equal(player.volume().toFixed(1), (0.1).toFixed(1), 'since lastVolume is less than 0.1, volume is set to 0.1');
  assert.equal(player.muted(), false, 'muted is set to false');

  player.dispose();
  muteToggle.dispose();
});

QUnit.test('ARIA value of VolumeBar should start at 100', function(assert) {
  const player = TestHelpers.makePlayer({ techOrder: ['html5'] });
  const volumeBar = new VolumeBar(player);

  this.clock.tick(1);

  assert.equal(volumeBar.el_.getAttribute('aria-valuenow'), 100, 'ARIA value of VolumeBar is 100');

  player.dispose();
  volumeBar.dispose();
});

QUnit.test('Muting with MuteToggle should set ARIA value of VolumeBar to 0', function(assert) {
  const player = TestHelpers.makePlayer({ techOrder: ['html5'] });
  const volumeBar = new VolumeBar(player);
  const muteToggle = new MuteToggle(player);

  this.clock.tick(1);

  assert.equal(player.volume(), 1, 'Volume is 1');
  assert.equal(player.muted(), false, 'Muted is false');
  assert.equal(volumeBar.el_.getAttribute('aria-valuenow'), 100, 'ARIA value of VolumeBar is 100');

  muteToggle.handleClick();

  // Because `volumechange` is triggered asynchronously, it doesn't end up
  // getting fired on `player` in the test environment, so we run it
  // manually.
  player.trigger('volumechange');

  assert.equal(player.volume(), 1, 'Volume remains 1');
  assert.equal(player.muted(), true, 'Muted is true');
  assert.equal(volumeBar.el_.getAttribute('aria-valuenow'), 0, 'ARIA value of VolumeBar is 0');

  player.dispose();
  muteToggle.dispose();
  volumeBar.dispose();
});

QUnit.test('controlbar children to false individually, does not cause an assertion', function(assert) {
  const defaultChildren = ControlBar.prototype.options_.children;

  defaultChildren.forEach((childName) => {
    const options = {controlBar: {}};

    options.controlBar[childName] = false;

    const player = TestHelpers.makePlayer(options);

    this.clock.tick(1000);
    player.triggerReady();
    player.dispose();
    assert.ok(true, `${childName}: false. did not cause an assertion`);
  });
});

QUnit.test('all controlbar children to false, does not cause an assertion', function(assert) {
  const defaultChildren = ControlBar.prototype.options_.children;
  const options = {controlBar: {}};

  defaultChildren.forEach((childName) => {
    options.controlBar[childName] = false;
  });

  const player = TestHelpers.makePlayer(options);

  this.clock.tick(1000);
  player.triggerReady();
  player.dispose();
  assert.ok(true, 'did not cause an assertion');
});

QUnit.test('Remaing time negative sign can be optional', function(assert) {
  const player = TestHelpers.makePlayer({ techOrder: ['html5'] });

  const rtd1 = new RemainingTimeDisplay(player);
  const rtd2 = new RemainingTimeDisplay(player, {displayNegative: false});

  this.clock.tick(1);

  assert.ok(rtd1.el().textContent.indexOf('-') > 0, 'Value is negative by default');
  assert.equal(rtd2.el().textContent.indexOf('-'), -1, 'Value is positive with option');

  rtd1.dispose();
  rtd2.dispose();
  player.dispose();
});