if typeof process isnt 'undefined' and process.execPath and process.execPath.match /node|iojs/
  chai = require 'chai' unless chai
  noflo = require '../src/lib/NoFlo.coffee'
  path = require 'path'
  root = path.resolve __dirname, '../'
  urlPrefix = './'
else
  noflo = require 'noflo'
  root = 'noflo'
  urlPrefix = '/'

wirePatternAsync = ->
  c = new noflo.Component
  c.inPorts.add 'in',
    datatype: 'string'
  c.outPorts.add 'out',
    datatype: 'string'

  noflo.helpers.WirePattern c,
    in: 'in'
    out: 'out'
    async: true
    forwardGroups: true
  , (data, groups, out, callback) ->
    setTimeout ->
      out.send data + c.nodeId
      callback()
    , 1

wirePatternMerge = ->
  c = new noflo.Component
  c.inPorts.add 'in1',
    datatype: 'string'
  c.inPorts.add 'in2',
    datatype: 'string'
  c.outPorts.add 'out',
    datatype: 'string'

  noflo.helpers.WirePattern c,
    in: ['in1', 'in2']
    out: 'out'
    async: true
    forwardGroups: true
  , (data, groups, out, callback) ->
    out.send "1#{data['in1']}#{c.nodeId}2#{data['in2']}#{c.nodeId}"
    callback()

processAsync = ->
  c = new noflo.Component
  c.inPorts.add 'in',
    datatype: 'string'
  c.outPorts.add 'out',
    datatype: 'string'

  c.process (input, output) ->
    data = input.getData 'in'
    setTimeout ->
      output.sendDone data + c.nodeId
    , 1

processMerge = ->
  c = new noflo.Component
  c.inPorts.add 'in1',
    datatype: 'string'
  c.inPorts.add 'in2',
    datatype: 'string'
  c.outPorts.add 'out',
    datatype: 'string'

  c.forwardBrackets =
    'in1': ['out']

  c.process (input, output) ->
    return unless input.has 'in1', 'in2', (ip) -> ip.type is 'data'
    first = input.getData 'in1'
    second = input.getData 'in2'

    output.sendDone
      out: "1#{first}:2#{second}:#{c.nodeId}"

# Merge with an addressable port
processMergeA = ->
  c = new noflo.Component
  c.inPorts.add 'in1',
    datatype: 'string'
  c.inPorts.add 'in2',
    datatype: 'string'
    addressable: true
  c.outPorts.add 'out',
    datatype: 'string'

  c.forwardBrackets =
    'in1': ['out']

  c.process (input, output) ->
    return unless input.hasData 'in1', ['in2', 0], ['in2', 1]
    first = input.getData 'in1'
    second0 = input.getData ['in2', 0]
    second1 = input.getData ['in2', 1]

    output.sendDone
      out: "1#{first}:2#{second0}:2#{second1}:#{c.nodeId}"

describe 'Scope isolation', ->
  loader = null
  before (done) ->
    loader = new noflo.ComponentLoader root
    loader.listComponents (err) ->
      return done err if err
      loader.registerComponent 'wirepattern', 'Async', wirePatternAsync
      loader.registerComponent 'wirepattern', 'Merge', wirePatternMerge
      loader.registerComponent 'process', 'Async', processAsync
      loader.registerComponent 'process', 'Merge', processMerge
      loader.registerComponent 'process', 'MergeA', processMergeA
      done()

  describe 'with WirePattern sending to Process API', ->
    c = null
    ins = null
    out = null
    before (done) ->
      fbpData = "
      INPORT=Wp.IN:IN
      OUTPORT=Pc.OUT:OUT
      Wp(wirepattern/Async) OUT -> IN Pc(process/Async)
      "
      noflo.graph.loadFBP fbpData, (err, g) ->
        return done err if err
        loader.registerComponent 'scope', 'Connected', g
        loader.load 'scope/Connected', (err, instance) ->
          return done err if err
          c = instance
          ins = noflo.internalSocket.createSocket()
          c.inPorts.in.attach ins
          done()
    beforeEach ->
      out = noflo.internalSocket.createSocket()
      c.outPorts.out.attach out
    afterEach ->
      c.outPorts.out.detach out
      out = null

    it 'should forward old-style groups as expected', (done) ->
      expected = [
        'CONN'
        '< 1'
        '< a'
        'DATA bazWpPc'
        '>'
        '>'
        'DISC'
      ]
      received = []

      out.on 'connect', ->
        received.push 'CONN'
      out.on 'begingroup', (group) ->
        received.push "< #{group}"
      out.on 'data', (data) ->
        received.push "DATA #{data}"
      out.on 'endgroup', ->
        received.push '>'
      out.on 'disconnect', ->
        received.push 'DISC'
        chai.expect(received).to.eql expected
        done()

      ins.connect()
      ins.beginGroup 1
      ins.beginGroup 'a'
      ins.send 'baz'
      ins.endGroup()
      ins.endGroup()
      ins.disconnect()
    it 'should forward new-style brackets as expected', (done) ->
      expected = [
        '< 1'
        '< a'
        'DATA fooWpPc'
        '>'
        '>'
      ]
      received = []
      brackets = []

      out.on 'ip', (ip) ->
        switch ip.type
          when 'openBracket'
            received.push "< #{ip.data}"
            brackets.push ip.data
          when 'data'
            received.push "DATA #{ip.data}"
          when 'closeBracket'
            received.push '>'
            brackets.pop()
            return if brackets.length
            chai.expect(received).to.eql expected
            done()

      ins.post new noflo.IP 'openBracket', 1
      ins.post new noflo.IP 'openBracket', 'a'
      ins.post new noflo.IP 'data', 'foo'
      ins.post new noflo.IP 'closeBracket', 'a'
      ins.post new noflo.IP 'closeBracket', 1
    it 'should forward scopes as expected', (done) ->
      expected = [
        'x < 1'
        'x < a'
        'x DATA barWpPc'
        'x >'
        'x >'
      ]
      received = []
      brackets = []

      out.on 'ip', (ip) ->
        switch ip.type
          when 'openBracket'
            received.push "#{ip.scope} < #{ip.data}"
            brackets.push ip.data
          when 'data'
            received.push "#{ip.scope} DATA #{ip.data}"
          when 'closeBracket'
            received.push "#{ip.scope} >"
            brackets.pop()
            return if brackets.length
            chai.expect(received).to.eql expected
            done()

      ins.post new noflo.IP 'openBracket', 1,
        scope: 'x'
      ins.post new noflo.IP 'openBracket', 'a',
        scope: 'x'
      ins.post new noflo.IP 'data', 'bar',
        scope: 'x'
      ins.post new noflo.IP 'closeBracket', 'a',
        scope: 'x'
      ins.post new noflo.IP 'closeBracket', 1,
        scope: 'x'

  describe 'pure Process API merging two inputs', ->
    c = null
    in1 = null
    in2 = null
    out = null
    before (done) ->
      fbpData = "
      INPORT=Pc1.IN:IN1
      INPORT=Pc2.IN:IN2
      OUTPORT=PcMerge.OUT:OUT
      Pc1(process/Async) OUT -> IN1 PcMerge(process/Merge)
      Pc2(process/Async) OUT -> IN2 PcMerge(process/Merge)
      "
      noflo.graph.loadFBP fbpData, (err, g) ->
        return done err if err
        loader.registerComponent 'scope', 'Merge', g
        loader.load 'scope/Merge', (err, instance) ->
          return done err if err
          c = instance
          in1 = noflo.internalSocket.createSocket()
          c.inPorts.in1.attach in1
          in2 = noflo.internalSocket.createSocket()
          c.inPorts.in2.attach in2
          done()
    beforeEach ->
      out = noflo.internalSocket.createSocket()
      c.outPorts.out.attach out
    afterEach ->
      c.outPorts.out.detach out
      out = null

    it 'should forward new-style brackets as expected', (done) ->
      expected = [
        'CONN'
        '< 1'
        '< a'
        'DATA 1bazPc1:2fooPc2:PcMerge'
        '>'
        '>'
        'DISC'
      ]
      received = []

      out.on 'connect', ->
        received.push 'CONN'
      out.on 'begingroup', (group) ->
        received.push "< #{group}"
      out.on 'data', (data) ->
        received.push "DATA #{data}"
      out.on 'endgroup', ->
        received.push '>'
      out.on 'disconnect', ->
        received.push 'DISC'
        chai.expect(received).to.eql expected
        done()

      in2.connect()
      in2.send 'foo'
      in2.disconnect()
      in1.connect()
      in1.beginGroup 1
      in1.beginGroup 'a'
      in1.send 'baz'
      in1.endGroup()
      in1.endGroup()
      in1.disconnect()
    it 'should forward new-style brackets as expected regardless of sending order', (done) ->
      expected = [
        'CONN'
        '< 1'
        '< a'
        'DATA 1bazPc1:2fooPc2:PcMerge'
        '>'
        '>'
        'DISC'
      ]
      received = []

      out.on 'connect', ->
        received.push 'CONN'
      out.on 'begingroup', (group) ->
        received.push "< #{group}"
      out.on 'data', (data) ->
        received.push "DATA #{data}"
      out.on 'endgroup', ->
        received.push '>'
      out.on 'disconnect', ->
        received.push 'DISC'
        chai.expect(received).to.eql expected
        done()

      in1.connect()
      in1.beginGroup 1
      in1.beginGroup 'a'
      in1.send 'baz'
      in1.endGroup()
      in1.endGroup()
      in1.disconnect()
      in2.connect()
      in2.send 'foo'
      in2.disconnect()
    it 'should forward scopes as expected', (done) ->
      expected = [
        'x < 1'
        'x DATA 1onePc1:2twoPc2:PcMerge'
        'x >'
      ]
      received = []
      brackets = []

      out.on 'ip', (ip) ->
        switch ip.type
          when 'openBracket'
            received.push "#{ip.scope} < #{ip.data}"
            brackets.push ip.data
          when 'data'
            received.push "#{ip.scope} DATA #{ip.data}"
          when 'closeBracket'
            received.push "#{ip.scope} >"
            brackets.pop()
            return if brackets.length
            chai.expect(received).to.eql expected
            done()

      in2.post new noflo.IP 'data', 'two',
        scope: 'x'
      in1.post new noflo.IP 'openBracket', 1,
        scope: 'x'
      in1.post new noflo.IP 'data', 'one',
        scope: 'x'
      in1.post new noflo.IP 'closeBracket', 1,
        scope: 'x'

  describe 'Process API with IIPs and scopes', ->
    c = null
    in1 = null
    in2 = null
    out = null
    before (done) ->
      fbpData = "
      INPORT=Pc1.IN:IN1
      OUTPORT=PcMerge.OUT:OUT
      Pc1(process/Async) -> IN1 PcMerge(process/Merge)
      'twoIIP' -> IN2 PcMerge(process/Merge)
      "
      noflo.graph.loadFBP fbpData, (err, g) ->
        return done err if err
        loader.registerComponent 'scope', 'MergeIIP', g
        loader.load 'scope/MergeIIP', (err, instance) ->
          return done err if err
          c = instance
          in1 = noflo.internalSocket.createSocket()
          c.inPorts.in1.attach in1
          done()
    beforeEach ->
      out = noflo.internalSocket.createSocket()
      c.outPorts.out.attach out
    afterEach ->
      c.outPorts.out.detach out
      out = null

    it 'should forward scopes as expected', (done) ->
      expected = [
        'x < 1'
        'x DATA 1onePc1:2twoIIP:PcMerge'
        'x >'
      ]
      received = []
      brackets = []

      out.on 'ip', (ip) ->
        switch ip.type
          when 'openBracket'
            received.push "#{ip.scope} < #{ip.data}"
            brackets.push ip.data
          when 'data'
            received.push "#{ip.scope} DATA #{ip.data}"
          when 'closeBracket'
            received.push "#{ip.scope} >"
            brackets.pop()
            return if brackets.length
            chai.expect(received).to.eql expected
            done()

      in1.post new noflo.IP 'openBracket', 1, scope: 'x'
      in1.post new noflo.IP 'data', 'one', scope: 'x'
      in1.post new noflo.IP 'closeBracket', 1, scope: 'x'

  describe 'Process API with IIPs to addressable ports and scopes', ->
    c = null
    in1 = null
    in2 = null
    out = null
    before (done) ->
      fbpData = "
      INPORT=Pc1.IN:IN1
      OUTPORT=PcMergeA.OUT:OUT
      Pc1(process/Async) -> IN1 PcMergeA(process/MergeA)
      'twoIIP0' -> IN2[0] PcMergeA
      'twoIIP1' -> IN2[1] PcMergeA
      "
      noflo.graph.loadFBP fbpData, (err, g) ->
        return done err if err
        loader.registerComponent 'scope', 'MergeIIPA', g
        loader.load 'scope/MergeIIPA', (err, instance) ->
          return done err if err
          c = instance
          in1 = noflo.internalSocket.createSocket()
          c.inPorts.in1.attach in1
          done()
    beforeEach ->
      out = noflo.internalSocket.createSocket()
      c.outPorts.out.attach out
    afterEach ->
      c.outPorts.out.detach out
      out = null

    it 'should forward scopes as expected', (done) ->
      expected = [
        'x < 1'
        'x DATA 1onePc1:2twoIIP0:2twoIIP1:PcMergeA'
        'x >'
      ]
      received = []
      brackets = []

      out.on 'ip', (ip) ->
        switch ip.type
          when 'openBracket'
            received.push "#{ip.scope} < #{ip.data}"
            brackets.push ip.data
          when 'data'
            received.push "#{ip.scope} DATA #{ip.data}"
          when 'closeBracket'
            received.push "#{ip.scope} >"
            brackets.pop()
            return if brackets.length
            chai.expect(received).to.eql expected
            done()

      in1.post new noflo.IP 'openBracket', 1, scope: 'x'
      in1.post new noflo.IP 'data', 'one', scope: 'x'
      in1.post new noflo.IP 'closeBracket', 1, scope: 'x'
