process.env.NODE_ENV = 'test'

path = require 'path'
assert = require 'assert'
async = require 'async'
LindaClient = require(path.resolve()).Client
TestServer = require './server'

port = process.env.PORT-0 || 13000

## start server
server = new TestServer().listen(port)
setTimeout ->
  server.close()
, 10000

## client
create_client = ->
  socket = require('socket.io-client').connect("http://localhost:#{port}")
  return new LindaClient().connect(socket)


describe 'instance of LindaClient', ->

  it 'should have method "connect"', ->
    assert.equal typeof new LindaClient()['connect'], 'function'

  it 'should have property "io"', ->
    assert.ok create_client().hasOwnProperty('io')

  it 'should have socket.io connection', (done) ->
    linda = create_client()
    ts = linda.tuplespace('chat')

    linda.io.on 'connect', ->
      assert.ok true
      done()

  it 'should have method "tuplespace"', ->
    assert.equal typeof new LindaClient()['tuplespace'], 'function'


  describe 'method "tuplespace"', ->

    it 'should have property "name"', ->
      ts = create_client().tuplespace('test')
      assert.equal ts.name, 'test'

    it 'should have method "create_callback_id"', ->
      ts = create_client().tuplespace('test')
      assert.equal typeof ts['create_callback_id'], 'function'

    it 'should have method "create_watch_callback_id"', ->
      ts = create_client().tuplespace('test')
      assert.equal typeof ts['create_watch_callback_id'], 'function'

    it 'should have method "write"', ->
      ts = create_client().tuplespace('test')
      assert.equal typeof ts['write'], 'function'

    it 'should have method "read"', ->
      ts = create_client().tuplespace('test')
      assert.equal typeof ts['read'], 'function'

    it 'should have method "take"', ->
      ts = create_client().tuplespace('test')
      assert.equal typeof ts['take'], 'function'

    it 'should have method "watch"', ->
      ts = create_client().tuplespace('test')
      assert.equal typeof ts['watch'], 'function'

    it 'should have method "cancel"', ->
      ts = create_client().tuplespace('test')
      assert.equal typeof ts['cancel'], 'function'

    describe 'method "write"', ->

      it 'should write Tuple', (done) ->
        linda = create_client()
        ts = linda.tuplespace('write')

        msg = "hello world #{new Date()}"

        assert.equal server.linda.tuplespace('write').size, 0
        linda.tuplespace('write').write {type: "chat", message: msg}
        server.linda.tuplespace('write').read {type: "chat"}, (err, tuple) ->
          assert.deepEqual tuple.data, {type: "chat", message: msg}
          assert.equal server.linda.tuplespace('write').size, 1
          done()

      it 'should have "expire" option', (done) ->
        @timeout(5000)
        linda = create_client()
        ts = linda.tuplespace('write_expire')
        server_ts = server.linda.tuplespace('write_expire')

        ts.write {foo: "bar"}, {expire: 2}
        ts.read {foo: "bar"}, (err, tuple) ->
          assert.deepEqual tuple.data, {foo: "bar"}
          assert.equal server_ts.size, 1
          setTimeout ->
            server_ts.check_expire()
            assert.equal server_ts.size, 0
            done()
          , 3000


    describe 'method "watch"', ->

      it 'should return cancel_id', ->
        cid = create_client().tuplespace('watch_cancel').watch {}, ->
        assert.ok cid > 0

      it 'should return matched Tuple', (done) ->
        writer = create_client()
        watcher = create_client()
        val_a = Math.random()
        val_b = Math.random()

        count = 0
        watcher.tuplespace('watch').watch {sensor: "light"}, (err, tuple) ->
          count += 1
          switch count
            when 1
              assert.deepEqual tuple.data, {sensor: "light", value: val_a}
            when 2
              assert.deepEqual tuple.data, {sensor: "light", value: val_b}
              done()

        writer.tuplespace('watch').write {sensor: "foo", value: 20}
        writer.tuplespace('watch').write {sensor: "light", value: val_a}
        writer.tuplespace('watch').write {name: "shokai", age: 29}
        writer.tuplespace('watch').write {sensor: "light", value: val_b}


      it 'should not return Tuple if canceled', (done) ->
        linda = create_client()
        ts = linda.tuplespace('watch_cancel_test')
        cid = null
        async.parallel [
          (async_done) ->
            cid_ = ts.watch {a:1}, (err, tuple) ->
              assert.deepEqual tuple.data, {a:1, b:2}
              async_done(null, cid_)
          (async_done) ->
            cid = ts.watch {}, (err, tuple) ->
              assert.equal err, "cancel"
              async_done(null, cid)
        ], (err, callback_ids) ->
          assert.notEqual callback_ids[0], callback_ids[1]
          assert.equal server_ts.callbacks.length, 1
          done()

        server_ts = server.linda.tuplespace('watch_cancel_test')
        assert.equal server_ts.callbacks.length, 0
        ts.cancel cid
        ts.write {a:1, b:2}

      it 'should use same callback_id for same Tuple watch', (done) ->
        linda = create_client()
        ts = linda.tuplespace('watch_callback_id')
        server_ts = server.linda.tuplespace('watch_callback_id')
        assert.equal server_ts.callbacks.length, 0

        async.parallel [
          (async_done) ->
            cid = ts.watch {sensor: "light"}, (err, tuple) ->
              assert.deepEqual tuple.data, {sensor: "light", value: 8}
              async_done(null, cid)
          (async_done) ->
            # watch same tuple
            cid = ts.watch {sensor: "light"}, (err, tuple) ->
              assert.deepEqual tuple.data, {sensor: "light", value: 8}
              async_done(null, cid)
          (async_done) ->
            cid = ts.watch {sensor: "temperature"}, (err, tuple) ->
              assert.deepEqual tuple.data, {sensor: "temperature", value: 19}
              async_done(null, cid)
        ], (err, callback_ids) ->
          assert.equal callback_ids[0], callback_ids[1]
          assert.notEqual callback_ids[0], callback_ids[2]
          assert.equal server_ts.callbacks.length, 2
          done()

        ts.write {sensor: "light", value: 8}
        ts.write {sensor: "temperature", value: 19}


    describe 'method "read"', ->

      it 'should return cancel_id', ->
        cid = create_client().tuplespace('read_cancel').read {}, ->
        assert.ok cid > 0

      it 'should return matched Tuple', (done) ->
        reader = create_client()
        writer = create_client()

        msg = "hello world #{new Date}"
        writer.tuplespace('read').write {type: "chat", message: msg}
        async.parallel [
          (async_done) ->
            reader.tuplespace('read').read {type: "chat"}, (err, tuple) ->
              assert.deepEqual tuple.data, {type: "chat", message: msg}
              async_done()
          (async_done) ->
            reader.tuplespace('read').read {type: "foobar"}, (err, tuple) ->
              assert.ok false
              async_done()
            setTimeout ->
              assert.ok true
              async_done()
            , 500
        ], (err, results) ->
          assert.equal server.linda.tuplespace('read').size, 1
          assert.equal server.linda.tuplespace('read').callbacks.length, 1
          done()

      it 'should return matched Tuple if using queue/stack mode', (done) ->
        reader = create_client()
        writer = create_client()
        msg = "hello world #{new Date}"
        count = 4
        for i in [1..count]
          writer.tuplespace('read_queue').write {type: 'chat', num: i}
        reader.tuplespace('read_queue').option({sort: 'queue'})
        .read {type: 'chat'}, (err, tuple) ->
          assert.equal tuple.data.num, 1
          assert.equal server.linda.tuplespace('read_queue').size, count
          reader.tuplespace('read_queue').read {type: 'chat'}, (err, tuple) ->
            assert.equal tuple.data.num, 4
            assert.equal server.linda.tuplespace('read_queue').size, count
            done()


      it 'should wait if Tuple not found', (done) ->
        reader = create_client()
        writer = create_client()

        msg = "hello world #{new Date}"
        reader.tuplespace('read_callback').read {type: "chat"}, (err, tuple) ->
          assert.deepEqual tuple.data, {type: "chat", message: msg}
          done()

        writer.tuplespace('read_callback').write {type: "chat", message: msg}

      it 'should not return Tuple if canceled', (done) ->
        linda = create_client()
        ts = linda.tuplespace('read_cancel_test')
        cid = null
        async.parallel [
          (async_done) ->
            cid_ = ts.read {a:1}, (err, tuple) ->
              assert.deepEqual tuple.data, {a:1, b:2}
              async_done(null, cid_)
          (async_done) ->
            cid = ts.read {}, (err, tuple) ->
              assert.equal err, "cancel"
              async_done(null, cid)
        ], (err, callback_ids) ->
          assert.notEqual callback_ids[0], callback_ids[1]
          assert.equal server_ts.callbacks.length, 0
          done()

        server_ts = server.linda.tuplespace('read_cancel_test')
        assert.equal server_ts.callbacks.length, 0
        ts.cancel cid
        ts.write {a:1, b:2}


    describe 'method "take"', ->

      it 'should return cancel_id', ->
        cid = create_client().tuplespace('take_cancel').take {}, ->
        assert.ok cid > 0

      it 'should return matched Tuple and delete', (done) ->
        taker = create_client()
        writer = create_client()

        msg = "hello world #{new Date}"
        writer.tuplespace('take').write {type: "chat", message: msg}
        async.parallel [
          (async_done) ->
            taker.tuplespace('take').take {type: "chat"}, (err, tuple) ->
              assert.deepEqual tuple.data, {type: "chat", message: msg}
              async_done()
          (async_done) ->
            taker.tuplespace('take').take {type: "foobar"}, (err, tuple) ->
              assert.ok false
              async_done()
            setTimeout ->
              assert.ok true
              async_done()
            , 500
        ], (err, results) ->
          assert.equal server.linda.tuplespace('take').size, 0
          assert.equal server.linda.tuplespace('take').callbacks.length, 1
          done()

      it 'should return matched Tuple if using queue/stack mode', (done) ->
        taker = create_client()
        writer = create_client()
        msg = "hello world #{new Date}"
        count = 4
        for i in [1..count]
          writer.tuplespace('take_queue').write {type: 'chat', num: i}
        taker.tuplespace('take_queue').option({sort: 'queue'})
        .take {type: 'chat'}, (err, tuple) ->
          assert.equal tuple.data.num, 1
          assert.equal server.linda.tuplespace('take_queue').size, count-1
          taker.tuplespace('take_queue').take {type: 'chat'}, (err, tuple) ->
            assert.equal tuple.data.num, 4
            assert.equal server.linda.tuplespace('take_queue').size, count-2
            done()


      it 'should wait if Tuple not found', (done) ->
        taker = create_client()
        writer = create_client()

        msg = "hello world #{new Date}"
        taker.tuplespace('take_callback').read {type: "chat"}, (err, tuple) ->
          assert.deepEqual tuple.data, {type: "chat", message: msg}
          done()

        writer.tuplespace('take_callback').write {type: "chat", message: msg}

      it 'should not return Tuple if canceled', (done) ->
        linda = create_client()
        ts = linda.tuplespace('take_cancel_test')
        cid = null
        async.parallel [
          (async_done) ->
            cid_ = ts.take {a:1}, (err, tuple) ->
              assert.deepEqual tuple.data, {a:1, b:2}
              async_done(null, cid_)
          (async_done) ->
            cid = ts.take {}, (err, tuple) ->
              assert.equal err, "cancel"
              async_done(null, cid)
        ], (err, callback_ids) ->
          assert.notEqual callback_ids[0], callback_ids[1]
          assert.equal server_ts.callbacks.length, 0
          done()

        server_ts = server.linda.tuplespace('take_cancel_test')
        assert.equal server_ts.callbacks.length, 0
        ts.cancel cid
        ts.write {a:1, b:2}

  describe 'returned Tuple', ->

#    it 'should have remote-address in "from" property', (done) ->
#      linda = create_client()
#      ts = linda.tuplespace('tuple_from')
#      async.parallel [
#        (async_done) ->
#          ts.read {a:1}, (err, tuple) ->
#            assert.ok /^\d+\.\d+\.\d+\.\d+$/.test tuple.from
#            async_done(null)
#        (async_done) ->
#          ts.watch {a:1}, (err, tuple) ->
#            assert.ok /^\d+\.\d+\.\d+\.\d+$/.test tuple.from
#            async_done(null)
#        (async_done) ->
#          ts.take {a:1}, (err, tuple) ->
#            assert.ok /^\d+\.\d+\.\d+\.\d+$/.test tuple.from
#            async_done(null)
#      ], (err) ->
#        done()
#
#      ts.write {a:1, b:2}
#
