#= require ./connection_monitor

# Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation.

{message_types, protocols} = ActionCable.INTERNAL

supportedProtocols…, unsupportedProtocol

protocols

class ActionCable.Connection

@reopenDelay: 500

constructor: (@consumer) ->
  {@subscriptions} = @consumer
  @monitor = new ActionCable.ConnectionMonitor this
  @disconnected = true

send: (data) ->
  if @isOpen()
    @webSocket.send(JSON.stringify(data))
    true
  else
    false

open: =>
  if @isActive()
    ActionCable.log("Attempted to open WebSocket, but existing socket is #{@getState()}")
    false
  else
    ActionCable.log("Opening WebSocket, current state is #{@getState()}, subprotocols: #{protocols}")
    @uninstallEventHandlers() if @webSocket?
    @webSocket = new ActionCable.WebSocket(@consumer.url, protocols)
    @installEventHandlers()
    @monitor.start()
    true

close: ({allowReconnect} = {allowReconnect: true}) ->
  @monitor.stop() unless allowReconnect
  @webSocket?.close() if @isActive()

reopen: ->
  ActionCable.log("Reopening WebSocket, current state is #{@getState()}")
  if @isActive()
    try
      @close()
    catch error
      ActionCable.log("Failed to reopen WebSocket", error)
    finally
      ActionCable.log("Reopening WebSocket in #{@constructor.reopenDelay}ms")
      setTimeout(@open, @constructor.reopenDelay)
  else
    @open()

getProtocol: ->
  @webSocket?.protocol

isOpen: ->
  @isState("open")

isActive: ->
  @isState("open", "connecting")

# Private

isProtocolSupported: ->
  @getProtocol() in supportedProtocols

isState: (states...) ->
  @getState() in states

getState: ->
  return state.toLowerCase() for state, value of WebSocket when value is @webSocket?.readyState
  null

installEventHandlers: ->
  for eventName of @events
    handler = @events[eventName].bind(this)
    @webSocket["on#{eventName}"] = handler
  return

uninstallEventHandlers: ->
  for eventName of @events
    @webSocket["on#{eventName}"] = ->
  return

events:
  message: (event) ->
    return unless @isProtocolSupported()
    {identifier, message, type} = JSON.parse(event.data)
    switch type
      when message_types.welcome
        @monitor.recordConnect()
        @subscriptions.reload()
      when message_types.ping
        @monitor.recordPing()
      when message_types.confirmation
        @subscriptions.notify(identifier, "connected")
      when message_types.rejection
        @subscriptions.reject(identifier)
      else
        @subscriptions.notify(identifier, "received", message)

  open: ->
    ActionCable.log("WebSocket onopen event, using '#{@getProtocol()}' subprotocol")
    @disconnected = false
    if not @isProtocolSupported()
      ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.")
      @close(allowReconnect: false)

  close: (event) ->
    ActionCable.log("WebSocket onclose event")
    return if @disconnected
    @disconnected = true
    @monitor.recordDisconnect()
    @subscriptions.notifyAll("disconnected", {willAttemptReconnect: @monitor.isRunning()})

  error: ->
    ActionCable.log("WebSocket onerror event")