# most of this code is based on github.com/livereload/livereload-js/blob/master/src/connector.coffee # class @Connector

MIN_DELAY = 1000
MAX_DELAY = 60 * 1000
HANDSHAKE_TIMEOUT = 5000

constructor: (url, @WebSocket, @console, @Timer, @_handler) ->
    uri = URI(url)
    newScheme =
        switch uri.protocol()
            when 'http'
                'ws'
            when 'https'
                'wss'
    uri.protocol(newScheme)
    @_uri = uri.toString()

    @_nextDelay = MIN_DELAY
    @_connectionDesired = no

    @protocolParser = new ProtocolParser
        connected: (hello_message) =>
            @_handshakeTimeout.stop()
            @_nextDelay = MIN_DELAY
            @_disconnectionReason = 'broken'
            @_callHandler('connected', hello_message)

        error: (e) =>
            @_callHandler('error', e)
            @_closeOnError()

        message: (message) =>
            if message.name == 'heartbeat'
                # TODO: reset timeout timer
            else
                @_callHandler('message', message)

    @_handshakeTimeout = new Timer =>
        return unless @_isSocketConnected()
        @console.error('Connector: Handshake timed out')
        @_disconnectionReason = 'handshake-timeout'
        @socket.close()

    @_reconnectTimer = new Timer =>
        return unless @_connectionDesired  # shouldn't hit this, but just in case
        @connect()

    @connect()

_isSocketConnected: ->
    @socket and @socket.readyState is @WebSocket.OPEN

connect: ->
    @_connectionDesired = yes
    return if @_isSocketConnected()

    @protocolParser.reset()

    # prepare for a new connection
    @_reconnectTimer.stop()
    @_disconnectionReason = 'cannot-connect'

    @_callHandler('connecting')

    @socket = new @WebSocket(@_uri)
    @socket.onopen    = (e) => @_onopen(e)
    @socket.onclose   = (e) => @_onclose(e)
    @socket.onmessage = (e) => @_onmessage(e)
    @socket.onerror   = (e) => @_onerror(e)

disconnect: (reason = 'manual')->
    @_connectionDesired = no
    @_reconnectTimer.stop()   # in case it was running
    return unless @_isSocketConnected()
    @_disconnectionReason = reason
    @socket.close()

_scheduleReconnection: ->
    return unless @_connectionDesired  # don't reconnect after manual disconnection
    unless @_reconnectTimer.running
        @_reconnectTimer.start(@_nextDelay)
        @_nextDelay = Math.min(MAX_DELAY, @_nextDelay * 2)

_callHandler: (message, arg...) ->
    func = @_handler[message]
    if func?
        func(arg...)
    else
        @console.error("Connector: error: handler doesn't know #{message}")

sendMessage: (name, data) ->
    message =
        name: name

    message.data = data if data?

    @socket.send(JSON.stringify(message))

_closeOnError: ->
    @_handshakeTimeout.stop()
    @_disconnectionReason = 'error'
    @socket.close()

_onopen: (e) ->
    @_callHandler('socketConnected')
    @_disconnectionReason = 'handshake-failed'

    # start handshake
    @sendMessage('hello')
    @_handshakeTimeout.start(HANDSHAKE_TIMEOUT)

_onclose: (e) ->
    @_callHandler('disconnected', @_disconnectionReason, @_nextDelay)
    @_scheduleReconnection()

_onerror: (e) ->
    @_callHandler('error', e)
    @console.error("Connector: #{e}")

_onmessage: (e) ->
    @protocolParser.process(e.data)