'use strict'

/* jshint maxlen:400 */

var chai = require('chai')
var createParser = require('./helper').createParser

// Helper to check an iip.
// source iip id is always unique so we only check whether it was set.
function checkIIP (real, expected) {
  chai.expect(expected).to.have.property('source')
  chai.expect(expected).to.have.property('target')
  chai.expect(expected).to.have.property('data')

  chai.expect(real).to.have.property('target')
  chai.expect(real).to.have.property('data')

  // check source structure
  chai.expect(real.source).to.have.property('port')

  // check target structure
  chai.expect(real.target).to.have.property('id')
  chai.expect(real.target).to.have.property('port')
  chai.expect(real.target).to.eql(expected.target)

  // check data
  chai.expect(real.data).to.eql(expected.data)

  // copy id and compare both together
  // expected.source.id = real.source.id;

  // do not care that much about metadata.title at the moment..
  expected.metadata = real.metadata

  chai.expect(real).to.eql(expected)
}

function checkIIPs (real, expected) {
  real.forEach(function (r, idx) {
    checkIIP(r, expected[idx])
  })
}

describe('FBP parser', function () {
  describe('with simple FBP string', function () {
    var fbpData

    var parser = createParser()
    fbpData = '\'somefile\' -> SOURCE Read(ReadFile)\n\''

    it('should provide a parse method', function () {
      chai.expect(parser.parse).to.be.a('function')
    })

    describe('the generated graph', function () {
      var graphData = parser.parse(fbpData)

      it('should contain one node', function () {
        chai.expect(graphData.nodes[0]).to.eql({
          id: 'Read',
          title: 'Read',
          name: 'ReadFile'
        })
      })

      it('should contain an IIP', function () {
        var iips = parser.getIIPs()
        chai.expect(iips).to.be.an('array')
        var expect = [{
          source: {
            port: ':iip'
          },
          target: {
            id: 'Read',
            port: 'SOURCE'
          },
          data: 'somefile'
        }]
        checkIIPs(iips, expect)
      })
    })
  })

  describe('Sending iips to different nodes using the same component', function () {
    var fbpData

    var parser = createParser()
    fbpData = '\'somefile\' -> SOURCE Read(ReadFile), \'file2\' -> SOURCE Read2(ReadFile)\n'

    describe('the generated graph', function () {
      var graphData = parser.parse(fbpData)

      it('should contain two nodes', function () {
        chai.expect(graphData.nodes[0]).to.eql({
          id: 'Read',
          title: 'Read',
          name: 'ReadFile'
        })

        chai.expect(graphData.nodes[1]).to.eql({
          id: 'Read2',
          title: 'Read2',
          name: 'ReadFile'
        })
      })

      it('should contain two IIPs', function () {
        var iips = parser.getIIPs()
        chai.expect(iips).to.be.an('array')
        var expect = [
          {
            source: {port: ':iip'},
            target: {id: 'Read', port: 'SOURCE'},
            data: 'somefile'
          },
          {
            source: {'port': ':iip'},
            target: {id: 'Read2', port: 'SOURCE'},
            data: 'file2'
          }
        ]
        checkIIPs(iips, expect)
      })
    })
  })

  describe('with three-statement FBP string', function () {
    var fbpData, graphData

    var parser = createParser()

    fbpData = '\'somefile.txt\' -> SOURCE Read(ReadFile) OUT -> IN Display(Output)'
    graphData = parser.parse(fbpData)

    it('should produce a graph JSON object', function () {
      chai.expect(graphData).to.be.an('object')
    })

    describe('the generated graph', function () {
      it('should contain two nodes and data', function () {
        chai.expect(graphData.nodes).to.eql([
          {
            id: 'Read',
            title: 'Read',
            name: 'ReadFile'
          },
          {
            id: 'Display',
            title: 'Display',
            name: 'Output'
          }
        ])
      })

      it('should contain an edge', function () {
        chai.expect(graphData.links).to.be.an('array')
        chai.expect(graphData.links.length).to.equal(1)
      })

      it('should contain an IIP', function () {
        chai.expect(graphData.links).to.be.an('array')

        checkIIPs(parser.getIIPs(), [
          {
            data: 'somefile.txt',
            source: {
              port: ':iip'
            },
            target: {
              id: 'Read',
              port: 'SOURCE'
            }
          }]
        )
      })
    })
  })

  describe('with a more complex FBP string', function () {
    var fbpData, graphData, parser

    fbpData = [
      '\'8003\' -> LISTEN WebServer(HTTP/Server) REQUEST -> IN Profiler(HTTP/Profiler) OUT -> IN Authentication(HTTP/BasicAuth)',
      'Authentication() OUT -> IN GreetUser(HelloController) OUT -> IN WriteResponse(HTTP/WriteResponse) OUT -> IN Send(HTTP/SendResponse)',
      '\'hello.jade\' -> SOURCE ReadTemplate(ReadFile) OUT -> TEMPLATE Render(Template)',
      'GreetUser() DATA -> OPTIONS Render() OUT -> STRING WriteResponse()'
    ].join('\n')

    parser = createParser()
    graphData = parser.parse(fbpData)

    it('should produce a graph JSON object', function () {
      chai.expect(graphData).to.be.an('object')
    })

    describe('the generated graph', function () {
      it('should contain eight nodes', function () {
        chai.expect(graphData.nodes).to.be.an('array')
        var keys = graphData.nodes.map(function (node) { return node.id })
        chai.expect(keys).to.eql([
          'WebServer', 'Profiler', 'Authentication',
          'GreetUser', 'WriteResponse', 'Send',
          'ReadTemplate', 'Render'
        ])
      })

      it('should contain eight edges', function () {
        chai.expect(graphData.links).to.be.an('array')
        chai.expect(graphData.links.length).to.equal(8)
      })

      it('should contain and two IIPs', function () {
        chai.expect(parser.renderer.getIIPs()).to.be.an('array')
        checkIIPs(parser.renderer.getIIPs(),
          [
            {
              data: '8003',
              source: {port: ':iip'},
              target: {id: 'WebServer', port: 'LISTEN'}
            },
            {
              data: 'hello.jade',
              source: {port: ':iip'},
              target: {id: 'ReadTemplate', port: 'SOURCE'}
            }
          ])
      })
    })
  })

  describe('with FBP string containing an IIP with whitespace', function () {
    var fbpData, graphData, parser

    fbpData = '\'foo Bar BAZ\' -> IN Display(Output)'
    parser = createParser()
    graphData = parser.parse(fbpData)

    it('should produce a graph JSON object', function () {
      chai.expect(graphData).to.be.an('object')
    })

    describe('the generated graph', function () {
      it('should contain a node', function () {
        chai.expect(graphData.nodes).to.eql([
          {id: 'Display', title: 'Display', name: 'Output'}
        ])
      })

      it('should contain an IIP', function () {
        chai.expect(graphData.links).to.be.an('array')
        chai.expect(graphData.links.length).to.equal(0)
        checkIIPs(parser.renderer.getIIPs(),
          [
            {
              data: 'foo Bar BAZ',
              source: {port: ':iip'},
              target: {id: 'Display', port: 'IN'}
            }
          ])
      })
    })
  })

  describe('with FBP string containing an empty IIP string', function () {
    var fbpData, graphData, parser
    fbpData = '\'\' -> IN Display(Output)'
    parser = createParser()
    graphData = parser.parse(fbpData)

    it('should produce a graph JSON object', function () {
      chai.expect(graphData).to.be.an('object')
    })

    describe('the generated graph', function () {
      it('should contain a node', function () {
        chai.expect(graphData.nodes).to.eql([
          {
            id: 'Display',
            title: 'Display',
            name: 'Output'
          }
        ])
      })

      it('should contain an IIP', function () {
        chai.expect(graphData.links).to.be.an('array')
        chai.expect(graphData.links.length).to.equal(0)
        checkIIPs(parser.renderer.getIIPs(),
          [
            {
              data: '',
              source: {port: ':iip'},
              target: {id: 'Display', port: 'IN'}
            }
          ])
      })
    })
  })

  describe('with FBP string containing comments', function () {
    var fbpData, graphData, parser
    fbpData = '# Do stuff\n\'foo bar\' -> IN Display(Output) # Here we show the string'
    parser = createParser()
    graphData = parser.parse(fbpData)

    it('should produce a graph JSON object', function () {
      chai.expect(graphData).to.be.an('object')
    })

    describe('the generated graph', function () {
      it('should contain a node', function () {
        chai.expect(graphData.nodes).to.eql([{
          id: 'Display',
          title: 'Display',
          name: 'Output'
        }])
      })

      it('should contain an IIP', function () {
        chai.expect(graphData.links).to.be.an('array')
        chai.expect(graphData.links.length).to.equal(0)
        checkIIPs(parser.renderer.getIIPs(),
          [
            {
              data: 'foo bar',
              source: {port: ':iip'},
              target: {id: 'Display', port: 'IN'}
            }
          ])
      })
    })
  })

  describe('with FBP string containing URL as IIP', function () {
    var fbpData, graphData, parser
    fbpData = '\'http://localhost:5984/default\' -> URL Conn(couchdb/OpenDatabase)'
    parser = createParser()
    graphData = parser.parse(fbpData)

    it('should produce a graph JSON object', function () {
      chai.expect(graphData).to.be.an('object')
    })

    describe('the generated graph', function () {
      it('should contain a node', function () {
        chai.expect(graphData.nodes).to.eql([{
          id: 'Conn',
          title: 'Conn',
          ns: 'couchdb',
          name: 'OpenDatabase'
        }])
      })

      it('should contain an IIP', function () {
        chai.expect(graphData.links).to.be.an('array')
        chai.expect(graphData.links.length).to.equal(0)
        checkIIPs(parser.renderer.getIIPs(),
          [
            {
              data: 'http://localhost:5984/default',
              source: {port: ':iip'},
              target: {id: 'Conn', port: 'URL'}
            }
          ])
      })
    })
  })

  describe('with FBP string containing RegExp as IIP', function () {
    var fbpData, graphData, parser

    fbpData = [
      '\'_id=(\d+\.\d+\.\d*)=http://iks-project.eu/%deliverable/$1\' -> REGEXP MapDeliverableUri(MapPropertyValue)',
      '\'path=/_(?!(includes|layouts)\' -> REGEXP MapDeliverableUri(MapPropertyValue)',
      '\'@type=deliverable\' -> PROPERTY SetDeliverableProps(SetProperty)',
      '\'#foo\' -> SELECTOR Get(dom/GetElement)',
      '\'Hi, {{ name }}\' -> TEMPLATE Get'
    ].join('\n')

    parser = createParser()
    graphData = parser.parse(fbpData)

    it('should produce a graph JSON object', function () {
      chai.expect(graphData).to.be.an('object')
    })

    describe('the generated graph', function () {
      it('should contain two nodes', function () {
        chai.expect(graphData.nodes).to.eql([
          {
            id: 'MapDeliverableUri',
            title: 'MapDeliverableUri',
            name: 'MapPropertyValue'
          },
          {
            id: 'SetDeliverableProps',
            title: 'SetDeliverableProps',
            name: 'SetProperty'
          },
          {
            id: 'Get',
            title: 'Get',
            ns: 'dom',
            name: 'GetElement'
          }
        ])
      })

      it('should contain IIPs', function () {
        chai.expect(graphData.links).to.be.an('array')
        chai.expect(graphData.links.length).to.equal(0)
        checkIIPs(parser.renderer.getIIPs(),
          [
            {
              data: '_id=(d+.d+.d*)=http://iks-project.eu/%deliverable/$1',
              source: {
                port: ':iip'
              },
              target: {
                id: 'MapDeliverableUri',
                port: 'REGEXP'
              }
            },
            {
              data: 'path=/_(?!(includes|layouts)',
              source: {
                port: ':iip'
              },
              target: {
                id: 'MapDeliverableUri',
                port: 'REGEXP'
              }
            },
            {
              data: '@type=deliverable',
              source: {
                port: ':iip'
              },
              target: {
                id: 'SetDeliverableProps',
                port: 'PROPERTY'
              }
            },
            {
              data: '#foo',
              source: {
                port: ':iip'
              },
              target: {
                id: 'Get',
                port: 'SELECTOR'
              }
            },
            {
              data: 'Hi, {{ name }}',
              source: {
                port: ':iip'
              },
              target: {
                id: 'Get',
                port: 'TEMPLATE'
              }
            }
          ])
      })
    })
  })

  describe('with FBP string with EXPORTs', function () {
    var fbpData, graphData

    fbpData = [
      '-> IN Read(ReadFile)',
      'Read OUT -> IN Display(Output)',
      '<- out Display'
    ].join('\n')

    graphData = null

    var parser = createParser()
    graphData = parser.parse(fbpData)

    it('should produce a graph JSON object', function () {
      chai.expect(graphData).to.be.an('object')
    })

    describe('the generated graph', function () {
      it('should contain two nodes', function () {
        chai.expect(graphData.nodes).to.eql([
          {
            id: 'Read',
            title: 'Read',
            name: 'ReadFile'
          },
          {
            id: 'Display',
            title: 'Display',
            name: 'Output'
          }
        ])
      })

      it('should contain external ports', function () {
        chai.expect(graphData.ports).to.eql(
          {
            input: {
              IN: {
                name: 'IN',
                nodeId: 'Read',
                title: 'IN'
              }
            },
            output: {
              out: {
                name: 'out',
                nodeId: 'Display',
                title: 'Out'
              }
            }
          }
        )
      })

      it('should contain a single connection', function () {
        chai.expect(graphData.links).to.be.an('array')
        chai.expect(graphData.links.length).to.equal(1)

        var res = {
          source: {
            id: 'Read',
            port: 'OUT'
          },
          target: {
            id: 'Display',
            port: 'IN'
          },
          metadata: {
            title: 'Read OUT -> IN Display'
          }
        }

        chai.expect(graphData.links[0]).to.eql(res)
      })
    })
  })

  describe('with FBP string containing EXPORTs, persistent ports, alternative names and index information', function () {
    var fbpData, graphData
    fbpData = '-> ^IN Read(ReadFile)\n-> [prop] ^IN2:Input2 Read\n Read OUT -> IN Display(Output)\n <- out Display'

    var parser = createParser()
    graphData = parser.parse(fbpData)

    it('should produce a graph JSON object', function () {
      chai.expect(graphData).to.be.an('object')
    })

    describe('the generated graph', function () {
      it('should contain two nodes', function () {
        chai.expect(graphData.nodes).to.eql([
          {
            id: 'Read',
            title: 'Read',
            name: 'ReadFile'
          },
          {
            id: 'Display',
            title: 'Display',
            name: 'Output'
          }
        ])
      })

      it('should contain external ports', function () {
        chai.expect(graphData.ports).to.eql(
          {
            'input': {
              'IN': {
                'name': 'IN',
                'nodeId': 'Read',
                'title': 'IN',
                'setting': {
                  'persist': true
                }
              },
              'Input2': {
                'name': 'IN2',
                'nodeId': 'Read',
                'title': 'Input2',
                'setting': {
                  'persist': true,
                  'index': 'prop'
                }
              }
            },
            'output': {
              'out': {
                'name': 'out',
                'nodeId': 'Display',
                'title': 'Out'
              }
            }
          }
        )
      })

      it('should contain a single connection', function () {
        chai.expect(graphData.links).to.be.an('array')
        chai.expect(graphData.links.length).to.equal(1)

        var res = {
          source: {
            id: 'Read',
            port: 'OUT'
          },
          target: {
            id: 'Display',
            port: 'IN'
          },
          metadata: {
            title: 'Read OUT -> IN Display'
          }
        }

        chai.expect(graphData.links[0]).to.eql(res)
      })
    })
  })

  describe('with an invalid FBP string', function () {
    var fbpData, parser
    fbpData = '\'foo\' -> Display(Output)'

    it('should fail with an Exception', function () {
      chai.expect(function () {
        parser = createParser()
        parser.parse(fbpData)
      }).to['throw'](Error)
    })
  })

  describe('with a component that contains dashes in name', function () {
    var fbpData, graphData, parser

    fbpData = '\'somefile\' -> SOURCE Read(my-cool-component/ReadFile)'

    parser = createParser()
    graphData = parser.parse(fbpData)

    it('should produce a graph JSON object', function () {
      chai.expect(graphData).to.be.an('object')
    })

    describe('the generated graph', function () {
      it('should contain one node', function () {
        chai.expect(graphData.nodes).to.eql([{
          id: 'Read',
          title: 'Read',
          ns: 'my-cool-component',
          name: 'ReadFile'
        }])
      })

      it('should contain an IIP', function () {
        chai.expect(parser.getIIPs()).to.be.an('array')
        checkIIPs(parser.renderer.getIIPs(),
          [
            {
              'data': 'somefile',
              'source': {
                'port': ':iip'
              },
              'target': {
                'id': 'Read',
                'port': 'SOURCE'
              }
            }
          ])
      })
    })
  })

  describe('with commas to separate statements', function () {
    var fbpData, graphData, parser
    fbpData = '\'Hello\' -> IN Foo(Component), \'World\' -> IN Bar(OtherComponent), Foo OUT -> DATA Bar'
    graphData = null
    it('should produce a graph JSON object', function () {
      parser = createParser()
      graphData = parser.parse(fbpData)
      chai.expect(graphData).to.be.an('object')
    })
    describe('the generated graph', function () {
      it('should contain two nodes', function () {
        chai.expect(graphData.nodes).to.eql([
          {
            id: 'Foo',
            title: 'Foo',
            name: 'Component'
          },
          {
            id: 'Bar',
            title: 'Bar',
            name: 'OtherComponent'
          }
        ])
      })

      it('should contain one edge', function () {
        chai.expect(graphData.links).to.eql([
          {
            source: {
              id: 'Foo',
              port: 'OUT'
            },
            metadata: {
              title: 'Foo OUT -> DATA Bar'
            },
            target: {
              id: 'Bar',
              port: 'DATA'
            }
          }
        ])
      })

      it('should contain two IIPs', function () {
        checkIIPs(parser.getIIPs(),
          [
            {
              'data': 'Hello',
              'source': {
                'port': ':iip'
              },
              'target': {
                'id': 'Foo',
                'port': 'IN'
              }
            },
            {
              'data': 'World',
              'source': {
                'port': ':iip'
              },
              'target': {
                'id': 'Bar',
                'port': 'IN'
              }
            }
          ])
      })
    })
  })

  describe('with underscores and numbers in ports, nodes, and components', function () {
    var fbpData, graphData, parser
    fbpData = '\'Hello 09\' -> IN_2 Foo_Node_42(Component_15)'
    graphData = null
    it('should produce a graph JSON object', function () {
      parser = createParser()
      graphData = parser.parse(fbpData)
      chai.expect(graphData).to.be.an('object')
    })
    describe('the generated graph', function () {
      it('should contain one node', function () {
        chai.expect(graphData.nodes).to.eql([
          {
            id: 'Foo_Node_42',
            title: 'Foo_Node_42',
            name: 'Component_15'
          }
        ])
      })
      it('should contain an IIP', function () {
        chai.expect(graphData.links).to.be.an('array')
        chai.expect(graphData.links.length).to.equal(0)
        checkIIPs(parser.renderer.getIIPs(),
          [
            {
              'data': 'Hello 09',
              'source': {
                'port': ':iip'
              },
              'target': {
                'id': 'Foo_Node_42',
                'port': 'IN_2'
              }
            }
          ])
      })
    })
  })

  describe('with array index on ports', function () {
    var fbpData, graphData, parser
    fbpData = '\'Hello 09\' -> [0] IN_2 Foo_Node_42(Component_15) OUT [9] -> [1] IN Some(Comp)'
    graphData = null
    it('should produce a graph JSON object', function () {
      parser = createParser()
      graphData = parser.parse(fbpData)
      chai.expect(graphData).to.be.an('object')
    })
    describe('the generated graph', function () {
      it('should contain two nodes', function () {
        chai.expect(graphData.nodes.length).to.eql(2)
      })
      it('the nodes should have an input and output index', function () {
        chai.expect(graphData.links).to.eql([
          {
            'metadata': {
              'title': 'Foo_Node_42 OUT -> IN Some'
            },
            'source': {
              'id': 'Foo_Node_42',
              'port': 'OUT',
              'setting': {
                'index': 9
              }
            },
            'target': {
              'id': 'Some',
              'port': 'IN',
              'setting': {
                'index': 1
              }
            }
          }])
      })
      it('should contain an IIP with index', function () {
        chai.expect(graphData.links).to.be.an('array')
        chai.expect(parser.renderer.getIIPs(),
          [
            {
              'data': 'Hello 09',
              'source': {
                'port': ':iip'
              },
              'target': {
                'id': 'Foo_Node_42',
                'port': 'IN_2',
                'setting': {
                  'index': 0
                }
              }
            }
          ])
      })
    })
  })

  describe('with numeric, float and boolean ips', function () {
    var fbpData, graphData, parser
    fbpData = [
      '9 -> IN Foo(Comp)',
      '7 -> IN Foo()',
      'true -> IN Bar(Bar)',
      'false -> IN Bar()',
      '0.0091 -> in Bla()',
      '.3 -> in Bla()',
      '-3 -> in Bla()'
    ].join(',')

    graphData = null
    it('should produce a graph JSON object', function () {
      parser = createParser()
      graphData = parser.parse(fbpData)
      chai.expect(graphData).to.be.an('object')
    })
    describe('the generated graph', function () {
      it('should contain an IIP', function () {
        chai.expect(graphData.links).to.be.an('array')
        chai.expect(graphData.links.length).to.equal(0)
        checkIIPs(parser.renderer.getIIPs(),
          [
            {
              'data': 9,
              'source': {
                'port': ':iip'
              },
              'target': {
                'id': 'Foo',
                'port': 'IN'
              }
            },
            {
              'data': 7,
              'source': {
                'port': ':iip'
              },
              'target': {
                'id': 'Foo',
                'port': 'IN'
              }
            },
            {
              'data': true,
              'source': {
                'port': ':iip'
              },
              'target': {
                'id': 'Bar',
                'port': 'IN'
              }
            },
            {
              'data': false,
              'source': {
                'port': ':iip'
              },
              'target': {
                'id': 'Bar',
                'port': 'IN'
              }
            },
            {
              'data': 0.0091,
              'source': {
                'port': ':iip'
              },
              'target': {
                'id': 'Bla',
                'port': 'in'
              }
            },
            {
              'data': 0.3,
              'source': {
                'port': ':iip'
              },
              'target': {
                'id': 'Bla',
                'port': 'in'
              }
            },
            {
              'data': -3,
              'source': {
                'port': ':iip'
              },
              'target': {
                'id': 'Bla',
                'port': 'in'
              }
            }
          ])
      })
    })
  })

  describe('a connection can define itself as being cyclic', function () {
    var fbpData, graphData
    fbpData = 'Test(my/test) out => IN SomeNode(some/node)'
    graphData = null
    it('should produce a graph JSON object', function () {
      var parser = createParser()
      graphData = parser.parse(fbpData)
      chai.expect(graphData).to.be.an('object')
    })
    describe('the generated graph', function () {
      it('should contain a cyclic connection', function () {
        chai.expect(graphData.links).to.eql([
          {
            source: {
              id: 'Test',
              port: 'out'
            },
            target: {
              id: 'SomeNode',
              port: 'IN',
              setting: {
                cyclic: true
              }
            },
            metadata: {
              title: 'Test out -> IN SomeNode'
            }
          }
        ])
      })
    })
  })

  describe('both data and connections can define an index', function () {
    var fbpData, graphData
    fbpData = '\'./graphs\' -> [0] in Join(path/join)\nGlob(fs/glob) match -> [1] in Join'

    var parser = createParser()
    graphData = parser.parse(fbpData)

    it('should produce a graph JSON object', function () {
      chai.expect(graphData).to.be.an('object')
    })

    it('should contain an IIP with index information', function () {
      checkIIPs(parser.getIIPs(),
        [
          {
            'data': './graphs',
            'source': {
              'port': ':iip'
            },
            'target': {
              'id': 'Join',
              'port': 'in',
              'setting': {
                'index': 0
              }
            }
          }
        ])
    })

    it('should contain 2 nodes', function () {
      chai.expect(graphData.nodes).to.eql(
        [
          {
            'id': 'Join',
            'name': 'join',
            'ns': 'path',
            'title': 'Join'
          },
          {
            'id': 'Glob',
            'name': 'glob',
            'ns': 'fs',
            'title': 'Glob'
          }
        ])
    })

    it('should contain a link with index information', function () {
      chai.expect(graphData.links).to.eql(
        [
          {
            'metadata': {
              'title': 'Glob match -> in Join'
            },
            'source': {
              'id': 'Glob',
              'port': 'match'
            },
            'target': {
              'id': 'Join',
              'port': 'in',
              'setting': {
                'index': 1
              }
            }
          }
        ]
      )
    })
  })

  describe('both data and connections can define an object index', function () {
    var fbpData, graphData
    fbpData = '\'./graphs\' -> [title] in Join(path/join)\nGlob(fs/glob) match [/test] -> [prop] in Join'

    var parser = createParser()
    graphData = parser.parse(fbpData)

    it('should produce a graph JSON object', function () {
      chai.expect(graphData).to.be.an('object')
    })

    it('should contain an IIP with index information', function () {
      checkIIPs(parser.getIIPs(),
        [
          {
            'data': './graphs',
            'source': {
              'port': ':iip'
            },
            'target': {
              'id': 'Join',
              'port': 'in',
              'setting': {
                'index': 'title'
              }
            }
          }
        ])
    })

    it('should contain 2 nodes', function () {
      chai.expect(graphData.nodes).to.eql(
        [
          {
            'id': 'Join',
            'name': 'join',
            'ns': 'path',
            'title': 'Join'
          },
          {
            'id': 'Glob',
            'name': 'glob',
            'ns': 'fs',
            'title': 'Glob'
          }
        ])
    })

    it('should contain a link with index information', function () {
      chai.expect(graphData.links).to.eql(
        [
          {
            'metadata': {
              'title': 'Glob match -> in Join'
            },
            'source': {
              'id': 'Glob',
              'port': 'match',
              'setting': {
                'index': '/test'
              }
            },
            'target': {
              'id': 'Join',
              'port': 'in',
              'setting': {
                'index': 'prop'
              }
            }
          }
        ]
      )
    })
  })

  describe('can define context', function () {
    var fbpData, graphData
    fbpData = '\'data\' -> @IN SomeNode(some/node)'
    graphData = null
    it('should produce a graph JSON object', function () {
      var parser = createParser()
      graphData = parser.parse(fbpData)
      chai.expect(graphData).to.be.an('object')
    })
    describe('the generated graph', function () {
      it('node should have context', function () {
        chai.expect(graphData.nodes).to.eql([{
          id: 'SomeNode',
          title: 'SomeNode',
          ns: 'some',
          name: 'node',
          context: {
            IN: 'data'
          }
        }
        ])
      })
    })
    describe('the generated graph has context', function () {
      it('should not contain any connections', function () {
        chai.expect(graphData.links).to.eql([])
      })
    })
  })

  describe('can a process and then define context', function () {
    var fbpData, graphData
    fbpData = 'SomeNode(some/node)\n \'data\' -> @IN SomeNode()'
    graphData = null
    it('should produce a graph JSON object', function () {
      var parser = createParser()
      graphData = parser.parse(fbpData)
      chai.expect(graphData).to.be.an('object')
    })
    describe('the generated graph', function () {
      it('node should have context', function () {
        chai.expect(graphData.nodes).to.eql([{
          id: 'SomeNode',
          title: 'SomeNode',
          ns: 'some',
          name: 'node',
          context: {
            IN: 'data'
          }
        }
        ])
      })
    })
    describe('the generated graph has context', function () {
      it('should not contain any connections', function () {
        chai.expect(graphData.links).to.eql([])
      })
    })
  })

  describe('a connection can define itself as persistent', function () {
    var fbpData, graphData
    fbpData = 'Test(my/test) out -> ^IN SomeNode(some/node)'
    graphData = null
    it('should produce a graph JSON object', function () {
      var parser = createParser()
      graphData = parser.parse(fbpData)
      chai.expect(graphData).to.be.an('object')
    })
    describe('the generated graph', function () {
      it('should contain a persistent connection', function () {
        chai.expect(graphData.links).to.eql([
          {
            source: {
              id: 'Test',
              port: 'out'
            },
            target: {
              id: 'SomeNode',
              port: 'IN',
              setting: {
                persist: true
              }
            },
            metadata: {
              title: 'Test out -> IN SomeNode'
            }
          }
        ])
      })
    })
  })

  describe('a connection can define itself as persistent and cyclic', function () {
    var fbpData, graphData
    fbpData = 'Test(my/test) out => ^IN SomeNode(some/node)'
    graphData = null
    it('should produce a graph JSON object', function () {
      var parser = createParser()
      graphData = parser.parse(fbpData)
      chai.expect(graphData).to.be.an('object')
    })
    describe('the generated graph', function () {
      it('should contain a persistent cyclic connection', function () {
        chai.expect(graphData.links).to.eql([
          {
            source: {
              id: 'Test',
              port: 'out'
            },
            metadata: {
              title: 'Test out -> IN SomeNode'
            },
            target: {
              id: 'SomeNode',
              port: 'IN',
              setting: {
                persist: true,
                cyclic: true
              }
            }
          }
        ])
      })
    })
  })

  describe('a connection can define itself as cyclic and a connection can collect', function () {
    var fbpData, graphData
    fbpData = 'Test(my/test) out => IN SomeNode(some/node) out >= in Collect(collect/it)'
    graphData = null
    it('should produce a graph JSON object', function () {
      var parser = createParser()
      graphData = parser.parse(fbpData)
      chai.expect(graphData).to.be.an('object')
    })
    describe('the generated graph', function () {
      it('should contain a cyclic and a collect connection', function () {
        chai.expect(graphData.links).to.eql([
          {
            source: {
              id: 'Test',
              port: 'out'
            },
            metadata: {
              title: 'Test out -> IN SomeNode'
            },
            target: {
              id: 'SomeNode',
              port: 'IN',
              setting: {
                cyclic: true
              }
            }
          },
          {
            source: {
              id: 'SomeNode',
              port: 'out',
              setting: {
                collect: true
              }
            },
            metadata: {
              title: 'SomeNode out -> in Collect'
            },
            target: {
              id: 'Collect',
              port: 'in'
            }
          }
        ])
      })
    })
  })
/*
  describe('an IIP can define itself as persistent', function() {
    var fbpData, graphData, iips;
    fbpData = '\'somefile\' -> ^SOURCE Read(ReadFile)\n';
    graphData = null;
    it('should produce a graph JSON object', function() {

      graphData = parser.parse(fbpData);
      iips = parser.renderer.getIIPs();
       chai.expect(graphData).to.be.an('object');
    });
     describe('the generated graph', function() {
      it('should contain one node', function() {
         chai.expect(graphData.nodes[0]).to.eql({
          title: 'Read',
          name: 'ReadFile'
        });
      });
       it('should contain an IIP', function() {
        chai.expect(iips).to.be.an('object');

        // ok this seems weird, but no we can send
        // multiple IIPs and even specify which one
        // should persist and also cycle.
        // Imagine being able to send an array to a port
        // and also a single entry.
        // Like a globed array of files + just some
        // single files, the globbed array will define
        // itself as cyclic.
         chai.expect(iips).to.eql({
          Read: { source: [{
            data:'somefile',
            settings: {
              persist: true
            }
          }] }
        });
      });
    });
  });
*/

  describe('allow a process to be defined without connecting it', function () {
    var fbpData, graphData
    fbpData = 'SomeNode(some/node)'
    graphData = null
    it('should produce a graph JSON object', function () {
      var parser = createParser()
      graphData = parser.parse(fbpData)
      chai.expect(graphData).to.be.an('object')
    })
    describe('the generated graph', function () {
      it('should contain one node', function () {
        chai.expect(graphData.nodes).to.eql([
          {
            id: 'SomeNode',
            title: 'SomeNode',
            ns: 'some',
            name: 'node'
          }
        ])
      })
      it('should not contain a connection', function () {
        chai.expect(graphData.links).to.be.an('array')
        chai.expect(graphData.links.length).to.equal(0)
      })
    })
  })

  describe('With a graph containing a Function as input', function () {
    var fbpData, graphData, iips

    var parser = createParser()
    fbpData = '( console.log("test") ) -> in Consume(consume)\n( console.log("default") ) -> @in Consume(consume)'

    describe('the generated graph', function () {
      graphData = parser.parse(fbpData)

      it('should contain one node', function () {
        chai.expect(graphData.nodes[0]).to.eql({
          id: 'Consume',
          title: 'Consume',
          name: 'consume',
          context: {
            'in': ' console.log("default") '
          }
        })
      })

      it('should contain an IIP', function () {
        iips = parser.getIIPs()
        chai.expect(iips).to.be.an('array')
        // should actually already be a function.
        checkIIPs(iips, [
          {
            'data': ' console.log("test") ',
            'source': {
              'port': ':iip'
            },
            'target': {
              'id': 'Consume',
              'port': 'in'
            }
          }])
      })
    })
  })

  describe('With a graph containing JSON Objects as input', function () {
    var fbpData, graphData, iips

    var parser = createParser()
    fbpData = '{ "hi": "huh", "me": [1,2,3] } -> in Consume(consume)\n'

    describe('the generated graph', function () {
      graphData = parser.parse(fbpData)

      it('should contain one node', function () {
        chai.expect(graphData.nodes[0]).to.eql({
          id: 'Consume',
          title: 'Consume',
          name: 'consume'
        })
      })

      it('should contain an IIP', function () {
        iips = parser.getIIPs()
        chai.expect(iips).to.be.an('array')
        checkIIPs(iips, [
          {
            'data': {
              'hi': 'huh',
              'me': [1, 2, 3]
            },
            'source': {
              'port': ':iip'
            },
            'target': {
              'id': 'Consume',
              'port': 'in'
            }
          }])
      })
    })
  })

  describe('With a graph containing actions', function () {
    var fbpData, graphData, iips

    var parser = createParser()
    fbpData = 'Proc1(proc)\n::Delete { \ntitle: Delete\n description: Delete a repository\n Proc1, Proc2(proc)\n "hi" -> in Proc1, "hey" -> in Proc2 \n } \n'

    describe('the generated graph', function () {
      graphData = parser.parse(fbpData)

      it('should contain two nodes', function () {
        chai.expect(graphData.nodes[0]).to.eql({
          id: 'Proc1',
          title: 'Proc1',
          name: 'proc'
        })
        chai.expect(graphData.nodes[1]).to.eql({
          id: 'Proc2',
          title: 'Proc2',
          name: 'proc'
        })
      })

      it('should contain one action', function () {
        chai.expect(graphData.actions).to.be.an('object')
        chai.expect(Object.keys(graphData.actions).length).to.eql(1)
        chai.expect(graphData.actions.Delete.nodes.length).to.eql(2)
        chai.expect(graphData.actions.Delete.title).to.eql('Delete')
        chai.expect(graphData.actions.Delete.description).to.eql('Delete a repository')
      })

      it.skip('should contain two IIP', function () {
        iips = parser.getIIPs()
        chai.expect(iips).to.be.an('array')
        checkIIPs(iips, [
          {
            'data': 'hi',
            'action': 'Delete',
            'source': {
              'port': ':iip'
            },
            'target': {
              'id': 'Proc1',
              'port': 'in'
            }
          },
          {
            'data': 'hey',
            'action': 'Delete',
            'source': {
              'port': ':iip'
            },
            'target': {
              'id': 'Proc2',
              'port': 'in'
            }
          }
        ])
      })
    })
  }
  )

  describe('With a graph sourcing actions', function () {
    var fbpData, graphData

    var parser = createParser()

    // ok this also indicate source should store the action also.
    // so an action also has it's own unique outports.
    fbpData = 'Proc(proc), Proc2(proc)\nProc2 out -> in Proc::my_action\nProc::my_action out -> msg Log(log)\n'

    describe('the generated graph', function () {
      graphData = parser.parse(fbpData)

      it('should contain two nodes', function () {
        chai.expect(graphData.nodes).to.eql([
          {
            id: 'Proc',
            title: 'Proc',
            name: 'proc'
          },
          {
            id: 'Proc2',
            title: 'Proc2',
            name: 'proc'
          },
          {
            id: 'Log',
            title: 'Log',
            name: 'log'
          }
        ])
      })

      it('should contain connections with actions', function () {
        chai.expect(graphData.links).to.eql([
          {
            source: {
              id: 'Proc2',
              port: 'out'
            },
            metadata: {
              title: 'Proc2 out -> in Proc::my_action'
            },
            target: {
              id: 'Proc',
              action: 'my_action',
              port: 'in'
            }
          },
          {
            source: {
              id: 'Proc',
              action: 'my_action',
              port: 'out'
            },
            metadata: {
              title: 'Proc::my_action out -> msg Log'
            },
            target: {
              id: 'Log',
              port: 'msg'
            }
          }
        ])
      })
    })
  }
  )

  describe('With a graph targeting actions', function () {
    var fbpData, graphData, iips

    var parser = createParser()

    // ok this also indicate source should store the action also.
    // so an action also has it's own unique outports.
    fbpData = '"bla" -> in Proc::my_action(proc) out -> in Proc2::other_action(proc2)\n'

    describe('the generated graph', function () {
      graphData = parser.parse(fbpData)

      it('should contain one node', function () {
        chai.expect(graphData.nodes[0]).to.eql({
          id: 'Proc',
          title: 'Proc',
          name: 'proc'
        })
      })

      it('should contain an IIP', function () {
        iips = parser.getIIPs()
        chai.expect(iips).to.be.an('array')
        checkIIPs(iips, [{
          'data': 'bla',
          'source': {
            'port': ':iip'
          },
          'target': {
            'id': 'Proc',
            'action': 'my_action',
            'port': 'in'
          }
        }])
      })

      it('should contain connections with actions', function () {
        chai.expect(graphData.links).to.eql([
          {
            source: {
              id: 'Proc',
              action: 'my_action',
              port: 'out'
            },
            metadata: {
              title: 'Proc::my_action out -> in Proc2::other_action'
            },
            target: {
              id: 'Proc2',
              action: 'other_action',
              port: 'in'
            }
          }
        ])
      })
    })
  }
  )

  describe('With another graph targeting actions', function () {
    var fbpData, graphData, iips

    var parser = createParser()

    // ok this also indicate source should store the action also.
    // so an action also has it's own unique outports.
    fbpData = 'provider ./{name}/{ns}.fbp as bla\n Test(bla:testing/test)\n Lower1(string/toLowerCase), Lower2(string/toLowerCase)\n \'file1\' -> in Lower1 out -> file Test::Delete \n \'file2\' -> in Lower2 out -> file Test::Create'

    describe('the generated graph', function () {
      graphData = parser.parse(fbpData)

      it('should contain three nodes', function () {
        chai.expect(graphData.nodes).to.eql([
          {
            id: 'Test',
            title: 'Test',
            name: 'test',
            provider: 'bla',
            ns: 'testing'
          },
          {
            id: 'Lower1',
            title: 'Lower1',
            name: 'toLowerCase',
            ns: 'string'
          },
          {
            id: 'Lower2',
            title: 'Lower2',
            name: 'toLowerCase',
            ns: 'string'
          }
        ])
      })

      it('should contain two IIP', function () {
        iips = parser.getIIPs()
        chai.expect(iips).to.be.an('array')
        checkIIPs(iips, [
          {
            'data': 'file1',
            'source': {
              'port': ':iip'
            },
            'target': {
              'id': 'Lower1',
              'port': 'in'
            }
          }, {
            'data': 'file2',
            'source': {
              'port': ':iip'
            },
            'target': {
              'id': 'Lower2',
              'port': 'in'
            }
          }
        ])
      })

      it('should contain two connections with actions', function () {
        chai.expect(graphData.links).to.eql([
          {
            source: {
              id: 'Lower1',
              port: 'out'
            },
            metadata: {
              title: 'Lower1 out -> file Test::Delete'
            },
            target: {
              id: 'Test',
              action: 'Delete',
              port: 'file'
            }
          },
          {
            source: {
              id: 'Lower2',
              port: 'out'
            },
            metadata: {
              title: 'Lower2 out -> file Test::Create'
            },
            target: {
              id: 'Test',
              action: 'Create',
              port: 'file'
            }
          }
        ])
      })
    })
  }
  )

  describe('With a graph containing JSON Arrays as input', function () {
    var fbpData, graphData, iips

    var parser = createParser()
    fbpData = '[ 1\n,\n2,\n\n3, { "bla": "di"\n} ] -> in Consume(consume)\n'

    describe('the generated graph', function () {
      graphData = parser.parse(fbpData)

      it('should contain one node', function () {
        chai.expect(graphData.nodes[0]).to.eql({
          id: 'Consume',
          title: 'Consume',
          name: 'consume'
        })
      })

      it('should contain an IIP', function () {
        iips = parser.getIIPs()
        chai.expect(iips).to.be.an('array')
        checkIIPs(iips, [{
          'data': [
            1,
            2,
            3, { bla: 'di' }
          ],
          'source': {
            'port': ':iip'
          },
          'target': {
            'id': 'Consume',
            'port': 'in'
          }
        }])
      })
    })
  }
  )

  describe('With a graph containing JSON Data', function () {
    var fbpData, graphData

    var parser = createParser()
    fbpData = [
      '"Length of 2"  ->   @msg AssertLength(assert/equal)',
      '2              -> expect AssertLength',
      '{',
      '"name": "test",',
      '"title": "Test"',
      '} -> in Keys(object/keys) out -> in Length(array/length)',
      'Length out -> in AssertLength'
    ].join('\n')

    describe('the generated graph', function () {
      graphData = parser.parse(fbpData)

      it('should contain three nodes', function () {
        chai.expect(graphData.nodes[0]).to.eql({
          id: 'AssertLength',
          title: 'AssertLength',
          ns: 'assert',
          name: 'equal',
          context: {
            msg: 'Length of 2'
          }
        })

        chai.expect(graphData.nodes[1]).to.eql({
          id: 'Keys',
          title: 'Keys',
          ns: 'object',
          name: 'keys'
        })

        chai.expect(graphData.nodes[2]).to.eql({
          id: 'Length',
          title: 'Length',
          ns: 'array',
          name: 'length'
        })
      })

      it('should contain correct links', function () {
        chai.expect(graphData.links).to.eql([
          {
            target: { id: 'Length', port: 'in' },
            source: { id: 'Keys', port: 'out' },
            metadata: { title: 'Keys out -> in Length' }
          }, {
            target: { id: 'AssertLength', port: 'in' },
            source: { id: 'Length', port: 'out' },
            metadata: { title: 'Length out -> in AssertLength' }
          }
        ])
      })
    })
  })

  describe('With a graph containing pointers and sync ports', function () {
    var fbpData, graphData

    var parser = createParser()
    fbpData = 'MyProcess(test) *out -> in[MyProcess] Consume(consume)\n'

    describe('the generated graph', function () {
      graphData = parser.parse(fbpData)

      it('should contain two nodes', function () {
        chai.expect(graphData.nodes[0]).to.eql({
          id: 'MyProcess',
          title: 'MyProcess',
          name: 'test'
        })
        chai.expect(graphData.nodes[1]).to.eql({
          id: 'Consume',
          title: 'Consume',
          name: 'consume'
        })
      })

      it('should contain a pointer and a sync port', function () {
        chai.expect(graphData.links).to.eql([
          {
            source: {
              id: 'MyProcess',
              port: 'out',
              setting: {
                pointer: true
              }
            },
            metadata: {
              title: 'MyProcess out -> in Consume'
            },
            target: {
              id: 'Consume',
              port: 'in',
              setting: {
                sync: 'MyProcess'
              }
            }
          }
        ])
      })
    })
  })

  // FBPDOC is not parsed yet, but at least allow them
  describe('With a graph containing jsdoc style comments', function () {
    var fbpData, graphData

    var parser = createParser()
    fbpData = '/* Some Comment\n @proc {MyProc} My Proc */\nMyProc(my/proc)\n'

    describe('the generated graph', function () {
      graphData = parser.parse(fbpData)

      it('should contain one node', function () {
        chai.expect(graphData.nodes[0]).to.eql({
          id: 'MyProc',
          // title: 'My Proc', FBP DOC
          title: 'MyProc',
          ns: 'my',
          name: 'proc'
        })
      })
    })
  })
})
