/* eslint-env mocha */
/* eslint max-nested-callbacks: ["error", 5] */
'use strict'

const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)

const parallel = require('async/parallel')
const TCP = require('libp2p-tcp')
const WebSockets = require('libp2p-websockets')
const mplex = require('libp2p-mplex')
const pMplex = require('pull-mplex')
const spdy = require('libp2p-spdy')
const pull = require('pull-stream')
const PeerBook = require('peer-book')

const utils = require('./utils')
const createInfos = utils.createInfos
const tryEcho = utils.tryEcho
const Switch = require('libp2p-switch')

describe('Switch (everything all together)', () => {
  [pMplex, spdy, mplex].forEach(muxer => {
    describe(muxer.multicodec, () => {
      let switchA // tcp
      let switchB // tcp+ws
      let switchC // tcp+ws
      let switchD // ws
      let switchE // ws

      before((done) => createInfos(5, (err, infos) => {
        expect(err).to.not.exist()

        const peerA = infos[0]
        const peerB = infos[1]
        const peerC = infos[2]
        const peerD = infos[3]
        const peerE = infos[4]

        switchA = new Switch(peerA, new PeerBook())
        switchB = new Switch(peerB, new PeerBook())
        switchC = new Switch(peerC, new PeerBook())
        switchD = new Switch(peerD, new PeerBook())
        switchE = new Switch(peerE, new PeerBook())

        done()
      }))

      after(function (done) {
        parallel([
          (cb) => switchA.stop(cb),
          (cb) => switchB.stop(cb),
          (cb) => switchD.stop(cb),
          (cb) => switchE.stop(cb)
        ], done)
      })

      it('add tcp', (done) => {
        switchA._peerInfo.multiaddrs.add('/ip4/127.0.0.1/tcp/10100')
        switchB._peerInfo.multiaddrs.add('/ip4/127.0.0.1/tcp/10200')
        switchC._peerInfo.multiaddrs.add('/ip4/127.0.0.1/tcp/10300')

        switchA.transport.add('tcp', new TCP())
        switchB.transport.add('tcp', new TCP())
        switchC.transport.add('tcp', new TCP())

        parallel([
          (cb) => switchA.transport.listen('tcp', {}, null, cb),
          (cb) => switchB.transport.listen('tcp', {}, null, cb)
        ], done)
      })

      it('add websockets', (done) => {
        switchB._peerInfo.multiaddrs.add('/ip4/127.0.0.1/tcp/9012/ws')
        switchC._peerInfo.multiaddrs.add('/ip4/127.0.0.1/tcp/9022/ws')
        switchD._peerInfo.multiaddrs.add('/ip4/127.0.0.1/tcp/9032/ws')
        switchE._peerInfo.multiaddrs.add('/ip4/127.0.0.1/tcp/9042/ws')

        switchB.transport.add('ws', new WebSockets())
        switchC.transport.add('ws', new WebSockets())
        switchD.transport.add('ws', new WebSockets())
        switchE.transport.add('ws', new WebSockets())

        parallel([
          (cb) => switchB.transport.listen('ws', {}, null, cb),
          (cb) => switchD.transport.listen('ws', {}, null, cb),
          (cb) => switchE.transport.listen('ws', {}, null, cb)
        ], done)
      })

      it('listen automatically', (done) => {
        switchC.start(done)
      })

      it('add spdy and enable identify', () => {
        switchA.connection.addStreamMuxer(muxer)
        switchB.connection.addStreamMuxer(muxer)
        switchC.connection.addStreamMuxer(muxer)
        switchD.connection.addStreamMuxer(muxer)
        switchE.connection.addStreamMuxer(muxer)

        switchA.connection.reuse()
        switchB.connection.reuse()
        switchC.connection.reuse()
        switchD.connection.reuse()
        switchE.connection.reuse()
      })

      it('warm up from A to B on tcp to tcp+ws', function (done) {
        this.timeout(10 * 1000)
        parallel([
          (cb) => switchB.once('peer-mux-established', (pi) => {
            expect(pi.id.toB58String()).to.equal(switchA._peerInfo.id.toB58String())
            cb()
          }),
          (cb) => switchA.once('peer-mux-established', (pi) => {
            expect(pi.id.toB58String()).to.equal(switchB._peerInfo.id.toB58String())
            cb()
          }),
          (cb) => switchA.dial(switchB._peerInfo, (err) => {
            expect(err).to.not.exist()
            expect(switchA.connection.getAll()).to.have.length(1)
            cb()
          })
        ], done)
      })

      it('warm up a warmed up, from B to A', (done) => {
        switchB.dial(switchA._peerInfo, (err) => {
          expect(err).to.not.exist()
          expect(switchA.connection.getAll()).to.have.length(1)
          done()
        })
      })

      it('dial from tcp to tcp+ws, on protocol', (done) => {
        switchB.handle('/anona/1.0.0', (protocol, conn) => pull(conn, conn))

        switchA.dial(switchB._peerInfo, '/anona/1.0.0', (err, conn) => {
          expect(err).to.not.exist()
          expect(switchA.connection.getAll()).to.have.length(1)
          tryEcho(conn, done)
        })
      })

      it('dial from ws to ws no proto', (done) => {
        switchD.dial(switchE._peerInfo, (err) => {
          expect(err).to.not.exist()
          expect(switchD.connection.getAll()).to.have.length(1)
          done()
        })
      })

      it('dial from ws to ws', (done) => {
        switchE.handle('/abacaxi/1.0.0', (protocol, conn) => pull(conn, conn))

        switchD.dial(switchE._peerInfo, '/abacaxi/1.0.0', (err, conn) => {
          expect(err).to.not.exist()
          expect(switchD.connection.getAll()).to.have.length(1)

          tryEcho(conn, () => setTimeout(() => {
            expect(switchE.connection.getAll()).to.have.length(1)
            done()
          }, 1000))
        })
      })

      it('dial from tcp to tcp+ws', (done) => {
        switchB.handle('/grapes/1.0.0', (protocol, conn) => pull(conn, conn))

        switchA.dial(switchB._peerInfo, '/grapes/1.0.0', (err, conn) => {
          expect(err).to.not.exist()
          expect(switchA.connection.getAll()).to.have.length(1)

          tryEcho(conn, done)
        })
      })

      it('dial from tcp+ws to tcp+ws', (done) => {
        let i = 0

        function check (err) {
          expect(err).to.not.exist()
          if (++i === 3) { done() }
        }

        switchC.handle('/mamao/1.0.0', (protocol, conn) => {
          conn.getPeerInfo((err, peerInfo) => {
            expect(err).to.not.exist()
            expect(peerInfo).to.exist()
            check()
          })

          pull(conn, conn)
        })

        switchA.dial(switchC._peerInfo, '/mamao/1.0.0', (err, conn) => {
          expect(err).to.not.exist()

          conn.getPeerInfo((err, peerInfo) => {
            expect(err).to.not.exist()
            expect(peerInfo).to.exist()
            check()
          })

          expect(switchA.connection.getAll()).to.have.length(2)
          expect(switchC._peerInfo.isConnected).to.exist()
          expect(switchA._peerInfo.isConnected).to.exist()

          tryEcho(conn, check)
        })
      })

      it('hangUp', (done) => {
        let count = 0
        const ready = () => ++count === 3 ? done() : null

        switchB.once('peer-mux-closed', (peerInfo) => {
          expect(switchB.connection.getAll()).to.have.length(0)
          expect(switchB._peerInfo.isConnected()).to.not.exist()
          ready()
        })

        switchA.once('peer-mux-closed', (peerInfo) => {
          expect(switchA.connection.getAll()).to.have.length(1)
          expect(switchA._peerInfo.isConnected()).to.not.exist()
          ready()
        })

        switchA.hangUp(switchB._peerInfo, (err) => {
          expect(err).to.not.exist()
          ready()
        })
      })

      it('close a muxer emits event', function (done) {
        this.timeout(3 * 1000)

        parallel([
          (cb) => switchA.once('peer-mux-closed', (peerInfo) => cb()),
          (cb) => switchC.stop(cb)
        ], done)
      })
    })
  })
})