/**
 * @license
 * Copyright Google Inc. All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */
import {AnimationMetadata, animate, style} from '@angular/animations';

import {AnimationStyleNormalizer, NoopAnimationStyleNormalizer} from '../../src/dsl/style_normalization/animation_style_normalizer';
import {AnimationDriver} from '../../src/render/animation_driver';
import {getBodyNode} from '../../src/render/shared';
import {TimelineAnimationEngine} from '../../src/render/timeline_animation_engine';
import {MockAnimationDriver, MockAnimationPlayer} from '../../testing/src/mock_animation_driver';

(function() {
  const defaultDriver = new MockAnimationDriver();

  function makeEngine(body: any, driver?: AnimationDriver, normalizer?: AnimationStyleNormalizer) {
    return new TimelineAnimationEngine(
        body, driver || defaultDriver, normalizer || new NoopAnimationStyleNormalizer());
  }

  // these tests are only mean't to be run within the DOM
  if (isNode) return;

  describe('TimelineAnimationEngine', () => {
    let element: any;

    beforeEach(() => {
      MockAnimationDriver.log = [];
      element = document.createElement('div');
      document.body.appendChild(element);
    });

    afterEach(() => document.body.removeChild(element));

    it('should animate a timeline', () => {
      const engine = makeEngine(getBodyNode());
      const steps = [style({height: 100}), animate(1000, style({height: 0}))];
      expect(MockAnimationDriver.log.length).toEqual(0);
      invokeAnimation(engine, element, steps);
      expect(MockAnimationDriver.log.length).toEqual(1);
    });

    it('should not destroy timeline-based animations after they have finished', () => {
      const engine = makeEngine(getBodyNode());

      const log: string[] = [];
      function capture(value: string) {
        return () => { log.push(value); };
      }

      const steps = [style({height: 0}), animate(1000, style({height: 500}))];

      const player = invokeAnimation(engine, element, steps);
      player.onDone(capture('done'));
      player.onDestroy(capture('destroy'));
      expect(log).toEqual([]);

      player.finish();
      expect(log).toEqual(['done']);

      player.destroy();
      expect(log).toEqual(['done', 'destroy']);
    });

    it('should normalize the style values that are animateTransitioned within an a timeline animation',
       () => {
         const engine =
             makeEngine(getBodyNode(), defaultDriver, new SuffixNormalizer('-normalized'));

         const steps = [
           style({width: '333px'}),
           animate(1000, style({width: '999px'})),
         ];

         const player = invokeAnimation(engine, element, steps) as MockAnimationPlayer;
         expect(player.keyframes).toEqual([
           {'width-normalized': '333px-normalized', offset: 0},
           {'width-normalized': '999px-normalized', offset: 1}
         ]);
       });

    it('should normalize `*` values', () => {
      const driver = new SuperMockDriver();
      const engine = makeEngine(getBodyNode(), driver);

      const steps = [
        style({width: '*'}),
        animate(1000, style({width: '999px'})),
      ];

      const player = invokeAnimation(engine, element, steps) as MockAnimationPlayer;
      expect(player.keyframes).toEqual([{width: '*star*', offset: 0}, {width: '999px', offset: 1}]);
    });
  });
})();

function invokeAnimation(
    engine: TimelineAnimationEngine, element: any, steps: AnimationMetadata | AnimationMetadata[],
    id: string = 'id') {
  engine.register(id, steps);
  return engine.create(id, element);
}

class SuffixNormalizer extends AnimationStyleNormalizer {
  constructor(private _suffix: string) { super(); }

  normalizePropertyName(propertyName: string, errors: string[]): string {
    return propertyName + this._suffix;
  }

  normalizeStyleValue(
      userProvidedProperty: string, normalizedProperty: string, value: string|number,
      errors: string[]): string {
    return value + this._suffix;
  }
}

class SuperMockDriver extends MockAnimationDriver {
  computeStyle(element: any, prop: string, defaultValue?: string): string { return '*star*'; }
}
