SocketIoClient = require('socket.io-client')
SocketIoStream = require('socket.io-stream')
OS = require('os')


Base = require('../base')
Caller = require('./caller')
ClientInfo = require('./client_info')
Connector = require('./connector')
Crypto = require('./crypto')
HttpProxy = require('./http_proxy')
Logger = require('./logger')
PolyglotRelay = require('./polyglot_relay')
Publisher = require('./publisher')


EVENTS = [ 'connect', 'connect_error', 'connect_timeout', 'reconnect', 'reconnect_error', 'reconnect_failed', 'error', 'disconnect', 'open', 'close', 'reconnect_attempt' ]

debug = Base.logger('connector')

isHttpProxy = false
isRelay = false

# Connection state variables.
socket = null
proxyData = null
isHttpProxySetup = false


exports.connectToProxy = (data) ->
  debug('connectToProxy', data)
  # Reset connection state.
  socket = null
  proxyData = null
  isHttpProxySetup = false
  Logger.loadFilter ->
    proxyData = data
    proxyData.proxyUrl = "https://"+Base.env.IX_PROXY_HOST_OVERRIDE if Base.env.IX_PROXY_HOST_OVERRIDE
    console.log('Connecting to '+proxyData.proxyUrl+'...')
    socket = SocketIoClient(proxyData.proxyUrl, { reconnection: false, multiplex: false, rejectUnauthorized: Base.env.IX_PROXY_HOST_ALLOW_UNVERIFIED != 'TRUE' }) # rejectUnauthorized: https://www.cigital.com/blog/node-js-socket-io/
    setDebugHandlers(socket, EVENTS)
    setDebugHandlers(socket.io, EVENTS, 'manager:')
    socket.on('connect', onConnect)
    socket.on 'connect_error', (err) ->
      console.log('Connect error: '+err)
      reconnect()
    socket.on 'disconnect', (cause) ->
      console.log('Disconnected from '+proxyData.proxyUrl+': '+cause)
      reconnect()
    unless isHttpProxy
      socket.on('ix-call', onIxCall)
      socket.on('ix-ping', onIxPing)
      socket.on('ix-x', onIxX)

exports.connectRelayToProxy = (data) ->
  debug('connectRelayToProxy', data)
  isRelay = true
  exports.connectToProxy(data)

exports.connectHttpToProxy = (upstream, data) ->
  debug('connectHttpToProxy', upstream, data)
  isHttpProxy = true
  HttpProxy.setupHttpProxy(upstream)
  exports.connectToProxy(data)


onConnect = ->
  debug('connect')
  ClientInfo.clientInfo()
  .then (clientInfo) ->
    debug('clientInfo', clientInfo)
    socket.emit 'set-client',
      apiConfig: Base.apiConfig()
      proxyUrl: proxyData.proxyUrl
      proxyExport: proxyData.proxyExport
      device: { key: clientInfo.deviceKey, secret: clientInfo.deviceSecret } # TODO: update IX to use deviceKey, deviceSecret instead of device.
      deviceKey: clientInfo.deviceKey
      deviceSecret: clientInfo.deviceSecret
      hostname: OS.hostname()
      pid: process.pid
      processKey: getProcessKey()
      clientInfo: clientInfo
    , (err, connectData) ->
      debug('connect result', err, connectData)
      if err
        console.log('Connect error: Unable to complete connection to '+proxyData.proxyUrl+': '+JSON.stringify(err))
        throw new Error(err)
      else
        if isHttpProxy
          SocketIoStream.forceBase64 = true unless connectData.enableBinaryStream
          debug('SocketIoStream.forceBase64', SocketIoStream.forceBase64)
          unless isHttpProxySetup
            isHttpProxySetup = true
            streamSocket = SocketIoStream(socket)
            setDebugHandlers(streamSocket, EVENTS, 'stream:')
            streamSocket.on('http-proxy', HttpProxy.onHttpProxy)
        console.log('Connected to '+proxyData.proxyUrl+'.')

reconnect = ->
  setTimeout ->
    Publisher.getProxyForReconnect(isHttpProxy)
    .then (data) ->
      Connector.connectToProxy(data)
    , (e) ->
      console.error('ERROR WHILE RECONNECTING', e)
      reconnect()
  , 1000 # + Math.random()*2000 # Float timers broken in 0.10.30: https://github.com/nodejs/node-v0.x-archive/pull/8073


onIxCall = (data, responder) ->
  debug('ix-call', arguments)
  try
    Logger.logCallRequest(data)
    responder = Logger.wrapResponder(data, debug.wrap(responder))
  catch e
  try
    if isRelay
      Caller.ixCall(data, responder, PolyglotRelay.invoker)
    else
      Caller.ixCallNodejs(data, responder)
  catch e
    responder(e)

onIxPing = (replyData, responder) ->
  debug('ix-ping', replyData)
  responder(null, replyData)

onIxX = (cipherParams, responder) ->
  debug('ix-x', cipherParams)
  Crypto.decrypt cipherParams, (err, data) ->
    if err
      responder.call(err); return
    onIxCall(data, Crypto.encryptingResponder(responder))


PROCESS_KEY = null
getProcessKey = ->
  PROCESS_KEY ?= (new Date()).getTime()+'-'+Math.floor((Math.random()*1000000))
  PROCESS_KEY

setDebugHandlers = (socket, events, prefix) ->
  prefix = prefix || ''
  for event in events
    socket.on(event, debugHandlerFor(prefix+event))

debugHandlerFor = (type) ->
  (args...) ->
    args.unshift('event:'+type)
    debug.apply(this, args)
