/* eslint-env qunit */
import ChaptersButton from '../../../src/js/control-bar/text-track-controls/chapters-button.js';
import DescriptionsButton from '../../../src/js/control-bar/text-track-controls/descriptions-button.js';
import SubtitlesButton from '../../../src/js/control-bar/text-track-controls/subtitles-button.js';
import CaptionsButton from '../../../src/js/control-bar/text-track-controls/captions-button.js';
import SubsCapsButton from '../../../src/js/control-bar/text-track-controls/subs-caps-button.js';

import TextTrack from '../../../src/js/tracks/text-track.js';
import TextTrackDisplay from '../../../src/js/tracks/text-track-display.js';
import Html5 from '../../../src/js/tech/html5.js';
import Tech from '../../../src/js/tech/tech.js';

import * as browser from '../../../src/js/utils/browser.js';
import TestHelpers from '../test-helpers.js';
import document from 'global/document';
import sinon from 'sinon';

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

QUnit.test('should place title list item into ul', function(assert) {
  const player = TestHelpers.makePlayer();
  const chaptersButton = new ChaptersButton(player);

  const menuContentElement = chaptersButton.el().getElementsByTagName('UL')[0];
  const titleElement = menuContentElement.children[0];

  assert.ok(titleElement.innerHTML === 'Chapters', 'title element placed in ul');

  player.dispose();
  chaptersButton.dispose();
});

QUnit.test('Player track methods call the tech', function(assert) {
  const player = TestHelpers.makePlayer();
  let calls = 0;

  player.tech_.textTracks = function() {
    calls++;
  };
  player.tech_.addTextTrack = function() {
    calls++;
  };

  player.addTextTrack();
  player.textTracks();

  assert.equal(calls, 2, 'both textTrack and addTextTrack defer to the tech');

  player.dispose();
});

QUnit.test('TextTrackDisplay initializes tracks on player ready', function(assert) {
  let calls = 0;
  const player = TestHelpers.makePlayer();

  player.addTextTrack = () => calls--;
  player.getChild = () => calls--;
  player.ready = () => calls++;

  const ttd = new TextTrackDisplay(player, {});

  assert.equal(calls, 1, 'only a player.ready call was made');

  ttd.dispose();
  player.dispose();
});

QUnit.test('listen to remove and add track events in native text tracks', function(assert) {
  const oldTestVid = Html5.TEST_VID;
  const oldTextTracks = Html5.prototype.textTracks;
  const events = {};

  Html5.prototype.textTracks = function() {
    return {
      removeEventListener() {},
      addEventListener(type, handler) {
        events[type] = true;
      }
    };
  };

  Html5.TEST_VID = {
    textTracks: []
  };

  const player = {
    // Function.prototype is a built-in no-op function.
    controls() {},
    ready() {},
    options() {
      return {};
    },
    addChild() {},
    id() {},
    el() {
      return {
        insertBefore() {},
        appendChild() {}
      };
    }
  };

  player.player_ = player;
  player.options_ = {};

  const html = new Html5({});

  assert.ok(events.removetrack, 'removetrack listener was added');
  assert.ok(events.addtrack, 'addtrack listener was added');

  Html5.TEST_VID = oldTestVid;
  Html5.prototype.textTracks = oldTextTracks;

  html.dispose();
});

QUnit.test('update texttrack buttons on removetrack or addtrack', function(assert) {
  let update = 0;
  const events = {};
  const oldCaptionsUpdate = CaptionsButton.prototype.update;
  const oldSubsUpdate = SubtitlesButton.prototype.update;
  const oldDescriptionsUpdate = DescriptionsButton.prototype.update;
  const oldChaptersUpdate = ChaptersButton.prototype.update;
  const oldSubsCapsUpdate = SubsCapsButton.prototype.update;

  CaptionsButton.prototype.update = function() {
    update++;
    oldCaptionsUpdate.call(this);
  };
  SubtitlesButton.prototype.update = function() {
    update++;
    oldSubsUpdate.call(this);
  };
  DescriptionsButton.prototype.update = function() {
    update++;
    oldDescriptionsUpdate.call(this);
  };
  ChaptersButton.prototype.update = function() {
    update++;
    oldChaptersUpdate.call(this);
  };
  SubsCapsButton.prototype.update = function() {
    update++;
    oldSubsCapsUpdate.call(this);
  };

  const oldFeaturesNativeTextTracks = Tech.prototype.featuresNativeTextTracks;
  const oldTextTracks = Tech.prototype.textTracks;

  Tech.prototype.featuresNativeTextTracks = true;
  Tech.prototype.textTracks = function() {
    return {
      length: 0,
      addEventListener(type, handler) {
        if (!events[type]) {
          events[type] = [];
        }
        events[type].push(handler);
      },
      // Required in player.dispose()
      removeEventListener() {}
    };
  };

  const tag = document.createElement('video');
  const track1 = document.createElement('track');
  const track2 = document.createElement('track');

  track1.kind = 'captions';
  track1.label = 'en';
  track1.language = 'English';
  track1.src = '#en.vtt';
  tag.appendChild(track1);

  track2.kind = 'captions';
  track2.label = 'es';
  track2.language = 'Spanish';
  track2.src = '#es.vtt';
  tag.appendChild(track2);

  const player = TestHelpers.makePlayer({
    controlBar: {
      captionsButton: true,
      subtitlesButton: true
    }
  }, tag);

  player.player_ = player;

  assert.equal(update, 5, 'update was called on the five buttons during init');

  for (let i = 0; i < events.removetrack.length; i++) {
    events.removetrack[i]();
  }

  assert.equal(update, 10, 'update was called on the five buttons for remove track');

  for (let i = 0; i < events.addtrack.length; i++) {
    events.addtrack[i]();
  }

  assert.equal(update, 15, 'update was called on the five buttons for remove track');

  Tech.prototype.textTracks = oldTextTracks;
  Tech.prototype.featuresNativeTextTracks = oldFeaturesNativeTextTracks;
  CaptionsButton.prototype.update = oldCaptionsUpdate;
  SubtitlesButton.prototype.update = oldSubsUpdate;
  ChaptersButton.prototype.update = oldChaptersUpdate;
  SubsCapsButton.prototype.update = oldSubsCapsUpdate;
  DescriptionsButton.prototype.update = oldDescriptionsUpdate;

  player.dispose();
});

QUnit.test('emulated tracks are always used, except in safari', function(assert) {
  const oldTestVid = Html5.TEST_VID;
  const oldIsAnySafari = browser.IS_ANY_SAFARI;

  Html5.TEST_VID = {
    textTracks: []
  };

  browser.stub_IS_ANY_SAFARI(false);

  assert.ok(!Html5.supportsNativeTextTracks(), 'Html5 does not support native text tracks, in non-safari');

  browser.stub_IS_ANY_SAFARI(true);

  assert.ok(Html5.supportsNativeTextTracks(), 'Html5 does support native text tracks in safari');

  Html5.TEST_VID = oldTestVid;
  browser.stub_IS_ANY_SAFARI(oldIsAnySafari);
});

QUnit.test('when switching techs, we should not get a new text track', function(assert) {
  const player = TestHelpers.makePlayer();

  player.loadTech_('TechFaker');
  const firstTracks = player.textTracks();

  player.loadTech_('TechFaker');
  const secondTracks = player.textTracks();

  assert.ok(firstTracks === secondTracks, 'the tracks are equal');

  player.dispose();
});

if (Html5.supportsNativeTextTracks()) {
  QUnit.test('listen to native remove and add track events in native text tracks', function(assert) {
    const done = assert.async();

    const el = document.createElement('video');
    const html = new Html5({el});
    const tt = el.textTracks;
    const emulatedTt = html.textTracks();
    const track = document.createElement('track');

    el.appendChild(track);

    const addtrack = function() {
      assert.equal(emulatedTt.length, tt.length, 'we have matching tracks length');
      assert.equal(emulatedTt.length, 1, 'we have one text track');

      el.removeChild(track);
    };

    emulatedTt.one('addtrack', addtrack);
    emulatedTt.one('removetrack', function() {
      assert.equal(emulatedTt.length, tt.length, 'we have matching tracks length');
      assert.equal(emulatedTt.length, 0, 'we have no more text tracks');

      html.dispose();
      done();
    });
  });

  QUnit.test('should have removed tracks on dispose', function(assert) {
    const done = assert.async();

    const el = document.createElement('video');
    const html = new Html5({el});
    const tt = el.textTracks;
    const emulatedTt = html.textTracks();
    const track = document.createElement('track');

    el.appendChild(track);

    const addtrack = function() {
      assert.equal(emulatedTt.length, tt.length, 'we have matching tracks length');
      assert.equal(emulatedTt.length, 1, 'we have one text track');

      emulatedTt.off('addtrack', addtrack);
      html.dispose();

      assert.equal(emulatedTt.length, tt.length, 'we have matching tracks length');
      assert.equal(emulatedTt.length, 0, 'we have no more text tracks');

      done();
    };

    emulatedTt.on('addtrack', addtrack);
  });
}

QUnit.test('should check for text track changes when emulating text tracks', function(assert) {
  const tech = new Tech();
  let numTextTrackChanges = 0;

  tech.on('texttrackchange', function() {
    numTextTrackChanges++;
  });
  tech.emulateTextTracks();
  assert.equal(numTextTrackChanges, 1, 'we got a texttrackchange event');

  tech.dispose();
});

QUnit.test('no lang attribute on cue elements if one is provided', function(assert) {
  const player = TestHelpers.makePlayer();
  const tt = new TextTrack({
    tech: player.tech_,
    mode: 'showing'
  });

  tt.addCue({
    id: '1',
    startTime: 2,
    endTime: 5
  });
  player.tech_.textTracks().addTrack(tt);

  player.currentTime(2);
  player.tech_.trigger('playing');

  assert.notOk(tt.activeCues[0].displayState.hasAttribute('lang'), 'no lang attribute should be set');

  player.dispose();
});

QUnit.test('set lang attribute on cue elements if one is provided', function(assert) {
  const player = TestHelpers.makePlayer();
  const tt = new TextTrack({
    srclang: 'en',
    tech: player.tech_,
    mode: 'showing'
  });

  tt.addCue({
    id: '1',
    startTime: 2,
    endTime: 5
  });
  player.tech_.textTracks().addTrack(tt);

  player.currentTime(2);
  player.tech_.trigger('playing');

  assert.equal(tt.activeCues[0].displayState.getAttribute('lang'), 'en', 'the lang should be set to en');

  player.dispose();
});

QUnit.test('removes cuechange event when text track is hidden for emulated tracks', function(assert) {
  const player = TestHelpers.makePlayer();
  const tt = new TextTrack({
    tech: player.tech_,
    mode: 'showing'
  });

  tt.addCue({
    id: '1',
    startTime: 2,
    endTime: 5
  });
  player.tech_.textTracks().addTrack(tt);

  let numTextTrackChanges = 0;

  player.tech_.on('texttrackchange', function() {
    numTextTrackChanges++;
  });

  tt.mode = 'disabled';
  this.clock.tick(1);
  assert.equal(
    numTextTrackChanges, 1,
    'texttrackchange should be called once for mode change'
  );
  tt.mode = 'showing';
  this.clock.tick(1);
  assert.equal(
    numTextTrackChanges, 2,
    'texttrackchange should be called once for mode change'
  );

  player.tech_.currentTime = function() {
    return 3;
  };
  player.tech_.trigger('playing');
  assert.equal(
    numTextTrackChanges, 3,
    'texttrackchange should be triggered once for the cuechange'
  );

  tt.mode = 'hidden';
  this.clock.tick(1);
  assert.equal(
    numTextTrackChanges, 4,
    'texttrackchange should be called once for the mode change'
  );

  player.tech_.currentTime = function() {
    return 7;
  };
  player.tech_.trigger('timeupdate');
  assert.equal(
    numTextTrackChanges, 4,
    'texttrackchange should be not be called since mode is hidden'
  );
  player.dispose();
});

QUnit.test('should return correct remote text track values', function(assert) {
  const fixture = document.getElementById('qunit-fixture');
  const html = `
    <video id="example_1" class="video-js" autoplay preload="none">
      <source src="http://google.com" type="video/mp4">
      <source src="http://google.com" type="video/webm">
      <track kind="captions" label="label">
    </video>
  `;

  fixture.innerHTML += html;
  const tag = document.getElementById('example_1');
  const player = TestHelpers.makePlayer({}, tag);

  this.clock.tick(10);

  assert.equal(player.remoteTextTracks().length, 1, 'add text track via html');
  assert.equal(player.remoteTextTrackEls().length, 1, 'add html track element via html');

  const htmlTrackElement = player.addRemoteTextTrack({
    kind: 'captions',
    label: 'label'
  }, true);

  assert.equal(player.remoteTextTracks().length, 2, 'add text track via method');
  assert.equal(player.remoteTextTrackEls().length, 2, 'add html track element via method');

  player.removeRemoteTextTrack(htmlTrackElement.track);

  assert.equal(player.remoteTextTracks().length, 1, 'remove text track via method');
  assert.equal(
    player.remoteTextTrackEls().length,
    1,
    'remove html track element via method'
  );

  player.dispose();
});

QUnit.test('should uniformly create html track element when adding text track', function(assert) {
  const player = TestHelpers.makePlayer();
  const track = {
    kind: 'kind',
    src: 'src',
    language: 'language',
    label: 'label',
    default: 'default'
  };

  assert.equal(player.remoteTextTrackEls().length, 0, 'no html text tracks');

  const htmlTrackElement = player.addRemoteTextTrack(track, true);

  assert.equal(
    htmlTrackElement.kind,
    htmlTrackElement.track.kind,
    'verify html track element kind'
  );
  assert.equal(
    htmlTrackElement.src,
    htmlTrackElement.track.src,
    'verify html track element src'
  );
  assert.equal(
    htmlTrackElement.srclang,
    htmlTrackElement.track.language,
    'verify html track element language'
  );
  assert.equal(
    htmlTrackElement.label,
    htmlTrackElement.track.label,
    'verify html track element label'
  );
  assert.equal(
    htmlTrackElement.default,
    htmlTrackElement.track.default,
    'verify html track element default'
  );

  assert.equal(player.remoteTextTrackEls().length, 1, 'html track element exist');
  assert.equal(
    player.remoteTextTrackEls().getTrackElementByTrack_(htmlTrackElement.track),
    htmlTrackElement,
    'verify same html track element'
  );

  player.dispose();
});

// disable in Firefox because while the code works in practice, during the
// tests, somehow the text track object isn't ready and thus it won't allow
// us to change the mode of the track rendering the test non-functional.
if (!browser.IS_FIREFOX) {
  QUnit.test('remote text tracks change event should fire when using native text tracks', function(assert) {
    const done = assert.async();

    const player = TestHelpers.makePlayer({
      techOrder: ['html5'],
      html5: { nativeTextTracks: true }
    });

    player.remoteTextTracks().on('change', function(e) {
      assert.ok(true, 'change event triggered');
      player.dispose();
      done();
    });

    const track = {
      kind: 'kind',
      src: 'src',
      language: 'language',
      label: 'label',
      default: 'default'
    };

    player.addRemoteTextTrack(track, true);
  });
}

QUnit.test('default text tracks should show by default', function(assert) {
  const tag = TestHelpers.makeTag();
  const capt = document.createElement('track');

  capt.setAttribute('kind', 'captions');
  capt.setAttribute('default', 'default');

  tag.appendChild(capt);

  const player = TestHelpers.makePlayer({
    html5: {
      nativeTextTracks: false
    }
  }, tag);

  // native tracks are initialized after the player is ready
  this.clock.tick(1);

  const tracks = player.textTracks();

  assert.equal(tracks[0].kind, 'captions', 'the captions track is present');
  assert.equal(tracks[0].mode, 'showing', 'the captions track is showing');

  player.dispose();
});

QUnit.test('default captions take precedence over default descriptions', function(assert) {
  const tag = TestHelpers.makeTag();
  const desc = document.createElement('track');
  const capt = document.createElement('track');

  desc.setAttribute('kind', 'descriptions');
  desc.setAttribute('default', 'default');
  capt.setAttribute('kind', 'captions');
  capt.setAttribute('default', 'default');

  tag.appendChild(desc);
  tag.appendChild(capt);

  const player = TestHelpers.makePlayer({
    html5: {
      nativeTextTracks: false
    }
  }, tag);

  // native tracks are initialized after the player is ready
  this.clock.tick(1);

  const tracks = player.textTracks();

  assert.equal(tracks[0].kind, 'descriptions', 'the descriptions track is first');
  assert.equal(tracks[0].mode, 'disabled', 'the descriptions track is disabled');
  assert.equal(tracks[1].kind, 'captions', 'the captions track is second');
  assert.equal(tracks[1].mode, 'showing', 'the captions track is showing');
  player.dispose();
});

QUnit.test('removeRemoteTextTrack should be able to take both a track and the response from addRemoteTextTrack', function(assert) {
  const player = TestHelpers.makePlayer();
  const track = {
    kind: 'kind',
    src: 'src',
    language: 'language',
    label: 'label',
    default: 'default'
  };
  let htmlTrackElement = player.addRemoteTextTrack(track, true);

  assert.equal(player.remoteTextTrackEls().length, 1, 'html track element exist');

  player.removeRemoteTextTrack(htmlTrackElement);

  assert.equal(
    player.remoteTextTrackEls().length,
    0,
    'the track element was removed correctly'
  );

  htmlTrackElement = player.addRemoteTextTrack(track, true);
  assert.equal(player.remoteTextTrackEls().length, 1, 'html track element exist');

  player.removeRemoteTextTrack(htmlTrackElement.track);
  assert.equal(
    player.remoteTextTrackEls().length,
    0,
    'the track element was removed correctly'
  );
  player.dispose();
});

if (Html5.isSupported()) {
  QUnit.test('auto remove tracks should not clean up tracks added while source is being added', function(assert) {
    const player = TestHelpers.makePlayer({
      techOrder: ['html5'],
      html5: {
        nativeTextTracks: false
      }
    });

    const track = {
      kind: 'kind',
      src: 'src',
      language: 'language',
      label: 'label',
      default: 'default'
    };

    player.src({src: 'example.mp4', type: 'video/mp4'});
    player.addRemoteTextTrack(track, false);

    this.clock.tick(1);
    assert.equal(player.textTracks().length, 1, 'we have one text track');

    player.dispose();
  });

  QUnit.test('auto remove tracks added right before a source change will be cleaned up', function(assert) {
    const player = TestHelpers.makePlayer({
      techOrder: ['html5'],
      html5: {
        nativeTextTracks: false
      }
    });

    const track = {
      kind: 'kind',
      src: 'src',
      language: 'language',
      label: 'label',
      default: 'default'
    };

    player.addRemoteTextTrack(track, false);
    player.src({src: 'example.mp4', type: 'video/mp4'});

    this.clock.tick(1);
    assert.equal(player.textTracks().length, 0, 'we do not have any tracks left');

    player.dispose();
  });
}
