/* eslint-disable no-console */
import document from 'global/document';
import window from 'global/window';
import QUnit from 'qunit';
import videojs from 'video.js';

import 'videojs-playlist';
import '../src/plugin';

const playlist = [{
  name: 'Movie 1',
  description: 'Movie 1 description',
  duration: 100,
  data: {
    id: '1',
    foo: 'bar'
  },
  sources: [{
    src: '//example.com/movie1.mp4',
    type: 'video/mp4'
  }]
}, {
  sources: [{
    src: '//example.com/movie2.mp4',
    type: 'video/mp4'
  }],
  thumbnail: '//example.com/movie2.jpg'
}];

const resolveUrl = url => {
  const a = document.createElement('a');

  a.href = url;
  return a.href;
};

const dom = videojs.dom || videojs;
const Html5 = videojs.getTech('Html5');

QUnit.test('the environment is sane', function(assert) {
  assert.ok(true, 'everything is swell');
});

function setup() {
  this.oldVideojsBrowser = videojs.browser;
  videojs.browser = videojs.mergeOptions({}, videojs.browser);

  this.fixture = document.querySelector('#qunit-fixture');

  // force HTML support so the tests run in a reasonable
  // environment under phantomjs
  this.realIsHtmlSupported = Html5.isSupported;
  Html5.isSupported = function() {
    return true;
  };

  // create a video element
  const video = document.createElement('video');

  this.fixture.appendChild(video);

  // create a video.js player
  this.player = videojs(video);

  // Create two playlist container elements.
  this.fixture.appendChild(dom.createEl('div', {className: 'vjs-playlist'}));
  this.fixture.appendChild(dom.createEl('div', {className: 'vjs-playlist'}));
}

function teardown() {
  videojs.browser = this.oldVideojsBrowser;
  Html5.isSupported = this.realIsHtmlSupported;
  this.player.dispose();
  this.player = null;
  dom.emptyEl(this.fixture);
}

QUnit.module('videojs-playlist-ui', {beforeEach: setup, afterEach: teardown});

QUnit.test('registers itself', function(assert) {
  assert.ok(this.player.playlistUi, 'registered the plugin');
});

QUnit.test('errors if used without the playlist plugin', function(assert) {
  assert.throws(function() {
    this.player.playlist = null;
    this.player.playlistUi();
  }, 'threw on init');
});

QUnit.test('is empty if the playlist plugin isn\'t initialized', function(assert) {
  this.player.playlistUi();

  const items = this.fixture.querySelectorAll('.vjs-playlist-item');

  assert.ok(this.fixture.querySelector('.vjs-playlist'), 'created the menu');
  assert.strictEqual(items.length, 0, 'displayed no items');
});

QUnit.test('can be initialized with an element (deprecated form)', function(assert) {
  const elem = dom.createEl('div');

  this.player.playlist(playlist);
  this.player.playlistUi(elem);

  assert.strictEqual(
    elem.querySelectorAll('li.vjs-playlist-item').length,
    playlist.length,
    'created an element for each playlist item'
  );
});

QUnit.test('can be initialized with an element', function(assert) {
  const elem = dom.createEl('div');

  this.player.playlist(playlist);
  this.player.playlistUi({el: elem});

  assert.strictEqual(
    elem.querySelectorAll('li.vjs-playlist-item').length,
    playlist.length,
    'created an element for each playlist item'
  );
});

QUnit.test('can look for an element with the class "vjs-playlist" that is not already in use', function(assert) {
  const firstEl = this.fixture.querySelectorAll('.vjs-playlist')[0];
  const secondEl = this.fixture.querySelectorAll('.vjs-playlist')[1];

  // Give the firstEl a child, so the plugin thinks it is in use and moves on
  // to the next one.
  firstEl.appendChild(dom.createEl('div'));

  this.player.playlist(playlist);
  this.player.playlistUi();

  assert.strictEqual(this.player.playlistMenu.el(), secondEl, 'used the first matching/empty element');
  assert.strictEqual(
    secondEl.querySelectorAll('li.vjs-playlist-item').length,
    playlist.length,
    'found an element for each playlist item'
  );
});

QUnit.test('can look for an element with a custom class that is not already in use', function(assert) {
  const firstEl = dom.createEl('div', {className: 'super-playlist'});
  const secondEl = dom.createEl('div', {className: 'super-playlist'});

  // Give the firstEl a child, so the plugin thinks it is in use and moves on
  // to the next one.
  firstEl.appendChild(dom.createEl('div'));

  this.fixture.appendChild(firstEl);
  this.fixture.appendChild(secondEl);

  this.player.playlist(playlist);
  this.player.playlistUi({
    className: 'super-playlist'
  });

  assert.strictEqual(this.player.playlistMenu.el(), secondEl, 'used the first matching/empty element');
  assert.strictEqual(
    this.fixture.querySelectorAll('li.vjs-playlist-item').length,
    playlist.length,
    'created an element for each playlist item'
  );
});

QUnit.test('specializes the class name if touch input is absent', function(assert) {
  videojs.browser.TOUCH_ENABLED = false;

  this.player.playlist(playlist);
  this.player.playlistUi();

  assert.ok(this.player.playlistMenu.hasClass('vjs-mouse'), 'marked the playlist menu');
});

QUnit.test('can be re-initialized without doubling the contents of the list', function(assert) {
  const el = this.fixture.querySelectorAll('.vjs-playlist')[0];

  this.player.playlist(playlist);
  this.player.playlistUi();
  this.player.playlistUi();
  this.player.playlistUi();

  assert.strictEqual(this.player.playlistMenu.el(), el, 'used the first matching/empty element');
  assert.strictEqual(
    el.querySelectorAll('li.vjs-playlist-item').length,
    playlist.length,
    'found an element for each playlist item'
  );
});

QUnit.module('videojs-playlist-ui: Components', {beforeEach: setup, afterEach: teardown});

// --------------------
// Creation and Updates
// --------------------

QUnit.test('includes the video name if provided', function(assert) {
  this.player.playlist(playlist);
  this.player.playlistUi();

  const items = this.fixture.querySelectorAll('.vjs-playlist-item');

  assert.strictEqual(
    items[0].querySelector('.vjs-playlist-name').textContent,
    playlist[0].name,
    'wrote the name'
  );
  assert.strictEqual(
    items[1].querySelector('.vjs-playlist-name').textContent,
    'Untitled Video',
    'wrote a placeholder for the name'
  );
});

QUnit.test('includes custom data attribute if provided', function(assert) {
  this.player.playlist(playlist);
  this.player.playlistUi();

  const items = this.fixture.querySelectorAll('.vjs-playlist-item');

  assert.strictEqual(
    items[0].dataset.id,
    playlist[0].data.id,
    'set a single data attribute'
  );
  assert.strictEqual(
    items[0].dataset.id,
    '1',
    'set a single data attribute (actual value)'
  );
  assert.strictEqual(
    items[0].dataset.foo,
    playlist[0].data.foo,
    'set an addtional data attribute'
  );
  assert.strictEqual(
    items[0].dataset.foo,
    'bar',
    'set an addtional data attribute'
  );
});

QUnit.test('outputs a <picture> for simple thumbnails', function(assert) {
  this.player.playlist(playlist);
  this.player.playlistUi();

  const pictures = this.fixture.querySelectorAll('.vjs-playlist-item picture');

  assert.strictEqual(pictures.length, 1, 'output one picture');
  const imgs = pictures[0].querySelectorAll('img');

  assert.strictEqual(imgs.length, 1, 'output one img');
  assert.strictEqual(imgs[0].src, window.location.protocol + playlist[1].thumbnail, 'set the src attribute');
});

QUnit.test('outputs a <picture> for responsive thumbnails', function(assert) {
  const playlistOverride = [{
    sources: [{
      src: '//example.com/movie.mp4',
      type: 'video/mp4'
    }],
    thumbnail: [{
      srcset: '/test/example/oceans.jpg',
      type: 'image/jpeg',
      media: '(min-width: 400px;)'
    }, {
      src: '/test/example/oceans-low.jpg'
    }]
  }];

  this.player.playlist(playlistOverride);
  this.player.playlistUi();

  const sources = this.fixture.querySelectorAll('.vjs-playlist-item picture source');
  const imgs = this.fixture.querySelectorAll('.vjs-playlist-item picture img');

  assert.strictEqual(sources.length, 1, 'output one source');
  assert.strictEqual(
    sources[0].srcset,
    playlistOverride[0].thumbnail[0].srcset,
    'wrote the srcset attribute'
  );
  assert.strictEqual(
    sources[0].type,
    playlistOverride[0].thumbnail[0].type,
    'wrote the type attribute'
  );
  assert.strictEqual(
    sources[0].media,
    playlistOverride[0].thumbnail[0].media,
    'wrote the type attribute'
  );
  assert.strictEqual(imgs.length, 1, 'output one img');
  assert.strictEqual(
    imgs[0].src,
    resolveUrl(playlistOverride[0].thumbnail[1].src),
    'output the img src attribute'
  );
});

QUnit.test('outputs a placeholder for items without thumbnails', function(assert) {
  this.player.playlist(playlist);
  this.player.playlistUi();

  const thumbnails = this.fixture.querySelectorAll('.vjs-playlist-item .vjs-playlist-thumbnail');

  assert.strictEqual(thumbnails.length, playlist.length, 'output two thumbnails');
  assert.strictEqual(thumbnails[0].nodeName.toLowerCase(), 'div', 'the second is a placeholder');
});

QUnit.test('includes the duration if one is provided', function(assert) {
  this.player.playlist(playlist);
  this.player.playlistUi();

  const durations = this.fixture.querySelectorAll('.vjs-playlist-item .vjs-playlist-duration');

  assert.strictEqual(durations.length, 1, 'skipped the item without a duration');
  assert.strictEqual(
    durations[0].textContent,
    '1:40',
    'wrote the duration'
  );
  assert.strictEqual(
    durations[0].getAttribute('datetime'),
    'PT0H0M' + playlist[0].duration + 'S',
    'wrote a machine-readable datetime'
  );
});

QUnit.test('marks the selected playlist item on startup', function(assert) {
  this.player.playlist(playlist);
  this.player.currentSrc = () => playlist[0].sources[0].src;
  this.player.playlistUi();

  const selectedItems = this.fixture.querySelectorAll('.vjs-playlist-item.vjs-selected');

  assert.strictEqual(selectedItems.length, 1, 'marked one playlist item');
  assert.strictEqual(
    selectedItems[0].querySelector('.vjs-playlist-name').textContent,
    playlist[0].name,
    'marked the first playlist item'
  );
});

QUnit.test('updates the selected playlist item on loadstart', function(assert) {
  this.player.playlist(playlist);
  this.player.playlistUi();

  this.player.playlist.currentItem(1);
  this.player.currentSrc = () => playlist[1].sources[0].src;
  this.player.trigger('loadstart');

  const selectedItems = this.fixture.querySelectorAll('.vjs-playlist-item.vjs-selected');

  assert.strictEqual(
    this.fixture.querySelectorAll('.vjs-playlist-item').length,
    playlist.length,
    'displayed the correct number of items'
  );
  assert.strictEqual(selectedItems.length, 1, 'marked one playlist item');
  assert.strictEqual(
    selectedItems[0].querySelector('img').src,
    resolveUrl(playlist[1].thumbnail),
    'marked the second playlist item'
  );
});

QUnit.test('selects no item if the playlist is not in use', function(assert) {
  this.player.playlist(playlist);
  this.player.playlist.currentItem = () => -1;
  this.player.playlistUi();

  this.player.trigger('loadstart');

  assert.strictEqual(
    this.fixture.querySelectorAll('.vjs-playlist-item.vjs-selected').length,
    0,
    'no items selected'
  );
});

QUnit.test('updates on "playlistchange", different lengths', function(assert) {
  this.player.playlist([]);
  this.player.playlistUi();

  let items = this.fixture.querySelectorAll('.vjs-playlist-item');

  assert.strictEqual(items.length, 0, 'no items initially');

  this.player.playlist(playlist);
  this.player.trigger('playlistchange');
  items = this.fixture.querySelectorAll('.vjs-playlist-item');
  assert.strictEqual(items.length, playlist.length, 'updated with the new items');
});

QUnit.test('updates on "playlistchange", equal lengths', function(assert) {
  this.player.playlist([{sources: []}, {sources: []}]);
  this.player.playlistUi();

  let items = this.fixture.querySelectorAll('.vjs-playlist-item');

  assert.strictEqual(items.length, 2, 'two items initially');

  this.player.playlist(playlist);
  this.player.trigger('playlistchange');
  items = this.fixture.querySelectorAll('.vjs-playlist-item');
  assert.strictEqual(items.length, playlist.length, 'updated with the new items');
  assert.strictEqual(this.player.playlistMenu.items[0].item, playlist[0], 'we have updated items');
  assert.strictEqual(this.player.playlistMenu.items[1].item, playlist[1], 'we have updated items');
});

QUnit.test('updates on "playlistchange", update selection', function(assert) {
  this.player.playlist(playlist);
  this.player.currentSrc = function() {
    return playlist[0].sources[0].src;
  };
  this.player.playlistUi();

  let items = this.fixture.querySelectorAll('.vjs-playlist-item');

  assert.strictEqual(items.length, 2, 'two items initially');

  assert.ok((/vjs-selected/).test(items[0].getAttribute('class')), 'first item is selected by default');
  this.player.playlist.currentItem(1);
  this.player.currentSrc = function() {
    return playlist[1].sources[0].src;
  };

  this.player.trigger('playlistchange');
  items = this.fixture.querySelectorAll('.vjs-playlist-item');
  assert.strictEqual(items.length, playlist.length, 'updated with the new items');
  assert.ok((/vjs-selected/).test(items[1].getAttribute('class')), 'second item is selected after update');
  assert.ok(!(/vjs-selected/).test(items[0].getAttribute('class')), 'first item is not selected after update');
});

QUnit.test('updates on "playlistsorted", different lengths', function(assert) {
  this.player.playlist([]);
  this.player.playlistUi();

  let items = this.fixture.querySelectorAll('.vjs-playlist-item');

  assert.strictEqual(items.length, 0, 'no items initially');

  this.player.playlist(playlist);
  this.player.trigger('playlistsorted');
  items = this.fixture.querySelectorAll('.vjs-playlist-item');
  assert.strictEqual(items.length, playlist.length, 'updated with the new items');
});

QUnit.test('updates on "playlistsorted", equal lengths', function(assert) {
  this.player.playlist([{sources: []}, {sources: []}]);
  this.player.playlistUi();

  let items = this.fixture.querySelectorAll('.vjs-playlist-item');

  assert.strictEqual(items.length, 2, 'two items initially');

  this.player.playlist(playlist);
  this.player.trigger('playlistsorted');
  items = this.fixture.querySelectorAll('.vjs-playlist-item');
  assert.strictEqual(items.length, playlist.length, 'updated with the new items');
  assert.strictEqual(this.player.playlistMenu.items[0].item, playlist[0], 'we have updated items');
  assert.strictEqual(this.player.playlistMenu.items[1].item, playlist[1], 'we have updated items');
});

QUnit.test('updates on "playlistsorted", update selection', function(assert) {
  this.player.playlist(playlist);
  this.player.currentSrc = function() {
    return playlist[0].sources[0].src;
  };
  this.player.playlistUi();

  let items = this.fixture.querySelectorAll('.vjs-playlist-item');

  assert.strictEqual(items.length, 2, 'two items initially');

  assert.ok((/vjs-selected/).test(items[0].getAttribute('class')), 'first item is selected by default');
  this.player.playlist.currentItem(1);
  this.player.currentSrc = function() {
    return playlist[1].sources[0].src;
  };

  this.player.trigger('playlistsorted');
  items = this.fixture.querySelectorAll('.vjs-playlist-item');
  assert.strictEqual(items.length, playlist.length, 'updated with the new items');
  assert.ok((/vjs-selected/).test(items[1].getAttribute('class')), 'second item is selected after update');
  assert.ok(!(/vjs-selected/).test(items[0].getAttribute('class')), 'first item is not selected after update');
});

QUnit.test('tracks when an ad is playing', function(assert) {
  this.player.playlist([]);
  this.player.playlistUi();

  this.player.duration = () => 5;

  const playlistMenu = this.player.playlistMenu;

  assert.ok(
    !playlistMenu.hasClass('vjs-ad-playing'),
    'does not have class vjs-ad-playing'
  );
  this.player.trigger('adstart');
  assert.ok(
    playlistMenu.hasClass('vjs-ad-playing'),
    'has class vjs-ad-playing'
  );

  this.player.trigger('adend');
  assert.ok(
    !playlistMenu.hasClass('vjs-ad-playing'),
    'does not have class vjs-ad-playing'
  );
});

// -----------
// Interaction
// -----------

QUnit.test('changes the selection when tapped', function(assert) {
  let playCalled = false;

  this.player.playlist(playlist);
  this.player.playlistUi({playOnSelect: true});
  this.player.play = function() {
    playCalled = true;
  };

  let sources;

  this.player.src = (src) => {
    if (src) {
      sources = src;
    }
    return sources[0];
  };
  this.player.currentSrc = () => sources[0].src;
  this.player.playlistMenu.items[1].trigger('tap');
  // trigger a loadstart synchronously to simplify the test
  this.player.trigger('loadstart');

  assert.ok(
    this.player.playlistMenu.items[1].hasClass('vjs-selected'),
    'selected the new item'
  );
  assert.ok(
    !this.player.playlistMenu.items[0].hasClass('vjs-selected'),
    'deselected the old item'
  );
  assert.strictEqual(playCalled, true, 'play gets called if option is set');
});

QUnit.test('play should not get called by default upon selection of menu items ', function(assert) {
  let playCalled = false;

  this.player.playlist(playlist);
  this.player.playlistUi();
  this.player.play = function() {
    playCalled = true;
  };

  let sources;

  this.player.src = (src) => {
    if (src) {
      sources = src;
    }
    return sources[0];
  };
  this.player.currentSrc = () => sources[0].src;
  this.player.playlistMenu.items[1].trigger('tap');
  // trigger a loadstart synchronously to simplify the test
  this.player.trigger('loadstart');
  assert.strictEqual(playCalled, false, 'play should not get called by default');
});

QUnit.test('disposing the playlist menu nulls out the player\'s reference to it', function(assert) {
  assert.strictEqual(this.fixture.querySelectorAll('.vjs-playlist').length, 2, 'there are two playlist containers at the start');

  this.player.playlist(playlist);
  this.player.playlistUi();
  this.player.playlistMenu.dispose();

  assert.strictEqual(this.fixture.querySelectorAll('.vjs-playlist').length, 1, 'only the unused playlist container is left');
  assert.strictEqual(this.player.playlistMenu, null, 'the playlistMenu property is null');
});

QUnit.test('disposing the playlist menu removes playlist menu items', function(assert) {
  assert.strictEqual(this.fixture.querySelectorAll('.vjs-playlist').length, 2, 'there are two playlist containers at the start');

  this.player.playlist(playlist);
  this.player.playlistUi();

  // Cache some references so we can refer to them after disposal.
  const items = [].concat(this.player.playlistMenu.items);

  this.player.playlistMenu.dispose();

  assert.strictEqual(this.fixture.querySelectorAll('.vjs-playlist').length, 1, 'only the unused playlist container is left');
  assert.strictEqual(this.player.playlistMenu, null, 'the playlistMenu property is null');

  items.forEach(i => {
    assert.strictEqual(i.el_, null, `the item "${i.id_}" has been disposed`);
  });
});

QUnit.test('disposing the player also disposes the playlist menu', function(assert) {
  assert.strictEqual(this.fixture.querySelectorAll('.vjs-playlist').length, 2, 'there are two playlist containers at the start');

  this.player.playlist(playlist);
  this.player.playlistUi();
  this.player.dispose();

  assert.strictEqual(this.fixture.querySelectorAll('.vjs-playlist').length, 1, 'only the unused playlist container is left');
  assert.strictEqual(this.player.playlistMenu, null, 'the playlistMenu property is null');
});
