
chai = require 'chai' unless chai
path = require 'path'
async = require 'async'

Coordinator = require('../src/coordinator').Coordinator
transport = require '../src/transport'
common = require '../src/common'
participants = require './fixtures/participants'

# Note: most require running an external broker service
transports =
  'direct': 'direct://broker2'
  'MQTT': 'mqtt://localhost'
  'AMQP': 'amqp://localhost'


connectAll = (clients, callback) ->
  connect = (c, cb) ->
    c.connect cb

  async.map clients, connect, callback

zip = () ->
  lengthArray = (arr.length for arr in arguments)
  length = Math.min(lengthArray...)
  for i in [0...length]
    arr[i] for arr in arguments

#
createConnectClients = (address, names, callback) ->
  createConnect = (name, cb) ->
    client = transport.getClient address
    client.connect (err) ->
      cb err, client

  async.map names, createConnect, (err, clients) ->
    return callback err if err
    ret = {}
    for nc in zip names, clients
      ret[nc[0]] = nc[1]
    return callback null, ret

createBindQueues = (broker, queueMapping, callback) ->
  createBindQueue = (det, cb) ->
    [client, type, srcQ, tgtQ] = det
    createQ = if type == 'outqueue' then srcQ else tgtQ
    client.createQueue type, createQ, (err) ->
      return cb err if err
      broker.bindQueue srcQ, tgtQ, cb

  async.map queueMapping, createBindQueue, callback

sendPackets = (packets, callback) ->
  send = (p, cb) ->
    [client, queue, data] = p
    client.sendToQueue queue, data, cb

  async.map packets, send, callback

subscribeData = (handlers, callback) ->
  sub = (h, cb) ->
    [client, queue, handler] = h
    ackHandler = (msg) ->
      client.ackMessage msg
      return handler msg
    client.subscribeToQueue queue, ackHandler, cb

  async.map handlers, sub, callback


describe 'Transport', ->

  Object.keys(transports).forEach (type) =>
    address = transports[type]
    broker = null

    describe "#{type}", () ->

      beforeEach (done) ->
        broker = transport.getBroker address
        broker.connect (err) ->
          err = null if not err?
          chai.expect(err).to.be.a 'null'
          done()

      afterEach (done) ->
        broker.disconnect () ->
          broker = null
          done()

      describe 'starting client', ->
        it 'should not error', (done) ->
          clientA = transport.getClient address
          clientA.connect (err) ->
            done err

      describe 'inqueue==outqueue without binding', ->
        it 'sending should be received on other end', (done) ->
          sender = transport.getClient address
          receiver = transport.getClient address
          payload = { foo: 'bar91' }
          sharedQueue = 'myqueue33'
          onReceive = (msg) ->
            chai.expect(msg).to.include.keys 'data'
            chai.expect(msg.data).to.eql payload
            done()
          connectAll [sender, receiver], (err) ->
            receiver.createQueue 'inqueue', sharedQueue, (err) ->
              chai.expect(err).to.be.a 'null'
              sender.createQueue 'outqueue', sharedQueue, (err) ->
                chai.expect(err).to.be.a 'null'

              receiver.subscribeToQueue sharedQueue, onReceive, (err) ->
                chai.expect(err).to.be.a 'null'
              sender.sendToQueue sharedQueue, payload, (err) ->
                chai.expect(err).to.be.a 'null'


      describe 'inqueue==outqueue with binding', ->
        it 'sending should be received on other end', (done) ->
          sender = transport.getClient address
          receiver = transport.getClient address
          payload = { foo: 'bar92' }
          sharedQueue = 'myqueue35'
          onReceive = (msg) ->
            chai.expect(msg).to.include.keys 'data'
            chai.expect(msg.data).to.eql payload
            done()
          connectAll [sender, receiver], (err) ->
            receiver.createQueue 'inqueue', sharedQueue, (err) ->
              chai.expect(err).to.be.a 'null'
              sender.createQueue 'outqueue', sharedQueue, (err) ->
                chai.expect(err).to.be.a 'null'

              broker.bindQueue sharedQueue, sharedQueue, (err) ->
                chai.expect(err).to.be.a 'null'

                receiver.subscribeToQueue sharedQueue, onReceive, (err) ->
                  chai.expect(err).to.be.a 'null'
                sender.sendToQueue sharedQueue, payload, (err) ->
                  chai.expect(err).to.be.a 'null'


      describe 'outqueue bound to inqueue', ->
        it 'sending to inqueue, show up on outqueue', (done) ->
          sender = transport.getClient address
          receiver = transport.getClient address
          payload = { foo: 'bar99' }
          inQueue = 'inqueue232'
          outQueue = 'outqueue353'
          onReceive = (msg) ->
            receiver.ackMessage msg
            chai.expect(msg).to.include.keys 'data'
            chai.expect(msg.data).to.eql payload
            done()
          connectAll [sender, receiver], (err) ->
            receiver.createQueue 'inqueue', inQueue, (err) ->
              chai.expect(err).to.be.a 'null'
              sender.createQueue 'outqueue', outQueue, (err) ->
                chai.expect(err).to.be.a 'null'

              broker.bindQueue outQueue, inQueue, (err) ->
                chai.expect(err).to.be.a 'null'

                receiver.subscribeToQueue inQueue, onReceive, (err) ->
                  chai.expect(err).to.be.a 'null'
                sender.sendToQueue outQueue, payload, (err) ->
                  chai.expect(err).to.be.a 'null'


      describe 'multiple outqueues bound to one inqueue', ->
        it 'all sent on outqueues shows up on inqueue', (done) ->
          @timeout 3000
          senders = [ 'sendA', 'sendB', 'sendC' ]
          clientNames = ['receive']
          clientNames.push.apply clientNames, senders
          createConnectClients address, clientNames, (err, clients) ->
            chai.expect(err).to.be.a 'null'

            expect = [ {name:'sendA'}, {name:'sendB'}, {name:'sendC'} ]

            received = []
            onReceive = (msg) ->
              clients.receive.ackMessage msg
              chai.expect(msg).to.include.keys 'data'
              received.push msg.data
              if received.length == expect.length
                received.sort (a,b) ->
                  return -1 if a.name < b.name
                  return 1 if a.name > b.name
                  return 0
                chai.expect(received).to.eql expect
                done()

            inQueue = 'inqueue27'

            clients.receive.createQueue 'inqueue', inQueue, (err) ->
              chai.expect(err).to.not.exist
              clients.receive.subscribeToQueue inQueue, onReceive, (err) ->
                chai.expect(err).to.not.exist

                # Bind all outqueues to same inqueue
                queueMapping = []
                for name in senders
                  queueMapping.push [ clients[name], 'outqueue', name, inQueue ]
                createBindQueues broker, queueMapping, (err) ->
                  chai.expect(err).to.not.exist

                  packets = []
                  for name in senders
                    packets.push [ clients[name], name, { name: name } ]
                  sendPackets packets, (err) ->
                    chai.expect(err).to.not.exist


      describe 'multiple inqueues bound to one outqueue', ->
        it 'data sent on outqueue shows up on all inqueues', (done) ->
          @timeout 3000
          senders = [ 'sender' ]
          receivers = ['r1', 'r2', 'r3']
          clientNames = common.clone receivers
          clientNames.push.apply clientNames, senders
          createConnectClients address, clientNames, (err, clients) ->
            chai.expect(err).to.not.exist

            expect = [ {q:'r1',d:'ident'}, {q:'r2',d:'ident'}, {q:'r3',d:'ident'} ]

            received = []
            checkExpected = (q, msg) ->
              received.push { q: q, d: msg.data.data }
              if received.length == expect.length
                received.sort (a,b) ->
                  return -1 if a.q < b.q
                  return 1 if a.q > b.q
                  return 0
                chai.expect(received).to.eql expect
                done()

            onReceives =
              r1: (msg) -> checkExpected 'r1', msg
              r2: (msg) -> checkExpected 'r2', msg
              r3: (msg) -> checkExpected 'r3', msg

            outQueue2 = 'outqueue39'
            clients.sender.createQueue 'outqueue', outQueue2, (err) ->
              chai.expect(err).to.not.exist

              # Bind same outqueue to all inqueues
              queueMapping = []
              for name in receivers
                queueMapping.push [ clients[name], 'inqueue', outQueue2, name ]
              createBindQueues broker, queueMapping, (err) ->
                chai.expect(err).to.not.exist

                handlers = []
                for name in receivers
                  handlers.push [ clients[name], name, onReceives[name] ]
                subscribeData handlers, (err) ->
                  chai.expect(err).to.not.exist
                  clients.sender.sendToQueue outQueue2, {data: 'ident'}, (err) ->
                    chai.expect(err).to.not.exist

