'use strict';

import * as helpers from '../helpers/helpers';
import EventEmitter from './eventEmitter';
import sinon from 'sinon';
import 'should';
import log4js from 'log4js';

/**
 * @test {EventEmitter}
 */
describe('EventEmitter', () => {

  let eventEmitter: EventEmitter;
  let sandbox = sinon.createSandbox();

  before(() => {
    log4js.configure(helpers.assembleLog4jsConfig());
  });

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

  afterEach(() => {
    sandbox.restore();
  });

  /**
   * @test {EventEmitter#emit}
   */
  describe('emit', () => {
    
    /**
     * @test {EventEmitter#emit}
     */
    it('should wait async listeners', async () => {
      let promise1 = helpers.createHandlePromise();
      let promise2 = helpers.createHandlePromise();

      let listener1 = sandbox.stub().returns(promise1);
      let listener2 = sandbox.stub().returns(promise2);

      eventEmitter.on('event', listener1);
      eventEmitter.on('event', listener2);

      let promise = helpers.wrapHandlePromise(eventEmitter.emit('event'));
      sinon.assert.called(listener1);
      sinon.assert.called(listener2);

      await helpers.delay(25);
      promise.completed.should.be.false();

      promise1.resolve();
      await helpers.delay(25);
      promise.completed.should.be.false();

      promise2.resolve();
      await promise;
    });

    /**
     * @test {EventEmitter#emit}
     */
    it('should catch listener errors', async () => {
      let listener1 = sandbox.stub().throws(new Error('syncError'));
      let listener2 = sandbox.stub().rejects(new Error('asyncError'));

      eventEmitter.on('event', listener1);
      eventEmitter.on('event', listener2);

      await eventEmitter.emit('event');
      sinon.assert.calledOnce(listener1);
      sinon.assert.calledOnce(listener2);
    });

  });

  /**
   * @test {EventEmitter#once}
   */
  describe('once', () => {

    /**
     * @test {EventEmitter#once}
     */
    it('should be called only once', () => {
      let listener = sandbox.stub();
      eventEmitter.once('event', listener);

      eventEmitter.emit('event', 42, 28);
      eventEmitter.emit('event', 101, 66);
      sinon.assert.callCount(listener, 1);
      sinon.assert.calledWith(listener, 42, 28);
    });

    /**
     * @test {EventEmitter#once}
     */
    it('should work correctly with async listener and awaited emit', async () => {
      let listenerPromise = helpers.createHandlePromise();
      let listener = sandbox.stub().returns(listenerPromise);
      eventEmitter.once('event', listener);

      let emitPromise = helpers.wrapHandlePromise(eventEmitter.emit('event', 42, 28));
      await eventEmitter.emit('event', 101, 66);
      sinon.assert.callCount(listener, 1);
      sinon.assert.calledWith(listener, 42, 28);

      await helpers.delay(25);
      emitPromise.completed.should.be.false();

      listenerPromise.reject(new Error('test'));
      await emitPromise;
      
      await helpers.delay(25);
      sinon.assert.callCount(listener, 1);
    });

    /**
     * @test {EventEmitter#once}
     */
    it('should wait for matching args to call the listener', () => {
      let listener = sandbox.stub();
      eventEmitter.once('event', listener, {ifArgs: id => id === 2});

      eventEmitter.emit('event', 1);
      eventEmitter.emit('wrong', 2);
      sinon.assert.callCount(listener, 0);

      eventEmitter.emit('event', 2, 'test');
      sinon.assert.callCount(listener, 1);

      eventEmitter.emit('event', 2, 'test');
      sinon.assert.callCount(listener, 1);
    });

  });

  /**
   * @test {EventEmitter#getListeners}
   */
  describe('getListeners', () => {

    /**
     * @test {EventEmitter#getListeners}
     */
    it('should return event listeners', () => {
      eventEmitter.getListeners('event').should.deepEqual([]);

      let listener1 = sandbox.stub();
      let listener2 = sandbox.stub();
      let listener3 = sandbox.stub();

      eventEmitter.on('event', listener1);
      eventEmitter.on('event', listener2);
      eventEmitter.on('event', listener3);
      eventEmitter.getListeners('event').should.deepEqual([listener1, listener2, listener3]);

      eventEmitter.off('event', listener2);
      eventEmitter.on('event2', listener2);
      eventEmitter.getListeners('event').should.deepEqual([listener1, listener3]);
    });

  });

});
