class SocketIORemoteServiceClient

  initialize: (options = {}) ->
    @_subscriberId = 0
    @_rpcId = 0
    @_subscribers = []
    @_promises = {}

    @_initializeSocketIo options


  _initializeSocketIo: ({ioClientInstance}) ->
    new Promise (resolve) =>
      @_io_socket = ioClientInstance
      @_initializeRPCResponseListener()
      resolve()


  _initializeRPCResponseListener: ->
    @_io_socket.on 'eventric:rpcResponse', (response) =>
      setTimeout =>
        @_handleRpcResponse response


  rpc: (payload) ->
    new Promise (resolve, reject) =>
      rpcId = @_getNextRpcId()
      payload.rpcId = rpcId
      @_promises[rpcId] =
        resolve: resolve
        reject: reject
      @_io_socket.emit 'eventric:rpcRequest', payload


  _getNextRpcId: ->
    @_rpcId++


  _handleRpcResponse: (response) ->
    if not response.rpcId?
      throw new Error 'Missing rpcId in RPC Response'
    if response.rpcId not of @_promises
      throw new Error "No promise registered for id #{response.rpcId}"
    if response.error
      if response.error.constructor isnt Error
        response.error = @_convertObjectToError response.error
      @_promises[response.rpcId].reject response.error
    else
      @_promises[response.rpcId].resolve response.data
    delete @_promises[response.rpcId]


  _convertObjectToError: (object) ->
    error = new Error object.message
    Object.keys(object).forEach (key) ->
      error[key] = object[key]
    return error


  subscribe: (context, [domainEventName, aggregateId]..., subscriberFn) ->
    new Promise (resolve, reject) =>
      fullEventName = @_getFullEventName context, domainEventName, aggregateId
      subscriber =
        eventName: fullEventName
        subscriberFn: subscriberFn
        subscriberId: @_getNextSubscriberId()
      @_io_socket.emit 'eventric:joinRoom', fullEventName
      @_io_socket.on fullEventName, subscriberFn
      @_subscribers.push subscriber
      resolve subscriber.subscriberId


  unsubscribe: (subscriberId) ->
    new Promise (resolve, reject) =>
      matchingSubscriber = @_subscribers.filter((subscriber) ->
        subscriber.subscriberId is subscriberId
      )[0]
      @_subscribers = @_subscribers.filter (subscriber) -> subscriber isnt matchingSubscriber
      @_io_socket.removeListener matchingSubscriber.eventName, matchingSubscriber.subscriberFn
      othersHaveSubscribedToThisEvent = @_subscribers.some (subscriber) ->
        subscriber.eventName is matchingSubscriber.eventName
      if not othersHaveSubscribedToThisEvent
        @_io_socket.emit 'eventric:leaveRoom', matchingSubscriber.eventName
      resolve()


  _getNextSubscriberId: ->
    @_subscriberId++


  _getFullEventName: (context, domainEventName, aggregateId) ->
    fullEventName = context
    if domainEventName
      fullEventName += "/#{domainEventName}"
    if aggregateId
      fullEventName += "/#{aggregateId}"
    fullEventName


  disconnect: ->
    @_io_socket.disconnect()


module.exports = new SocketIORemoteServiceClient
