describe Wisper::Publisher do
  let(:listener)  { double('listener') }
  let(:publisher) { publisher_class.new }

  describe '.subscribe' do
    it 'subscribes given listener to all published events' do
      expect(listener).to receive(:this_happened)
      expect(listener).to receive(:so_did_this)

      publisher.subscribe(listener)

      publisher.send(:broadcast, 'this_happened')
      publisher.send(:broadcast, 'so_did_this')
    end

    describe ':on argument' do
      before do
        allow(listener).to receive(:something_a_happened)
        allow(listener).to receive(:and_this)
        allow(listener).to receive(:so_did_this)
      end

      describe 'given a string' do
        it 'subscribes listener to an event' do
          expect(listener).to receive(:this_happened)
          expect(listener).not_to receive(:so_did_this)

          publisher.subscribe(listener, on: 'this_happened')

          publisher.send(:broadcast, 'this_happened')
          publisher.send(:broadcast, 'so_did_this')
        end
      end

      describe 'given a symbol' do
        it 'subscribes listener to an event' do
          expect(listener).to receive(:this_happened)
          expect(listener).not_to receive(:so_did_this)

          publisher.subscribe(listener, on: :this_happened)

          publisher.send(:broadcast, 'this_happened')
          publisher.send(:broadcast, 'so_did_this')
        end
      end

      describe 'given an array' do
        it 'subscribes listener to events' do
          expect(listener).to receive(:this_happened)
          expect(listener).to receive(:and_this)
          expect(listener).not_to receive(:so_did_this)

          publisher.subscribe(listener, on: ['this_happened', 'and_this'])

          publisher.send(:broadcast, 'this_happened')
          publisher.send(:broadcast, 'so_did_this')
          publisher.send(:broadcast, 'and_this')
        end
      end

      describe 'given a regex' do
        it 'subscribes listener to matching events' do
          expect(listener).to receive(:something_a_happened)
          expect(listener).not_to receive(:so_did_this)

          publisher.subscribe(listener, on: /something_._happened/)

          publisher.send(:broadcast, 'something_a_happened')
          publisher.send(:broadcast, 'so_did_this')
        end
      end

      describe 'given an unsupported argument' do
        it 'raises an error' do
          publisher.subscribe(listener, on: Object.new)
          expect { publisher.send(:broadcast, 'something_a_happened') }.to raise_error(ArgumentError)
        end
      end
    end

    describe ':with argument' do
      it 'sets method to call listener with on event' do
        expect(listener).to receive(:different_method).twice

        publisher.subscribe(listener, :with => :different_method)

        publisher.send(:broadcast, 'this_happened')
        publisher.send(:broadcast, 'so_did_this')
      end
    end

    describe ':prefix argument' do
      it 'prefixes broadcast events with given symbol' do
        expect(listener).to receive(:after_it_happened)
        expect(listener).not_to receive(:it_happened)

        publisher.subscribe(listener, :prefix => :after)

        publisher.send(:broadcast, 'it_happened')
      end

      it 'prefixes broadcast events with "on" when given true' do
        expect(listener).to receive(:on_it_happened)
        expect(listener).not_to receive(:it_happened)

        publisher.subscribe(listener, :prefix => true)

        publisher.send(:broadcast, 'it_happened')
      end
    end

    describe ':scope argument' do
      let(:listener_1) { double('Listener') }
      let(:listener_2) { double('Listener') }

      it 'scopes listener to given class' do
        expect(listener_1).to receive(:it_happended)
        expect(listener_2).not_to receive(:it_happended)
        publisher.subscribe(listener_1, :scope => publisher.class)
        publisher.subscribe(listener_2, :scope => Class.new)
        publisher.send(:broadcast, 'it_happended')
      end

      it 'scopes listener to given class string' do
        expect(listener_1).to receive(:it_happended)
        expect(listener_2).not_to receive(:it_happended)
        publisher.subscribe(listener_1, :scope => publisher.class.to_s)
        publisher.subscribe(listener_2, :scope => Class.new.to_s)
        publisher.send(:broadcast, 'it_happended')
      end

      it 'includes all subclasses of given class' do
        publisher_super_klass = publisher_class
        publisher_sub_klass = Class.new(publisher_super_klass)

        listener = double('Listener')
        expect(listener).to receive(:it_happended).once

        publisher = publisher_sub_klass.new

        publisher.subscribe(listener, :scope => publisher_super_klass)
        publisher.send(:broadcast, 'it_happended')
      end
    end

    describe ':broadcaster argument'do
      let(:broadcaster) { double('broadcaster') }
      let(:listener)    { double('listener') }
      let(:event_name)  { 'it_happened' }

      before do
        Wisper.configuration.broadcasters.clear
        allow(listener).to    receive(event_name)
        allow(broadcaster).to receive(:broadcast)
      end

      after { Wisper.setup } # restore default configuration

      it 'given an object which responds_to broadcast it uses object' do
        publisher.subscribe(listener, broadcaster: broadcaster)
        expect(broadcaster).to receive('broadcast')
        publisher.send(:broadcast, event_name)
      end

      it 'given a key it uses a configured broadcaster' do
        Wisper.configure { |c| c.broadcaster(:foobar, broadcaster) }
        publisher.subscribe(listener, broadcaster: :foobar)
        expect(broadcaster).to receive('broadcast')
        publisher.send(:broadcast, event_name)
      end

      it 'given an unknown key it raises error' do
        expect { publisher.subscribe(listener, broadcaster: :foobar) }.to raise_error(KeyError, /broadcaster not found/)
      end

      it 'given nothing it uses the default broadcaster' do
        Wisper.configure { |c| c.broadcaster(:default, broadcaster) }
        publisher.subscribe(listener)
        expect(broadcaster).to receive('broadcast')
        publisher.send(:broadcast, event_name)
      end

      describe 'async alias' do
        it 'given an object which responds_to broadcast it uses object' do
          publisher.subscribe(listener, async: broadcaster)
          expect(broadcaster).to receive('broadcast')
          publisher.send(:broadcast, event_name)
        end

        it 'given true it uses configured async broadcaster' do
          Wisper.configure { |c| c.broadcaster(:async, broadcaster) }
          publisher.subscribe(listener, async: true)
          expect(broadcaster).to receive('broadcast')
          publisher.send(:broadcast, event_name)
        end

        it 'given false it uses configured default broadcaster' do
          Wisper.configure { |c| c.broadcaster(:default, broadcaster) }
          publisher.subscribe(listener, async: false)
          expect(broadcaster).to receive('broadcast')
          publisher.send(:broadcast, event_name)
        end
      end

      describe 'callable broadcasters' do
        let(:broadcaster) { Struct.new(:options) }
        let(:async_options) { { queue: 'custom', retry: false } }

        before do
          Wisper.configure { |c| c.broadcaster(:async, Proc.new { |options| broadcaster.new(options) }) }
        end

        it 'initializes broadcaster with configured options during subscribe action' do
          expect(broadcaster).to receive(:new).with(async_options)
          publisher.subscribe(listener, async: async_options)
        end

        it 'invokes configured broadcaster action during event broadcast' do
          publisher.subscribe(listener, async: async_options)
          expect_any_instance_of(broadcaster).to receive('broadcast')
          publisher.send(:broadcast, event_name)
        end
      end
    end

    it 'returns publisher so methods can be chained' do
      expect(publisher.subscribe(listener, :on => 'so_did_this')).to \
        eq publisher
    end

    it 'is aliased to .subscribe' do
      expect(publisher).to respond_to(:subscribe)
    end

    it 'raises a helpful error if trying to pass a block' do
      invalid = ->{
        publisher.subscribe(:success) do
          puts
        end
      }
      expect{ invalid.call }.to raise_error(ArgumentError)
    end
  end

  describe '.on' do
    it 'returns publisher so methods can be chained' do
      expect(publisher.on('this_thing_happened') {}).to eq publisher
    end

    it 'raise an error if no events given' do
      expect { publisher.on() {} }.to raise_error(ArgumentError)
    end

    it 'raises an error of no block given' do
      expect { publisher.on(:something) }.to raise_error(ArgumentError)
    end

    it 'returns publisher so methods can be chained' do
      expect(publisher.on(:foo) {}).to eq publisher
    end
  end

  describe '.broadcast' do

    it 'does not publish events which cannot be responded to' do
      expect(listener).not_to receive(:so_did_this)
      allow(listener).to receive(:respond_to?).and_return(false)

      publisher.subscribe(listener, :on => 'so_did_this')

      publisher.send(:broadcast, 'so_did_this')
    end

    describe ':event argument' do
      it 'is indifferent to string and symbol' do
        expect(listener).to receive(:this_happened).twice

        publisher.subscribe(listener)

        publisher.send(:broadcast, 'this_happened')
        publisher.send(:broadcast, :this_happened)
      end

      it 'is indifferent to dasherized and underscored strings' do
        expect(listener).to receive(:this_happened).twice

        publisher.subscribe(listener)

        publisher.send(:broadcast, 'this_happened')
        publisher.send(:broadcast, 'this-happened')
      end
    end

    it 'returns publisher' do
      expect(publisher.send(:broadcast, :foo)).to eq publisher
    end

    it 'is not public' do
      expect(publisher).not_to respond_to(:broadcast)
    end

    it 'is alised as .publish' do
      expect(publisher.method(:broadcast)).to eq publisher.method(:publish)
    end
  end

  describe '.listeners' do
    it 'returns an immutable collection' do
      expect(publisher.listeners).to be_frozen
      expect { publisher.listeners << listener }.to raise_error(RuntimeError)
    end

    it 'returns local listeners' do
       publisher.subscribe(listener)
       expect(publisher.listeners).to eq [listener]
       expect(publisher.listeners.size).to eq 1
    end
  end

  describe '#subscribe' do
    let(:publisher_klass_1) { publisher_class }
    let(:publisher_klass_2) { publisher_class }

    it 'subscribes listener to all instances of publisher' do
      publisher_klass_1.subscribe(listener)
      expect(listener).to receive(:it_happened).once
      publisher_klass_1.new.send(:broadcast, 'it_happened')
      publisher_klass_2.new.send(:broadcast, 'it_happened')
    end
  end
end