/* istanbul ignore next */
describe('Ultron', function () {
  'use strict';

  var EventEmitter = require('events').EventEmitter
    , EE = require('events').EventEmitter
    , assert = require('assert')
    , Ultron = require('ultron')
    , ultron
    , ee;

  beforeEach(function () {
    ee = new EventEmitter();
    ultron = new Ultron(ee);
  });

  afterEach(function () {
    ultron.destroy();
    ee.removeAllListeners();
  });

  it('is exposed as a function', function () {
    assert.ok(Ultron);
  });

  it('can be initialized without the new keyword', function () {
    assert.ok(Ultron(ee) instanceof Ultron);  
  });

  it('assigns a unique id to every instance', function () {
    for (var i = 0; i < 100; i++) {
      assert.notEqual(ultron.id, ((new Ultron()).id));
    }
  });

  it('allows removal through the event emitter', function () {
    function foo() {}
    function bar() {}

    ultron.on('foo', foo);
    ultron.once('foo', bar);

    assert.equal(foo.__ultron, ultron.id);
    assert.equal(bar.__ultron, ultron.id);
    assert.equal(ee.listeners('foo').length, 2);

    ee.removeListener('foo', foo);
    assert.equal(ee.listeners('foo').length, 1);

    ee.removeListener('foo', bar);
    assert.equal(ee.listeners('foo').length, 0);
  });

  describe('#on', function () {
    it('assigns a listener', function () {
      assert.equal(ee.listeners('foo').length, 0);

      function foo() {}

      ultron.on('foo', foo);
      assert.equal(ee.listeners('foo').length, 1);
      assert.equal(ee.listeners('foo')[0], foo);
    });

    it('tags the assigned function', function () {
      assert.equal(ee.listeners('foo').length, 0);

      ultron.on('foo', function () {});
      assert.equal(ee.listeners('foo')[0].__ultron, ultron.id);
    });

    it('also passes in the context', function (next) {
      var context = 1313;

      ultron.on('foo', function (a, b, c) {
        assert.equal(a, 'a');
        assert.equal(b, 'b');
        assert.equal(c, 'c');

        console.log("FIXME: ", this, context);

        next();
      }, context);

      ee.emit('foo', 'a', 'b', 'c');
    });

    it('works with regular eventemitters as well', function (next) {
      var ee = new EE()
        , ultron = new Ultron(ee);

      ultron.on('foo', function (a, b, c) {
        assert.equal(a, 'a');
        assert.equal(b, 'b');
        assert.equal(c, 'c');

        next();
      });

      ee.emit('foo', 'a', 'b', 'c');
    });
  });

  describe('#once', function () {
    it('assigns a listener', function () {
      assert.equal(ee.listeners('foo').length, 0);

      function foo() {}
      ultron.once('foo', foo);
      assert.equal(ee.listeners('foo').length, 1);
      assert.equal(ee.listeners('foo')[0], foo);
    });

    it('tags the assigned function', function () {
      assert.equal(ee.listeners('foo').length, 0);

      ultron.once('foo', function () {});
      assert.equal(ee.listeners('foo')[0].__ultron, ultron.id);
    });

    it('also passes in the context', function (next) {
      var context = 1313;

      ultron.once('foo', function (a, b, c) {
        assert.equal(a, 'a');
        assert.equal(b, 'b');
        assert.equal(c, 'c');

        console.log("FIXME: ", this, context);

        next();
      }, context);

      ee.emit('foo', 'a', 'b', 'c');
      ee.emit('foo', 'a', 'b', 'c'); // Ensure that we don't double execute
    });

    it('works with regular eventemitters as well', function (next) {
      var ee = new EE()
        , ultron = new Ultron(ee);

      ultron.once('foo', function (a, b, c) {
        assert.equal(a, 'a');
        assert.equal(b, 'b');
        assert.equal(c, 'c');

        next();
      });

      ee.emit('foo', 'a', 'b', 'c');
      ee.emit('foo', 'a', 'b', 'c'); // Ensure that we don't double execute
    });
  });

  describe('#remove', function () {
    it('removes only our assigned `on` listeners', function () {
      function foo() {}
      function bar() {}

      ee.on('foo', foo);
      ultron.on('foo', bar);
      assert.equal(ee.listeners('foo').length, 2);

      ultron.remove('foo');
      assert.equal(ee.listeners('foo').length, 1);
      assert.equal(ee.listeners('foo')[0], foo);
    });

    it('removes only our assigned `once` listeners', function () {
      function foo() {}
      function bar() {}

      ee.once('foo', foo);
      ultron.once('foo', bar);
      assert.equal(ee.listeners('foo').length, 2);

      ultron.remove('foo');
      assert.equal(ee.listeners('foo').length, 1);
      assert.equal(ee.listeners('foo')[0], foo);

      ee.removeAllListeners();
      ultron.destroy();

      ee = new EE();
      ultron = new Ultron(ee);

      ee.once('foo', foo);
      ultron.once('foo', bar);
      assert.equal(ee.listeners('foo').length, 2);

      ultron.remove('foo');
      assert.equal(ee.listeners('foo').length, 1);
      var actual = ee.listeners('foo')[0];
      //
      // In Node.js 8+ `EventEmitter#listeners()` returns the original listener
      // also for once listeners.
      //
      assert.equal(actual.listener || actual, foo);
    });

    it('removes all assigned events if called without args', function () {
      function foo() {}
      function bar() {}

      assert(ultron.remove(), ultron);

      ultron.on('foo', foo);
      ultron.on('bar', bar);
      ultron.on('baz', bar);

      assert.equal(ee.listeners('foo').length, 1);
      assert.equal(ee.listeners('bar').length, 1);
      assert.equal(ee.listeners('baz').length, 1);

      ultron.remove();

      assert.equal(ee.listeners('foo').length, 0);
      assert.equal(ee.listeners('bar').length, 0);
      assert.equal(ee.listeners('baz').length, 0);

      ee.removeAllListeners();
      ultron.destroy();

      ee = new EE();
      ultron = new Ultron(ee);

      assert(ultron.remove(), ultron);

      ultron.on('foo', foo);
      ultron.on('bar', bar);
      ultron.on('baz', bar);

      assert.equal(ee.listeners('foo').length, 1);
      assert.equal(ee.listeners('bar').length, 1);
      assert.equal(ee.listeners('baz').length, 1);

      ultron.remove();

      assert.equal(ee.listeners('foo').length, 0);
      assert.equal(ee.listeners('bar').length, 0);
      assert.equal(ee.listeners('baz').length, 0);
    });

    it('removes multiple listeners based on args', function () {
      function foo() {}
      function bar() {}
      function baz() {}

      ultron.on('foo', foo);
      ultron.on('bar', bar);
      ultron.on('baz', baz);

      assert.equal(ee.listeners('foo').length, 1);
      assert.equal(ee.listeners('bar').length, 1);
      assert.equal(ee.listeners('baz').length, 1);

      ultron.remove('foo', 'bar');

      assert.equal(ee.listeners('foo').length, 0);
      assert.equal(ee.listeners('bar').length, 0);
      assert.equal(ee.listeners('baz').length, 1);
    });

    it('removes multiple listeners if first arg is separated string', function () {
      function foo() {}
      function bar() {}
      function baz() {}

      ultron.on('foo', foo);
      ultron.on('bar', bar);
      ultron.on('baz', baz);

      assert.equal(ee.listeners('foo').length, 1);
      assert.equal(ee.listeners('bar').length, 1);
      assert.equal(ee.listeners('baz').length, 1);

      ultron.remove('foo, bar');

      assert.equal(ee.listeners('foo').length, 0);
      assert.equal(ee.listeners('bar').length, 0);
      assert.equal(ee.listeners('baz').length, 1);
    });

    if ('undefined' !== typeof Symbol) it('works with ES6 symbols', function () {
      var s = Symbol('s');

      function foo() {}
      function bar() {}
      function baz() {}

      ee.on(s, foo);
      ultron.on(s, bar);
      assert.equal(ee.listeners(s).length, 2);

      ultron.remove(s);
      assert.equal(ee.listeners(s).length, 1);
      assert.equal(ee.listeners(s)[0], foo);

      ultron.once(s, bar);
      assert.equal(ee.listeners(s).length, 2);

      ultron.remove(s);
      assert.equal(ee.listeners(s).length, 1);
      assert.equal(ee.listeners(s)[0], foo);

      ultron.on(s, bar);
      ultron.on(s, baz);
      assert.equal(ee.listeners(s).length, 3);

      ultron.remove();
      assert.equal(ee.listeners(s).length, 1);
      assert.equal(ee.listeners(s)[0], foo);

      ee.removeAllListeners();
      ultron.destroy();

      ee = new EE();
      ultron = new Ultron(ee);

      ee.on(s, foo);
      ultron.on(s, bar);
      assert.equal(ee.listeners(s).length, 2);

      ultron.remove(s);
      assert.equal(ee.listeners(s).length, 1);
      assert.equal(ee.listeners(s)[0], foo);

      ultron.once(s, bar);
      assert.equal(ee.listeners(s).length, 2);

      ultron.remove(s);
      assert.equal(ee.listeners(s).length, 1);
      assert.equal(ee.listeners(s)[0], foo);

      ultron.on(s, bar);
      ultron.on(s, baz);
      assert.equal(ee.listeners(s).length, 3);

      ultron.remove();
      assert.equal(ee.listeners(s).length, 1);
      assert.equal(ee.listeners(s)[0], foo);
    });
  });

  describe('#destroy', function () {
    it('removes all listeners', function () {
      function foo() {}
      function bar() {}
      function baz() {}

      ultron.on('foo', foo);
      ultron.on('bar', bar);
      ultron.on('baz', baz);

      assert.equal(ee.listeners('foo').length, 1);
      assert.equal(ee.listeners('bar').length, 1);
      assert.equal(ee.listeners('baz').length, 1);

      ultron.destroy();

      assert.equal(ee.listeners('foo').length, 0);
      assert.equal(ee.listeners('bar').length, 0);
      assert.equal(ee.listeners('baz').length, 0);
    });

    it('removes the .ee reference', function () {
      assert.equal(ultron.ee, ee);
      ultron.destroy();
      assert.equal(ultron.ee, null);
    });

    it('returns booleans for state indication', function () {
      assert.ok(ultron.destroy());
      assert.equal(ultron.destroy(), false);
      assert.equal(ultron.destroy(), false);
      assert.equal(ultron.destroy(), false);
    });
  });
});
