/* eslint-env qunit */
import document from 'global/document';
import sinon from 'sinon';
import TestHelpers from './test-helpers';
import FullscreenApi from '../../src/js/fullscreen-api.js';

QUnit.module('Player: User Actions: Click', {

  beforeEach() {
    this.clock = sinon.useFakeTimers();
    this.player = TestHelpers.makePlayer({controls: true});
  },

  afterEach() {
    this.player.dispose();
    this.clock.restore();
  }
});

QUnit.test('by default, click toggles play', function(assert) {
  let paused = true;

  this.player.paused = () => paused;
  this.player.play = sinon.spy();
  this.player.pause = sinon.spy();

  this.player.handleTechClick_({target: this.player.tech_.el_});

  assert.strictEqual(this.player.play.callCount, 1, 'has called play');
  assert.strictEqual(this.player.pause.callCount, 0, 'has not called pause');

  paused = false;
  this.player.handleTechClick_({target: this.player.tech_.el_});

  assert.strictEqual(this.player.play.callCount, 1, 'has called play, previously');
  assert.strictEqual(this.player.pause.callCount, 1, 'has called pause');
});

QUnit.test('when controls are disabled, click does nothing', function(assert) {
  let paused = true;

  this.player.controls(false);

  this.player.paused = () => paused;
  this.player.play = sinon.spy();
  this.player.pause = sinon.spy();

  this.player.handleTechClick_({target: this.player.tech_.el_});

  assert.strictEqual(this.player.play.callCount, 0, 'has not called play');
  assert.strictEqual(this.player.pause.callCount, 0, 'has not called pause');

  paused = false;
  this.player.handleTechClick_({target: this.player.tech_.el_});

  assert.strictEqual(this.player.play.callCount, 0, 'has not called play, previously');
  assert.strictEqual(this.player.pause.callCount, 0, 'has not called pause');
});

QUnit.test('when userActions.click is false, click does nothing', function(assert) {
  let paused = true;

  this.player.dispose();
  this.player = TestHelpers.makePlayer({
    controls: true,
    userActions: {
      click: false
    }
  });

  this.player.paused = () => paused;
  this.player.play = sinon.spy();
  this.player.pause = sinon.spy();

  this.player.handleTechClick_({target: this.player.tech_.el_});

  assert.strictEqual(this.player.play.callCount, 0, 'has not called play');
  assert.strictEqual(this.player.pause.callCount, 0, 'has not called pause');

  paused = false;
  this.player.handleTechClick_({target: this.player.tech_.el_});

  assert.strictEqual(this.player.play.callCount, 0, 'has not called play, previously');
  assert.strictEqual(this.player.pause.callCount, 0, 'has not called pause');
});

QUnit.test('when userActions.click is a function, that function is called instead of toggling play', function(assert) {
  let paused = true;
  const clickSpy = sinon.spy();

  this.player.dispose();
  this.player = TestHelpers.makePlayer({
    controls: true,
    userActions: {
      click: clickSpy
    }
  });

  this.player.paused = () => paused;
  this.player.play = sinon.spy();
  this.player.pause = sinon.spy();

  let event = {target: this.player.tech_.el_};

  this.player.handleTechClick_(event);

  assert.strictEqual(this.player.play.callCount, 0, 'has not called play');
  assert.strictEqual(this.player.pause.callCount, 0, 'has not called pause');
  assert.strictEqual(clickSpy.callCount, 1, 'has called the click handler');
  assert.strictEqual(clickSpy.getCall(0).args[0], event, 'has passed the event to the handler');

  paused = false;
  event = {target: this.player.tech_.el_};
  this.player.handleTechClick_(event);

  assert.strictEqual(this.player.play.callCount, 0, 'has not called play, previously');
  assert.strictEqual(this.player.pause.callCount, 0, 'has not called pause');
  assert.strictEqual(clickSpy.callCount, 2, 'has called the click handler');
  assert.strictEqual(clickSpy.getCall(1).args[0], event, 'has passed the event to the handler');
});

QUnit.module('Player: User Actions: Double Click', {

  beforeEach() {
    this.clock = sinon.useFakeTimers();
    this.player = TestHelpers.makePlayer({controls: true});
  },

  afterEach() {
    this.player.dispose();
    this.clock.restore();
  }
});

QUnit.test('by default, double-click opens fullscreen', function(assert) {
  let fullscreen = false;

  this.player.isFullscreen = () => fullscreen;
  this.player.requestFullscreen = sinon.spy();
  this.player.exitFullscreen = sinon.spy();

  this.player.handleTechDoubleClick_({target: this.player.tech_.el_});

  assert.strictEqual(this.player.requestFullscreen.callCount, 1, 'has gone fullscreen once');
  assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');

  fullscreen = true;
  this.player.handleTechDoubleClick_({target: this.player.tech_.el_});

  assert.strictEqual(this.player.requestFullscreen.callCount, 1, 'has gone fullscreen once');
  assert.strictEqual(this.player.exitFullscreen.callCount, 1, 'has exited fullscreen');
});

QUnit.test('in document picture in picture mode, double-click exits pip', function(assert) {
  this.player.isInPictureInPicture = () => true;
  this.player.exitPictureInPicture = sinon.spy();
  this.player.requestFullscreen = sinon.spy();
  this.player.exitFullscreen = sinon.spy();

  this.player.handleTechDoubleClick_({target: this.player.tech_.el_});

  assert.strictEqual(this.player.exitPictureInPicture.callCount, 1, 'has exited pip once');
  assert.strictEqual(this.player.requestFullscreen.callCount, 0, 'has not entered fullscreen');
  assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
});

QUnit.test('when controls are disabled, double-click does nothing', function(assert) {
  let fullscreen = false;

  this.player.controls(false);

  this.player.isFullscreen = () => fullscreen;
  this.player.requestFullscreen = sinon.spy();
  this.player.exitFullscreen = sinon.spy();

  this.player.handleTechDoubleClick_({target: this.player.tech_.el_});

  assert.strictEqual(this.player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
  assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');

  fullscreen = true;
  this.player.handleTechDoubleClick_({target: this.player.tech_.el_});

  assert.strictEqual(this.player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
  assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
});

QUnit.test('when userActions.doubleClick is false, double-click does nothing', function(assert) {
  let fullscreen = false;

  this.player.dispose();
  this.player = TestHelpers.makePlayer({
    controls: true,
    userActions: {
      doubleClick: false
    }
  });

  this.player.isFullscreen = () => fullscreen;
  this.player.requestFullscreen = sinon.spy();
  this.player.exitFullscreen = sinon.spy();

  this.player.handleTechDoubleClick_({target: this.player.tech_.el_});

  assert.strictEqual(this.player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
  assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');

  fullscreen = true;
  this.player.handleTechDoubleClick_({target: this.player.tech_.el_});

  assert.strictEqual(this.player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
  assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
});

QUnit.test('when userActions.doubleClick is a function, that function is called instead of going fullscreen', function(assert) {
  let fullscreen = false;

  const doubleClickSpy = sinon.spy();

  this.player.dispose();
  this.player = TestHelpers.makePlayer({
    controls: true,
    userActions: {
      doubleClick: doubleClickSpy
    }
  });

  this.player.isFullscreen = () => fullscreen;
  this.player.requestFullscreen = sinon.spy();
  this.player.exitFullscreen = sinon.spy();

  let event = {target: this.player.tech_.el_};

  this.player.handleTechDoubleClick_(event);

  assert.strictEqual(this.player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
  assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
  assert.strictEqual(doubleClickSpy.callCount, 1, 'has called the doubleClick handler');
  assert.strictEqual(doubleClickSpy.getCall(0).args[0], event, 'has passed the event to the handler');

  fullscreen = true;
  event = {target: this.player.tech_.el_};
  this.player.handleTechDoubleClick_(event);

  assert.strictEqual(this.player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
  assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
  assert.strictEqual(doubleClickSpy.callCount, 2, 'has called the doubleClick handler');
  assert.strictEqual(doubleClickSpy.getCall(1).args[0], event, 'has passed the event to the handler');
});

QUnit.module('Player: User Actions: Hotkeys', {

  beforeEach() {
    this.clock = sinon.useFakeTimers();
    this.player = TestHelpers.makePlayer();
  },

  afterEach() {
    this.player.dispose();
    this.clock.restore();
  }
});

const mockKeyDownEvent = (key) => {
  return {
    preventDefault() {},
    stopPropagation() {},
    type: 'keydown',
    key
  };
};

const defaultKeyTests = {
  fullscreen(player, assert, positive) {
    let fullscreen;

    if (document[FullscreenApi.fullscreenEnabled] === false) {
      assert.ok(true, 'skipped fullscreen test because not supported');
      assert.ok(true, 'skipped fullscreen test because not supported');
      assert.ok(true, 'skipped fullscreen test because not supported');
      assert.ok(true, 'skipped fullscreen test because not supported');
      return;
    }

    player.isFullscreen = () => fullscreen;
    player.requestFullscreen = sinon.spy();
    player.exitFullscreen = sinon.spy();

    fullscreen = false;
    player.handleKeyDown(mockKeyDownEvent('f'));

    if (positive) {
      assert.strictEqual(player.requestFullscreen.callCount, 1, 'has gone fullscreen');
      assert.strictEqual(player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
    } else {
      assert.strictEqual(player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
      assert.strictEqual(player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
    }

    fullscreen = true;
    player.handleKeyDown(mockKeyDownEvent('f'));

    if (positive) {
      assert.strictEqual(player.requestFullscreen.callCount, 1, 'has gone fullscreen');
      assert.strictEqual(player.exitFullscreen.callCount, 1, 'has exited fullscreen');
    } else {
      assert.strictEqual(player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
      assert.strictEqual(player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
    }
  },
  mute(player, assert, positive) {
    let muted = false;

    player.muted = sinon.spy((val) => {
      if (val !== undefined) {
        muted = val;
      }
      return muted;
    });

    player.handleKeyDown(mockKeyDownEvent('m'));

    if (positive) {
      assert.strictEqual(player.muted.callCount, 2, 'muted was called twice (get and set)');
      assert.strictEqual(player.muted.lastCall.args[0], true, 'most recent call was to mute');
    } else {
      assert.strictEqual(player.muted.callCount, 0, 'muted was not called');
    }

    player.handleKeyDown(mockKeyDownEvent('m'));

    if (positive) {
      assert.strictEqual(player.muted.callCount, 4, 'muted was called twice (get and set)');
      assert.strictEqual(player.muted.lastCall.args[0], false, 'most recent call was to unmute');
    } else {
      assert.strictEqual(player.muted.callCount, 0, 'muted was not called');
    }
  },
  playPause(player, assert, positive) {
    let paused;

    player.paused = () => paused;
    player.pause = sinon.spy();
    player.play = sinon.spy();

    paused = true;
    player.handleKeyDown(mockKeyDownEvent('k'));

    if (positive) {
      assert.strictEqual(player.pause.callCount, 0, 'has not paused');
      assert.strictEqual(player.play.callCount, 1, 'has played');
    } else {
      assert.strictEqual(player.pause.callCount, 0, 'has not paused');
      assert.strictEqual(player.play.callCount, 0, 'has not played');
    }

    paused = false;
    player.handleKeyDown(mockKeyDownEvent('k'));

    if (positive) {
      assert.strictEqual(player.pause.callCount, 1, 'has paused');
      assert.strictEqual(player.play.callCount, 1, 'has played');
    } else {
      assert.strictEqual(player.pause.callCount, 0, 'has not paused');
      assert.strictEqual(player.play.callCount, 0, 'has not played');
    }

    paused = true;
    player.handleKeyDown(mockKeyDownEvent(' '));

    if (positive) {
      assert.strictEqual(player.pause.callCount, 1, 'has paused');
      assert.strictEqual(player.play.callCount, 2, 'has played twice');
    } else {
      assert.strictEqual(player.pause.callCount, 0, 'has not paused');
      assert.strictEqual(player.play.callCount, 0, 'has not played');
    }

    paused = false;
    player.handleKeyDown(mockKeyDownEvent(' '));

    if (positive) {
      assert.strictEqual(player.pause.callCount, 2, 'has paused twice');
      assert.strictEqual(player.play.callCount, 2, 'has played twice');
    } else {
      assert.strictEqual(player.pause.callCount, 0, 'has not paused');
      assert.strictEqual(player.play.callCount, 0, 'has not played');
    }
  }
};

QUnit.test('by default, hotkeys are disabled', function(assert) {
  assert.expect(14);
  defaultKeyTests.fullscreen(this.player, assert, false);
  defaultKeyTests.mute(this.player, assert, false);
  defaultKeyTests.playPause(this.player, assert, false);
});

QUnit.test('when userActions.hotkeys is true, hotkeys are enabled', function(assert) {
  this.player.dispose();
  this.player = TestHelpers.makePlayer({
    controls: true,
    userActions: {
      hotkeys: true
    }
  });

  assert.expect(16);
  defaultKeyTests.fullscreen(this.player, assert, true);
  defaultKeyTests.mute(this.player, assert, true);
  defaultKeyTests.playPause(this.player, assert, true);
});

QUnit.test('when userActions.hotkeys is an object, hotkeys are enabled', function(assert) {
  this.player.dispose();
  this.player = TestHelpers.makePlayer({
    controls: true,
    userActions: {
      hotkeys: {}
    }
  });

  assert.expect(16);
  defaultKeyTests.fullscreen(this.player, assert, true);
  defaultKeyTests.mute(this.player, assert, true);
  defaultKeyTests.playPause(this.player, assert, true);
});

QUnit.test('when userActions.hotkeys.fullscreenKey can be a function', function(assert) {
  if (document[FullscreenApi.fullscreenEnabled] === false) {
    assert.expect(0);
    return;
  }

  this.player.dispose();
  this.player = TestHelpers.makePlayer({
    controls: true,
    userActions: {
      hotkeys: {
        fullscreenKey: sinon.spy((e) => e.key === 'x')
      }
    }
  });

  let fullscreen;

  this.player.isFullscreen = () => fullscreen;
  this.player.requestFullscreen = sinon.spy();
  this.player.exitFullscreen = sinon.spy();

  fullscreen = false;
  this.player.handleKeyDown(mockKeyDownEvent('f'));

  assert.strictEqual(this.player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
  assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');

  this.player.handleKeyDown(mockKeyDownEvent('x'));

  assert.strictEqual(this.player.requestFullscreen.callCount, 1, 'has gone fullscreen');
  assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');

  fullscreen = true;
  this.player.handleKeyDown(mockKeyDownEvent('x'));

  assert.strictEqual(this.player.requestFullscreen.callCount, 1, 'has gone fullscreen');
  assert.strictEqual(this.player.exitFullscreen.callCount, 1, 'has exited fullscreen');
});

QUnit.test('when userActions.hotkeys.muteKey can be a function', function(assert) {
  this.player.dispose();
  this.player = TestHelpers.makePlayer({
    controls: true,
    userActions: {
      hotkeys: {
        muteKey: sinon.spy((e) => e.key === 'x')
      }
    }
  });

  let muted = false;

  this.player.muted = sinon.spy((val) => {
    if (val !== undefined) {
      muted = val;
    }
    return muted;
  });

  this.player.handleKeyDown(mockKeyDownEvent('m'));

  assert.strictEqual(this.player.muted.callCount, 0, 'muted was not called');

  this.player.handleKeyDown(mockKeyDownEvent('x'));

  assert.strictEqual(this.player.muted.callCount, 2, 'muted was called twice (get and set)');
  assert.strictEqual(this.player.muted.lastCall.args[0], true, 'most recent call was to mute');

  this.player.handleKeyDown(mockKeyDownEvent('x'));

  assert.strictEqual(this.player.muted.callCount, 4, 'muted was called twice (get and set)');
  assert.strictEqual(this.player.muted.lastCall.args[0], false, 'most recent call was to unmute');
});

QUnit.test('when userActions.hotkeys.playPauseKey can be a function', function(assert) {
  this.player.dispose();
  this.player = TestHelpers.makePlayer({
    controls: true,
    userActions: {
      hotkeys: {
        playPauseKey: sinon.spy((e) => e.key === 'x')
      }
    }
  });

  let paused;

  this.player.paused = () => paused;
  this.player.pause = sinon.spy();
  this.player.play = sinon.spy();

  paused = true;
  this.player.handleKeyDown(mockKeyDownEvent('k'));
  this.player.handleKeyDown(mockKeyDownEvent(' '));

  assert.strictEqual(this.player.pause.callCount, 0, 'has not paused');
  assert.strictEqual(this.player.play.callCount, 0, 'has not played');

  this.player.handleKeyDown(mockKeyDownEvent('x'));

  assert.strictEqual(this.player.pause.callCount, 0, 'has not paused');
  assert.strictEqual(this.player.play.callCount, 1, 'has played');

  paused = false;
  this.player.handleKeyDown(mockKeyDownEvent('x'));

  assert.strictEqual(this.player.pause.callCount, 1, 'has paused');
  assert.strictEqual(this.player.play.callCount, 1, 'has played');
});

QUnit.test('hotkeys are ignored when focus is in a contenteditable element', function(assert) {
  this.player.dispose();
  this.player = TestHelpers.makePlayer({
    controls: true,
    userActions: {
      hotkeys: true
    }
  });

  const div = document.createElement('div');

  div.contentEditable = 'true';
  this.player.el_.appendChild(div);
  div.focus();

  assert.expect(14);
  defaultKeyTests.fullscreen(this.player, assert, false);
  defaultKeyTests.mute(this.player, assert, false);
  defaultKeyTests.playPause(this.player, assert, false);
});

QUnit.test('hotkeys are ignored when focus is in a textarea', function(assert) {
  this.player.dispose();
  this.player = TestHelpers.makePlayer({
    controls: true,
    userActions: {
      hotkeys: true
    }
  });

  const textarea = document.createElement('textarea');

  this.player.el_.appendChild(textarea);
  textarea.focus();

  assert.expect(14);
  defaultKeyTests.fullscreen(this.player, assert, false);
  defaultKeyTests.mute(this.player, assert, false);
  defaultKeyTests.playPause(this.player, assert, false);
});

QUnit.test('hotkeys are ignored when focus is in a text input', function(assert) {
  this.player.dispose();
  this.player = TestHelpers.makePlayer({
    controls: true,
    userActions: {
      hotkeys: true
    }
  });

  const input = document.createElement('input');

  input.type = 'text';
  this.player.el_.appendChild(input);
  input.focus();

  assert.expect(14);
  defaultKeyTests.fullscreen(this.player, assert, false);
  defaultKeyTests.mute(this.player, assert, false);
  defaultKeyTests.playPause(this.player, assert, false);
});

QUnit.test('hotkeys are NOT ignored when focus is on a button element', function(assert) {
  this.player.dispose();
  this.player = TestHelpers.makePlayer({
    controls: true,
    userActions: {
      hotkeys: true
    }
  });

  const button = document.createElement('button');

  this.player.el_.appendChild(button);
  button.focus();

  assert.expect(16);
  defaultKeyTests.fullscreen(this.player, assert, true);
  defaultKeyTests.mute(this.player, assert, true);
  defaultKeyTests.playPause(this.player, assert, true);
});

QUnit.test('hotkeys are NOT ignored when focus is on a button input', function(assert) {
  this.player.dispose();
  this.player = TestHelpers.makePlayer({
    controls: true,
    userActions: {
      hotkeys: true
    }
  });

  const input = document.createElement('input');

  input.type = 'button';
  this.player.el_.appendChild(input);
  input.focus();

  assert.expect(16);
  defaultKeyTests.fullscreen(this.player, assert, true);
  defaultKeyTests.mute(this.player, assert, true);
  defaultKeyTests.playPause(this.player, assert, true);
});
