/* eslint-env qunit */
import window from 'global/window';
import Html5 from '../../../src/js/tech/html5.js';
import { constructColor } from '../../../src/js/tracks/text-track-display.js';
import Component from '../../../src/js/component.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 Track Display', {
  beforeEach(assert) {
    this.clock = sinon.useFakeTimers();
  },
  afterEach(assert) {
    this.clock.restore();
  }
});

const getMenuItemByLanguage = function(items, language) {
  for (let i = items.length - 1; i > 0; i--) {
    const captionMenuItem = items[i];
    const trackLanguage = captionMenuItem.track.language;

    if (trackLanguage && trackLanguage === language) {
      return captionMenuItem;
    }
  }
};

QUnit.test('if native text tracks are not supported, create a texttrackdisplay', function(assert) {
  const oldTestVid = Html5.TEST_VID;
  const oldIsFirefox = browser.IS_FIREFOX;
  const oldTextTrackDisplay = Component.getComponent('TextTrackDisplay');
  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);

  Html5.TEST_VID = {
    textTracks: []
  };

  browser.stub_IS_FIREFOX(true);

  const fakeTTDSpy = sinon.spy();

  class FakeTTD extends Component {
    constructor(player, options) {
      super(player, options);
      fakeTTDSpy();
    }
  }

  Component.registerComponent('TextTrackDisplay', FakeTTD);

  const player = TestHelpers.makePlayer({}, tag);

  assert.strictEqual(fakeTTDSpy.callCount, 1, 'text track display was created');

  Html5.TEST_VID = oldTestVid;
  browser.stub_IS_FIREFOX(oldIsFirefox);
  Component.registerComponent('TextTrackDisplay', oldTextTrackDisplay);

  player.dispose();
});

QUnit.test('shows the default caption track first', function(assert) {
  const player = TestHelpers.makePlayer();
  const track1 = {
    kind: 'captions',
    label: 'English',
    language: 'en',
    src: 'en.vtt',
    default: true
  };
  const track2 = {
    kind: 'captions',
    label: 'Spanish',
    language: 'es',
    src: 'es.vtt'
  };

  // Add the text tracks
  const englishTrack = player.addRemoteTextTrack(track1, true).track;
  const spanishTrack = player.addRemoteTextTrack(track2, true).track;

  // Make sure the ready handler runs
  this.clock.tick(1);

  assert.ok(englishTrack.mode === 'showing', 'English track should be showing');
  assert.ok(spanishTrack.mode === 'disabled', 'Spanish track should not be showing');
  player.dispose();
});

if (!Html5.supportsNativeTextTracks()) {
  QUnit.test('text track display should attach screen orientation change event handler', function(assert) {
    const oldScreen = window.screen;
    const removeHandlerSpy = sinon.spy();
    let changeHandlerSpy;
    let changeHandlerAttached;

    window.screen = {
      orientation: {
        addEventListener: (type, func) => {
          changeHandlerAttached = true;
          changeHandlerSpy = sinon.spy();
        },
        dispatchEvent: (type) => changeHandlerSpy(),
        removeEventListener: removeHandlerSpy
      }
    };

    const player = TestHelpers.makePlayer();

    this.clock.tick(1);

    assert.true(changeHandlerAttached, 'screen orientation change event handler was not attached');
    assert.strictEqual(changeHandlerSpy.callCount, 0, 'screen orientation change event handler should not be called');

    window.screen.orientation.dispatchEvent('change');

    assert.strictEqual(changeHandlerSpy.callCount, 1, 'screen orientation change event handler was not called');

    player.dispose();

    assert.strictEqual(
      removeHandlerSpy.callCount,
      1,
      'screen orientation change event handler was not removed during player dispose'
    );
    window.screen = oldScreen;
  });

  QUnit.test('selectedlanguagechange is triggered by a track mode change', function(assert) {
    const player = TestHelpers.makePlayer();
    const track1 = {
      kind: 'captions',
      label: 'English',
      language: 'en',
      src: 'en.vtt'
    };
    const spy = sinon.spy();
    const selectedLanguageHandler = function(event) {
      spy();
    };
    const englishTrack = player.addRemoteTextTrack(track1, true).track;

    player.textTracks().addEventListener('selectedlanguagechange', selectedLanguageHandler);
    englishTrack.mode = 'showing';

    assert.strictEqual(spy.callCount, 1, 'selectedlanguagechange event was fired');
    player.dispose();
    player.textTracks().removeEventListener('selectedlanguagechange', selectedLanguageHandler);
  });

  QUnit.test("if user-selected language is unavailable, don't pick a track to show", function(assert) {
    // The video has no default language but has ‘English’ captions only
    const player = TestHelpers.makePlayer();
    const track1 = {
      kind: 'captions',
      label: 'English',
      language: 'en',
      src: 'en.vtt'
    };
    const captionsButton = player.controlBar.getChild('SubsCapsButton');

    player.src({type: 'video/mp4', src: 'http://google.com'});
    // manualCleanUp = true by default
    const englishTrack = player.addRemoteTextTrack(track1, true).track;

    // Force 'es' as user-selected track
    player.cache_.selectedLanguage = { language: 'es', kind: 'captions' };

    this.clock.tick(1);
    player.play();

    assert.ok(!captionsButton.hasClass('vjs-hidden'), 'The captions button is shown');
    assert.ok(englishTrack.mode === 'disabled', 'English track should be disabled');
    player.dispose();
  });

  QUnit.test('the user-selected language takes priority over default language', function(assert) {
    // The video has ‘English’ captions as default, but has ‘Spanish’ captions also
    const player = TestHelpers.makePlayer();
    const track1 = {
      kind: 'captions',
      label: 'English',
      language: 'en',
      src: 'en.vtt',
      default: true
    };
    const track2 = {
      kind: 'captions',
      label: 'Spanish',
      language: 'es',
      src: 'es.vtt'
    };

    player.src({type: 'video/mp4', src: 'http://google.com'});
    // manualCleanUp = true by default
    const englishTrack = player.addRemoteTextTrack(track1, true).track;
    const spanishTrack = player.addRemoteTextTrack(track2, true).track;

    // Force 'es' as user-selected track
    player.cache_.selectedLanguage = { enabled: true, language: 'es', kind: 'captions' };
    this.clock.tick(1);

    assert.ok(spanishTrack.mode === 'showing', 'Spanish captions should be shown');
    assert.ok(englishTrack.mode === 'disabled', 'English captions should be hidden');
    player.dispose();
  });

  QUnit.test("don't select user language if it is an empty string", function(assert) {
    const player = TestHelpers.makePlayer();
    const track1 = {
      kind: 'captions',
      label: 'English',
      language: 'en',
      src: 'en.vtt'
    };
    const track2 = {
      kind: 'captions',
      label: 'Spanish',
      language: 'es',
      src: 'es.vtt'
    };
    const track3 = {
      kind: 'metadata',
      label: 'segment-metadata'
    };

    player.src({type: 'video/mp4', src: 'http://google.com'});
    // manualCleanUp = true by default
    const englishTrack = player.addRemoteTextTrack(track1, true).track;
    const spanishTrack = player.addRemoteTextTrack(track2, true).track;
    const metadataTrack = player.addRemoteTextTrack(track3, true).track;

    // Force empty string ('') as "user-selected" track
    player.cache_.selectedLanguage = { enabled: true, language: '', kind: 'captions' };
    this.clock.tick(1);

    assert.equal(spanishTrack.mode, 'disabled', 'Spanish captions should be disabled');
    assert.equal(englishTrack.mode, 'disabled', 'English captions should be disabled');
    assert.notEqual(metadataTrack.mode, 'showing', 'Metadata track should not be showing');

    // Force es as "user-selected" track
    player.cache_.selectedLanguage = { enabled: true, language: 'es', kind: 'captions' };
    player.trigger('loadedmetadata');

    assert.equal(spanishTrack.mode, 'showing', 'Spanish captions should be showing');
    assert.equal(englishTrack.mode, 'disabled', 'English captions should be disabled');
    assert.notEqual(metadataTrack.mode, 'showing', 'Metadata track should not be showing');

    player.dispose();
  });

  QUnit.test("matching both the selectedLanguage's language and kind takes priority over just matching the language", function(assert) {
    const player = TestHelpers.makePlayer();
    const track1 = {
      kind: 'captions',
      label: 'English',
      language: 'en',
      src: 'en.vtt'
    };
    const track2 = {
      kind: 'subtitles',
      label: 'English',
      language: 'en',
      src: 'en.vtt'
    };

    player.src({type: 'video/mp4', src: 'http://google.com'});
    // manualCleanUp = true by default
    const captionTrack = player.addRemoteTextTrack(track1, true).track;
    const subsTrack = player.addRemoteTextTrack(track2, true).track;

    // Force English captions as user-selected track
    player.cache_.selectedLanguage = { enabled: true, language: 'en', kind: 'captions' };
    this.clock.tick(1);

    assert.ok(captionTrack.mode === 'showing', 'Captions track should be preselected');
    assert.ok(subsTrack.mode === 'disabled', 'Subtitles track should remain disabled');
    player.dispose();
  });

  QUnit.test('the user-selected language is used for subsequent source changes', function(assert) {
    // Start with two captions tracks: English and Spanish
    const player = TestHelpers.makePlayer();
    const track1 = {
      kind: 'captions',
      label: 'English',
      language: 'en',
      src: 'en.vtt'
    };
    const track2 = {
      kind: 'captions',
      label: 'Spanish',
      language: 'es',
      src: 'es.vtt'
    };
    const tracks = player.tech_.remoteTextTracks();
    const captionsButton = player.controlBar.getChild('SubsCapsButton');
    let esCaptionMenuItem;
    let enCaptionMenuItem;

    player.src({type: 'video/mp4', src: 'http://google.com'});
    // manualCleanUp = true by default
    player.addRemoteTextTrack(track1, true);
    player.addRemoteTextTrack(track2, true);

    // Keep track of menu items
    esCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'es');
    enCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'en');

    // The user chooses Spanish
    player.play();
    esCaptionMenuItem.trigger('click');

    // Track mode changes on user-selection
    assert.ok(
      esCaptionMenuItem.track.mode === 'showing',
      'Spanish should be showing after selection'
    );
    assert.ok(
      enCaptionMenuItem.track.mode === 'disabled',
      'English should be disabled after selecting Spanish'
    );
    assert.deepEqual(
      player.cache_.selectedLanguage,
      { enabled: true, language: 'es', kind: 'captions' }
    );

    // Switch source and remove old tracks
    player.tech_.src({type: 'video/mp4', src: 'http://example.com'});
    while (tracks.length > 0) {
      player.removeRemoteTextTrack(tracks[0]);
    }
    // Add tracks for the new source
    // change the kind of track to subtitles
    track1.kind = 'subtitles';
    track2.kind = 'subtitles';
    const englishTrack = player.addRemoteTextTrack(track1, true).track;
    const spanishTrack = player.addRemoteTextTrack(track2, true).track;

    // Make sure player ready handler runs
    this.clock.tick(1);

    // Keep track of menu items
    esCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'es');
    enCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'en');

    // The user-selection should have persisted
    assert.ok(
      esCaptionMenuItem.track.mode === 'showing',
      'Spanish should remain showing'
    );
    assert.ok(
      enCaptionMenuItem.track.mode === 'disabled',
      'English should remain disabled'
    );
    assert.deepEqual(
      player.cache_.selectedLanguage,
      { enabled: true, language: 'es', kind: 'captions' }
    );

    assert.ok(spanishTrack.mode === 'showing', 'Spanish track remains showing');
    assert.ok(englishTrack.mode === 'disabled', 'English track remains disabled');
    player.dispose();
  });

  QUnit.test('the user-selected language is cleared on turning off captions', function(assert) {
    const player = TestHelpers.makePlayer();
    const track1 = {
      kind: 'captions',
      label: 'English',
      language: 'en',
      src: 'en.vtt'
    };
    const captionsButton = player.controlBar.getChild('SubsCapsButton');

    player.src({type: 'video/mp4', src: 'http://google.com'});
    // manualCleanUp = true by default
    const englishTrack = player.addRemoteTextTrack(track1, true).track;
    // Keep track of menu items
    const enCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'en');
    // we know the position of the OffTextTrackMenuItem
    const offMenuItem = captionsButton.items[1];

    // Select English initially
    player.play();
    enCaptionMenuItem.trigger('click');

    assert.deepEqual(
      player.cache_.selectedLanguage,
      { enabled: true, language: 'en', kind: 'captions' }, 'English track is selected'
    );
    assert.ok(englishTrack.mode === 'showing', 'English track should be showing');

    // Select the off button
    offMenuItem.trigger('click');

    assert.deepEqual(
      player.cache_.selectedLanguage,
      { enabled: false }, 'selectedLanguage is cleared'
    );
    assert.ok(englishTrack.mode === 'disabled', 'English track is disabled');
    player.dispose();
  });

  QUnit.test('a color can be constructed from a three digit hex code', function(assert) {
    const hex = '#f0e';

    // f gets mapped to ff -> 255 in decimal,
    // 0 gets mapped to 00 -> 0 in decimal,
    // e gets mapped to ee -> 238 in decimal.
    assert.equal(constructColor(hex, 1), 'rgba(255,0,238,1)');
  });

  QUnit.test('a color can be constructed from a six digit hex code', function(assert) {
    const hex = '#f604e2';

    // f6 -> 246 in decimal,
    // 04 -> 4 in decimal,
    // e2 -> 226 in decimal.
    assert.equal(constructColor(hex, 1), 'rgba(246,4,226,1)');
  });

  QUnit.test('an invalid hex code will throw an error', function(assert) {
    const hex = '#f';

    assert.throws(
      function() {
        constructColor(hex, 1);
      },
      new Error('Invalid color code provided, #f; must be formatted as e.g. #f0e or #f604e2.'),
      'colors must be valid hex codes.'
    );
  });

  const skipOnOldChrome = window.CSS.supports('inset-inline: 10px') ? 'test' : 'skip';

  QUnit[skipOnOldChrome]('text track display should overlay a video', function(assert) {
    const tag = document.createElement('video');

    tag.width = 320;
    tag.height = 180;
    const player = TestHelpers.makePlayer({}, tag);
    const textTrackDisplay = player.getChild('TextTrackDisplay');
    const textTrackDisplayStyle = textTrackDisplay.el().style;

    assert.ok(textTrackDisplayStyle.insetInline === '', 'text track display style insetInline equal to empty string');
    assert.ok(textTrackDisplayStyle.insetBlock === '', 'text track display style insetBlock equal to empty string');

    // video aspect ratio equal to NaN
    player.tech_.videoWidth = () => 0;
    player.tech_.videoHeight = () => 0;

    assert.ok(textTrackDisplayStyle.insetInline === '', 'text track display style insetInline equal to empty string');
    assert.ok(textTrackDisplayStyle.insetBlock === '', 'text track display style insetBlock equal to empty string');

    // video aspect ratio 2:1
    player.tech_.videoWidth = () => 100;
    player.tech_.videoHeight = () => 50;

    textTrackDisplay.updateDisplayOverlay();

    assert.ok(textTrackDisplayStyle.insetInline === '', 'text track display style insetInline equal to empty string');
    assert.ok(textTrackDisplayStyle.insetBlock === '10px', 'text track display style insetBlock equal to 10px');

    // video aspect ratio 4:3
    player.tech_.videoWidth = () => 100;
    player.tech_.videoHeight = () => 75;

    textTrackDisplay.updateDisplayOverlay();

    assert.ok(textTrackDisplayStyle.insetInline === '40px', 'text track display style insetInline equal to 40px');
    assert.ok(textTrackDisplayStyle.insetBlock === '', 'text track display style insetBlock equal to empty string');

    // video aspect ratio 16:9
    player.tech_.videoWidth = () => 320;
    player.tech_.videoHeight = () => 180;

    textTrackDisplay.updateDisplayOverlay();

    assert.ok(textTrackDisplayStyle.insetInline === '', 'text track display style insetInline equal to empty string');
    assert.ok(textTrackDisplayStyle.insetBlock === '', 'text track display style insetBlock equal to empty string');

    player.dispose();
  });

  QUnit.test('should use relative position for vjs-text-track-display element if environment does not support window.CSS', function(assert) {
    const cssDescriptor = Object.getOwnPropertyDescriptor(window, 'CSS');

    try {
      // Set conditions for the use of the style modifications. Temporarily
      // remove window.CSS to match the environment created by jsdom.
      // https://github.com/jsdom/jsdom/issues/3991
      delete window.CSS;

      const player = TestHelpers.makePlayer();
      const track1 = {
        kind: 'captions',
        label: 'English',
        language: 'en',
        src: 'en.vtt',
        default: true
      };

      // Add the text track
      player.addRemoteTextTrack(track1, true);

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

      // as if metadata was loaded
      player.textTrackDisplay.updateDisplayOverlay();

      // Make sure the ready handler runs
      this.clock.tick(1);

      const textTrack = window.document.querySelector('.vjs-text-track-display');

      assert.ok(textTrack.style.position === 'relative', 'Style of position for vjs-text-track-display element should be relative');
      assert.ok(textTrack.style.top === 'unset', 'Style of position for vjs-text-track-display element should be unset');
      assert.ok(textTrack.style.bottom === '0px', 'Style of bottom for vjs-text-track-display element should be 0px');
      player.dispose();
    } finally {
      // Restore window.CSS
      Object.defineProperty(window, 'CSS', cssDescriptor);
    }
  });

  QUnit.test('should use relative position for vjs-text-track-display element if browser does not support inset property', function(assert) {
    // Set conditions for the use of the style modifications
    window.CSS.supports = () => false;
    browser.IS_SMART_TV = () => true;

    const player = TestHelpers.makePlayer();
    const track1 = {
      kind: 'captions',
      label: 'English',
      language: 'en',
      src: 'en.vtt',
      default: true
    };

    // Add the text track
    player.addRemoteTextTrack(track1, true);

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

    // as if metadata was loaded
    player.textTrackDisplay.updateDisplayOverlay();

    // Make sure the ready handler runs
    this.clock.tick(1);

    const textTrack = window.document.querySelector('.vjs-text-track-display');

    assert.ok(textTrack.style.position === 'relative', 'Style of position for vjs-text-track-display element should be relative');
    assert.ok(textTrack.style.top === 'unset', 'Style of position for vjs-text-track-display element should be unset');
    assert.ok(textTrack.style.bottom === '0px', 'Style of bottom for vjs-text-track-display element should be 0px');
    player.dispose();
  });

  QUnit.test('track cue should use values of top, right, botton, left if browser does not support inset property', function(assert) {
    // Set conditions for the use of the style modifications
    window.CSS.supports = () => false;
    browser.IS_SMART_TV = () => true;

    const player = TestHelpers.makePlayer();
    const track1 = {
      kind: 'captions',
      label: 'English',
      language: 'en',
      src: 'en.vtt',
      default: true
    };

    // Add the text track
    player.addRemoteTextTrack(track1, true);

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

    // mock caption
    const textTrackDisplay = window.document.querySelector('.vjs-text-track-display').firstChild;
    const node = document.createElement('div');

    node.classList.add('vjs-text-track-cue');
    node.style.inset = '1px 2px 3px';
    const textnode = document.createTextNode('Sample text');

    node.appendChild(textnode);
    textTrackDisplay.appendChild(node);

    // avoid captions clear
    player.textTrackDisplay.clearDisplay = () => '';

    // as if metadata was loaded
    player.textTrackDisplay.updateDisplay();

    assert.ok(player.textTrackDisplay.el_.querySelector('.vjs-text-track-cue').style.left === 'unset', 'Style of left for vjs-text-track-cue element should be unset');
    assert.ok(player.textTrackDisplay.el_.querySelector('.vjs-text-track-cue').style.top === '1px', 'Style of top for vjs-text-track-cue element should be 1px');
    assert.ok(player.textTrackDisplay.el_.querySelector('.vjs-text-track-cue').style.right === '2px', 'Style of right for vjs-text-track-cue element should be 2px');
    player.dispose();
  });
}
