/**
 * @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 {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
import {filter} from 'rxjs/operators';

import {EventEmitter} from '../src/event_emitter';

{
  describe('EventEmitter', () => {
    let emitter: EventEmitter<any>;

    beforeEach(() => { emitter = new EventEmitter(); });

    it('should call the next callback',
       inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
         emitter.subscribe({
           next: (value: any) => {
             expect(value).toEqual(99);
             async.done();
           }
         });
         emitter.emit(99);
       }));

    it('should call the throw callback',
       inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
         emitter.subscribe({
           next: () => {},
           error: (error: any) => {
             expect(error).toEqual('Boom');
             async.done();
           }
         });
         emitter.error('Boom');
       }));

    it('should work when no throw callback is provided',
       inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
         emitter.subscribe({next: () => {}, error: (_: any) => { async.done(); }});
         emitter.error('Boom');
       }));

    it('should call the return callback',
       inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
         emitter.subscribe(
             {next: () => {}, error: (_: any) => {}, complete: () => { async.done(); }});
         emitter.complete();
       }));

    it('should subscribe to the wrapper synchronously', () => {
      let called = false;
      emitter.subscribe({next: (value: any) => { called = true; }});
      emitter.emit(99);

      expect(called).toBe(true);
    });

    it('delivers next and error events synchronously',
       inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
         const log: any[] /** TODO #9100 */ = [];

         emitter.subscribe({
           next: (x: any) => {
             log.push(x);
             expect(log).toEqual([1, 2]);
           },
           error: (err: any) => {
             log.push(err);
             expect(log).toEqual([1, 2, 3, 4]);
             async.done();
           }
         });
         log.push(1);
         emitter.emit(2);
         log.push(3);
         emitter.error(4);
         log.push(5);
       }));

    it('delivers next and complete events synchronously', () => {
      const log: any[] /** TODO #9100 */ = [];

      emitter.subscribe({
        next: (x: any) => {
          log.push(x);
          expect(log).toEqual([1, 2]);
        },
        error: null,
        complete: () => {
          log.push(4);
          expect(log).toEqual([1, 2, 3, 4]);
        }
      });
      log.push(1);
      emitter.emit(2);
      log.push(3);
      emitter.complete();
      log.push(5);
      expect(log).toEqual([1, 2, 3, 4, 5]);
    });

    it('delivers events asynchronously when forced to async mode',
       inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
         const e = new EventEmitter(true);
         const log: any[] /** TODO #9100 */ = [];
         e.subscribe((x: any) => {
           log.push(x);
           expect(log).toEqual([1, 3, 2]);
           async.done();
         });
         log.push(1);
         e.emit(2);
         log.push(3);

       }));

    it('reports whether it has subscribers', () => {
      const e = new EventEmitter(false);
      expect(e.observers.length > 0).toBe(false);
      e.subscribe({next: () => {}});
      expect(e.observers.length > 0).toBe(true);
    });

    it('remove a subscriber subscribed directly to EventEmitter', () => {
      const sub = emitter.subscribe();
      expect(emitter.observers.length).toBe(1);
      sub.unsubscribe();
      expect(emitter.observers.length).toBe(0);
    });

    it('remove a subscriber subscribed after applying operators with pipe()', () => {
      const sub = emitter.pipe(filter(() => true)).subscribe();
      expect(emitter.observers.length).toBe(1);
      sub.unsubscribe();
      expect(emitter.observers.length).toBe(0);
    });

    it('unsubscribing a subscriber invokes the dispose method', () => {
      inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
        const sub = emitter.subscribe();
        sub.add(() => async.done());
        sub.unsubscribe();
      });
    });

    it('unsubscribing a subscriber after applying operators with pipe() invokes the dispose method',
       () => {
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           const sub = emitter.pipe(filter(() => true)).subscribe();
           sub.add(() => async.done());
           sub.unsubscribe();
         });
       });

    it('error thrown inside an Rx chain propagates to the error handler and disposes the chain',
       () => {
         let errorPropagated = false;
         emitter.pipe(filter(() => { throw new Error(); }), )
             .subscribe(() => {}, err => errorPropagated = true, );

         emitter.next(1);

         expect(errorPropagated).toBe(true);
         expect(emitter.observers.length).toBe(0);
       });

    it('error sent by EventEmitter should dispose the Rx chain and remove subscribers', () => {
      let errorPropagated = false;
      emitter.pipe(filter(() => true)).subscribe(() => {}, err => errorPropagated = true, );

      emitter.error(1);

      expect(errorPropagated).toBe(true);
      expect(emitter.observers.length).toBe(0);
    });

    // TODO: vsavkin: add tests cases
    // should call dispose on the subscription if generator returns {done:true}
    // should call dispose on the subscription on throw
    // should call dispose on the subscription on return
  });
}
