import sinon from 'sinon';
import { moduleFor, test } from 'ember-qunit';
import Service from 'ember-service';
import EmberObject from 'ember-object';
import $ from 'jquery';
import { LEFT_COLUMN_WIDTH_CLOSED, LEFT_COLUMN_WIDTH_OPEN,
        PERSISTENT_PLAYER_BOTTOM_MARGIN, PERSISTENT_PLAYER_LEFT_MARGIN } from 'web-client/services/layout';
import { getAbsoluteElementPosition, getFixedElementPosition } from 'web-client/utilities/element-position';
import MockPersistentPlayerContent from 'web-client/tests/helpers/mock-persistent-player-content-service';

// These constants are intended to be private within the player service,
// so they are not exported, but we are using them here to test the private
// methods that care about them
const POS_STATE_FULLSIZE = 'state_full';
const POS_STATE_MINI = 'state_mini';
const POS_STATE_ANIMATING = 'state_anim';

let mockGlobals = Service.extend({});
let mockLayout = Service.extend({});
let mockStorage = Service.extend({});

moduleFor('service:persistent-player', 'Unit | Service | persistent-player', {
  beforeEach() {
    this.mockChannel = EmberObject.create({
      id: 'channelName'
    });

    this.mockVOD = EmberObject.create({
      id: '12345',
      owner: this.mockChannel
    });

    this.register('service:globals', mockGlobals);
    this.register('service:layout', mockLayout);
    this.register('service:storage', mockStorage);
    this.register('service:persistentPlayerContent', MockPersistentPlayerContent);

    this.service = this.subject();

    this.service.get('layout').setProperties({
      fullSizePlayerDimensions: {
        height: 600,
        width: 900
      },
      persistentPlayerDimensions: {
        height: 200,
        width: 300
      }
    });
  }
});

test('isVisible is false when there is no content', function(assert) {
  this.service.set('persistentPlayerContent.hasContent', false);
  this.service.set('shouldShow', true);
  assert.notOk(this.service.get('isVisible'));
});

test('isVisible is false when shouldShow is false', function(assert) {
  this.service.set('persistentPlayerContent.hasContent', true);
  this.service.set('shouldShow', false);
  assert.notOk(this.service.get('isVisible'));
});

test('isVisible is true when there is content and shouldShow is true', function(assert) {
  this.service.set('persistentPlayerContent.hasContent', true);
  this.service.set('shouldShow', true);
  assert.ok(this.service.get('isVisible'));
});

test('when the player has viewable content', function(assert) {
  this.service.set('persistentPlayerContent.hasPlayableContent', true);
  assert.ok(this.service.get('shouldPersist'), 'shouldPersist is true');
});

test('when the player does not have viewable content', function(assert) {
  this.service.set('persistentPlayerContent.hasPlayableContent', false);
  assert.notOk(this.service.get('shouldPersist'), 'shouldPersist is false ');
});

test('when the player is both visible and mini', function(assert) {
  this.service.set('persistentPlayerContent.hasContent', true);
  this.service.set('shouldShow', true);
  this.service.setMini(true);
  assert.ok(this.service.get('isMiniPlayerVisible'), 'isMiniPlayerVisible is true');
});

test('when the player is not visible', function(assert) {
  this.service.set('shouldShow', false);
  this.service.setMini(true);
  assert.notOk(this.service.get('isMiniPlayerVisible'), 'isMiniPlayerVisible is false');
});

test('when the player is not mini', function(assert) {
  this.service.set('persistentPlayerContent.hasContent', true);
  this.service.set('shouldShow', true);
  this.service.setMini(false);
  assert.notOk(this.service.get('isMiniPlayerVisible'), 'isMiniPlayerVisible is false');
});

test('when the player is locked in full size', function(assert) {
  this.service.set('persistentPlayerContent.hasContent', true);
  this.service.set('lockedInFullSize', true);
  assert.notOk(this.service.get('shouldPersist'), 'shouldPersist should be false');
});

test('persistentPlayerLocation places the player correctly when the sidebar is open', function(assert) {
  this.service.set('layout.isLeftColumnClosed', false);

  let layoutHeight = this.service.get('layout.persistentPlayerDimensions.height');
  let playerLocation = this.service.get('persistentPlayerLocation');
  assert.equal(playerLocation.top, window.innerHeight - layoutHeight - PERSISTENT_PLAYER_BOTTOM_MARGIN);
  assert.equal(playerLocation.left, LEFT_COLUMN_WIDTH_OPEN + PERSISTENT_PLAYER_LEFT_MARGIN);
});

test('persistentPlayerLocation places the player correctly when the sidebar is closed', function(assert) {
  this.service.set('layout.isLeftColumnClosed', true);

  let layoutHeight = this.service.get('layout.persistentPlayerDimensions.height');
  let playerLocation = this.service.get('persistentPlayerLocation');
  assert.equal(playerLocation.top, window.innerHeight - layoutHeight - PERSISTENT_PLAYER_BOTTOM_MARGIN);
  assert.equal(playerLocation.left, LEFT_COLUMN_WIDTH_CLOSED + PERSISTENT_PLAYER_LEFT_MARGIN);
});

test('playerDimensions returns fullSizePlayerDimensions when isMini is false', function(assert) {
  this.service.setMini(false);
  let expectedDimensions = this.service.get('layout.fullSizePlayerDimensions');

  assert.equal(this.service.get('playerDimensions'), expectedDimensions);
});

test('playerDimensions returns persistentPlayerDimensions when isMini is true', function(assert) {
  this.service.setMini(true);
  let expectedDimensions = this.service.get('layout.persistentPlayerDimensions');

  assert.equal(this.service.get('playerDimensions'), expectedDimensions);
});

test('_queueAnimation | when the destinationState is the same as the current state', function(assert) {
  this.service.setMini(false);
  this.service._queueAnimation(POS_STATE_FULLSIZE, () => {});
  assert.equal(this.service.get('queuedAnimation'), null, 'it should not set the queued animation');
});

test('_queueAnimation | when the destinationState differs from the the current state', function(assert) {
  this.service.setMini(false);
  let callback = () => {};
  let expected = {
    destinationState: POS_STATE_MINI,
    afterAnimate: callback
  };

  this.service._queueAnimation(POS_STATE_MINI, callback);
  assert.propEqual(this.service.get('queuedAnimation'), expected, 'it should set the queued animation');
});

test('_setAnimationCoords sets the animationCoords position to the current player position', function(assert) {
  let playerElement = $('<div id="mock_player"></div>');
  playerElement.css({
    position: 'absolute',
    top: '0px',
    left: '0px'
  });
  $('body').append(playerElement);

  this.service.set('playerElement', playerElement);
  this.service._setAnimationCoords(POS_STATE_FULLSIZE);
  let coords = this.service.get('animationCoords');

  assert.equal(coords.originX, 0);
  assert.equal(coords.originY, 0);
});

test('_setAnimationCoords converts the mini player\'s position to absolute when the destinationState is POS_STATE_FULLSIZE', function(assert) {
  this.service.setMini(true);

  let playerParent = $('<div id="player_parent"></div>');
  let playerElement = $('<div id="mock_player"></div>');

  playerParent.css({
    position: 'absolute',
    top: '200px',
    left: '200px'
  });

  playerElement.css({
    position: 'fixed',
    top: '0px',
    left: '0px'
  });

  playerParent.append(playerElement);
  $('body').append(playerParent);

  this.service.set('playerElement', playerElement);
  this.service._setAnimationCoords(POS_STATE_FULLSIZE);

  let expectedPosition = getAbsoluteElementPosition(playerElement);
  let coords = this.service.get('animationCoords');

  assert.equal(coords.position, 'absolute');
  assert.equal(coords.originX, expectedPosition.left);
  assert.equal(coords.originY, expectedPosition.top);
});

test('_setAnimationCoords converts the full-sized player\'s position to fixed when destinationState is POS_STATE_MINI', function(assert) {
  this.service.setMini(false);

  let playerParent = $('<div id="player_parent"></div>');
  let playerElement = $('<div id="mock_player"></div>');

  playerParent.css({
    position: 'absolute',
    top: '200px',
    left: '200px'
  });

  playerElement.css({
    position: 'fixed',
    top: '0px',
    left: '0px'
  });

  playerParent.append(playerElement);
  $('body').append(playerParent);

  this.service.set('playerElement', playerElement);
  this.service._setAnimationCoords(POS_STATE_MINI);

  let expectedPosition = getFixedElementPosition(playerElement);
  let coords = this.service.get('animationCoords');

  assert.equal(coords.position, 'fixed');
  assert.equal(coords.originX, expectedPosition.left);
  assert.equal(coords.originY, expectedPosition.top);
});

test('_getDestinationTransform when resizing from full-size to mini', function(assert) {
  let originLocation = {
    top: 455,
    left: 270
  };

  let destinationLocation = this.service.get('persistentPlayerLocation');
  let originDimensions = this.service.get('layout.fullSizePlayerDimensions');
  let destinationDimensions = this.service.get('layout.persistentPlayerDimensions');

  let expectedScaleX = destinationDimensions.width / originDimensions.width;
  let expectedScaleY = destinationDimensions.height / originDimensions.height;
  let expectedTranslateX = destinationLocation.left - originLocation.left;
  let expectedTranslateY = destinationLocation.top - originLocation.top;

  let transform = this.service._getDestinationTransform(POS_STATE_MINI, originLocation);

  assert.equal(transform.scaleX, expectedScaleX, 'computes the proper X scale factor');
  assert.equal(transform.scaleY, expectedScaleY, 'computes the proper Y scale factor');
  assert.equal(transform.translateX, expectedTranslateX, 'computes the proper X translation');
  assert.equal(transform.translateY, expectedTranslateY, 'computes the proper Y translation');
});

test('_getDestinationTransform when resizing from mini to full-size', function(assert) {
  let originLocation = this.service.get('persistentPlayerLocation');

  let destinationLocation = {
    top: 455,
    left: 0
  };

  this.service.setMini(true);
  this.service.set('fullSizePlayerLocation', destinationLocation);

  let originDimensions = this.service.get('layout.persistentPlayerDimensions');
  let destinationDimensions = this.service.get('layout.fullSizePlayerDimensions');

  let expectedScaleX = destinationDimensions.width / originDimensions.width;
  let expectedScaleY = destinationDimensions.height / originDimensions.height;
  let expectedTranslateX = destinationLocation.left - originLocation.left;
  let expectedTranslateY = destinationLocation.top - originLocation.top;

  let transform = this.service._getDestinationTransform(POS_STATE_FULLSIZE, originLocation);

  assert.equal(transform.scaleX, expectedScaleX, 'computes the proper X scale factor');
  assert.equal(transform.scaleY, expectedScaleY, 'computes the proper Y scale factor');
  assert.equal(transform.translateX, expectedTranslateX, 'computes the proper X translation');
  assert.equal(transform.translateY, expectedTranslateY, 'computes the proper Y translation');
});

test('_flushAnimations performs the appropriate sequence of state changes to animate the player', function(assert) {
  assert.expect(2);

  let done = assert.async();
  let mockElement = $('<div></div>');

  this.service.setProperties({
    shouldShow: true,
    playerElement: mockElement
  });

  this.service._queueAnimation(POS_STATE_MINI, () => {
    assert.ok(this.service.get('isAnimating'), 'the animation should run');
    done();
  });

  this.service._flushAnimations();

  assert.ok(this.service.get('isPreAnimation'), 'the animation should be prepared after flush');

  mockElement.trigger('transitionend');
});

test('_flushAnimations | when there is a queued animation when it finishes', function(assert) {
  assert.expect(3);

  let done = assert.async();
  let mockElement = $('<div></div>');

  this.service.setProperties({
    shouldShow: true,
    playerElement: mockElement
  });

  this.service._queueAnimation(POS_STATE_MINI, () => {
    assert.ok(this.service.get('isAnimating'), 'the animation should run');

    this.service._queueAnimation(POS_STATE_FULLSIZE, () => {});
    sinon.stub(this.service, '_flushAnimations', () => {
      assert.ok(true, '_flushAnimations gets invoked again');
      done();
    });
  });

  this.service._flushAnimations();
  assert.ok(this.service.get('isPreAnimation'), 'the animation should be prepared after flush');

  mockElement.trigger('transitionend');
});

test('_flushAnimations | when the player isn\'t visible', function(assert) {
  let callback = sinon.spy();
  let mockElement = $('<div></div>');

  this.service.setProperties({
    shouldShow: false,
    playerElement: mockElement
  });

  this.service._queueAnimation(POS_STATE_MINI, callback);

  this.service._flushAnimations();

  assert.notOk(this.service.get('isPreAnimation'));
  mockElement.trigger('transitionend');
  assert.equal(callback.called, false);
});

test('setMini', function(assert) {
  this.service.setMini(false);
  assert.equal(this.service.get('positionState'), POS_STATE_FULLSIZE, 'sets the position state to POS_STATE_FULLSIZE when called with false');
  this.service.setMini(true);
  assert.equal(this.service.get('positionState'), POS_STATE_MINI, 'sets the position state to POS_STATE_MINI when called with true');
});

test('shrink | when there is no animation running' , function(assert) {
  let flushSpy = sinon.spy(this.service, '_flushAnimations');
  this.service.setMini(false);

  this.service.shrink();

  assert.equal(this.service.get('queuedAnimation.destinationState'), POS_STATE_MINI, 'it should queue an animation to shrink');
  assert.ok(flushSpy.called, 'it should immediately invoke _flushAnimations');
});

test('shrink | when there is an animation currently running' , function(assert) {
  let flushSpy = sinon.spy(this.service, '_flushAnimations');

  this.service.set('positionState', POS_STATE_ANIMATING);
  this.service.shrink();

  assert.equal(this.service.get('queuedAnimation.destinationState'), POS_STATE_MINI, 'it should queue an animation to shrink');
  assert.ok(flushSpy.notCalled, 'it should not invoke _flushAnimations');
});

test('anchor | when there is no animation running' , function(assert) {
  let flushSpy = sinon.spy(this.service, '_flushAnimations');

  this.service.setMini(true);

  this.service.anchor();

  assert.equal(this.service.get('queuedAnimation.destinationState'), POS_STATE_FULLSIZE, 'it should queue an animation to anchor');
  assert.ok(flushSpy.called, 'it should immediately invoke _flushAnimations');
});

test('anchor | when there is an animation currently running' , function(assert) {
  let flushSpy = sinon.spy(this.service, '_flushAnimations');

  this.service.set('positionState', POS_STATE_ANIMATING);
  this.service.anchor();

  assert.equal(this.service.get('queuedAnimation.destinationState'), POS_STATE_FULLSIZE, 'it should queue an animation to anchor');
  assert.ok(flushSpy.notCalled, 'it should not invoke _flushAnimations');
});

test('syncPersistenceSetting', function(assert) {
  this.service.set('storage.persistenceEnabled', undefined);
  this.service.syncPersistenceSetting();
  assert.ok(this.service.get('persistenceEnabled'), 'sets the value to true if the value isn`t defined');

  this.service.set('storage.persistenceEnabled', 'false');
  this.service.syncPersistenceSetting();
  assert.notOk(this.service.get('persistenceEnabled'), 'sets the value to false if the value is false');

  this.service.set('storage.persistenceEnabled', 'true');
  this.service.syncPersistenceSetting();
  assert.ok(this.service.get('persistenceEnabled'), 'sets the value to true if the value is true');
});
