
{EventEmitter} = require 'events'
{spawn} = require 'child_process'
LineStream = require 'linestream'
_ = require 'underscore'
async = require 'async'
{encodeLine, decodeLine} = require './appborg_util'


class ABSubprocessWrapper extends EventEmitter
  constructor: ({@router, @name, @debug, spawnArgs}) ->
    super()
    @p = @subproc = spawn spawnArgs...
    @stderr = @p.stderr
    ls = new LineStream @p.stdout
    ls.on 'data', (line) =>
      decoded = null
      try
        decoded = decodeLine line
      catch e
        @killDashNine()
        @router.componentError @, "Error decoding line #{JSON.stringify line} from #{JSON.stringify @name}"
      if decoded
        @emit 'event', decoded
    @p.on 'exit', (args...) =>
      @emit 'exit', args...
  
  processEvent: (event) ->
    data = encodeLine event
    @p.stdin.write data
    if @debug
      @router.debugArr.push """
        ---- [ABRouter] to subproc stdin ----
        #{JSON.stringify decodeLine data}
        #{require('hexy').hexy data}
        ---------------------------------
      """
  
  killDashNine: (callback=(->)) ->
    @p.kill 'SIGKILL'
    @killed = true
    @p.on 'exit', callback


class ABRouter extends EventEmitter
  constructor: (opt={}) ->
    super()
    @verbose = opt.verbose or false
    @debug = opt.debug or false
    @names = if opt.name
      [opt.name]
    else if opt.names
      opt.names
    else
      ["router"]
    @counter = 0
    @wrappers = {}
    @callbacks = {}
    @debugArr = []
  
  killDashNine: (callback) ->
    f = (x, cb) ->
      if x.killDashNine
        x.killDashNine cb
      else
        cb null
    async.forEach _.values(@wrappers), f, callback
  
  getDebugText: () ->
    @debugArr.join '\n\n'
  
  spawn: (command, args, opt=null) ->
    name = opt?.name or "subprocess"
    wrapper = new ABSubprocessWrapper {
      router: @
      name: name
      spawnArgs: [command, args, opt]
      debug: @debug
    }
    @wrappers[name] = wrapper
    wrapper.on 'event', (e) => 
      @processEvent e
    
    wrapper.on 'exit', () =>
      if @verbose
        console.log "[ABRouter][#{name}] subprocess exited."
    
    wrapper.stderr.on 'data', (data) =>
      if @verbose
        console.log "[ABRouter][#{name}] STDERR: #{data.toString()}"
    
    wrapper.subproc
  
  send: (method, args...) ->
    #DRY
    dicts = []
    callback = (->)
    for x in args
      if typeof x == 'function'
        callback = x
      else
        dicts.push x
    recipient = "subprocess"
    info = {}
    if dicts.length == 1
      [info] = dicts
    else
      [opt, info] = dicts
      recipient = opt.to or recipient
    @sendTo recipient, method, info, callback
  
  sendTo: (dest, method, info, callback=->) ->
    @counter++
    @callbacks[@counter] = callback
    @processEvent {
      method: method
      info: info
      id: @counter
      from: @names[0]
      to: dest
    }
  
  processEvent: (e) ->
    if _.contains @names, e.to
      if e.method
        @emit 'event', e
      else
        @emit 'response', e
        f = @callbacks[e.id]
        if f
          f (e.error or null), (e.result or {})
    else
      if not @wrappers[e.to]
        throw new Error "Unknown recipient: #{e.to}"
      @wrappers[e.to].processEvent e
  
  componentError: (component, message) ->
    @emit 'componentError', component, message
  


module.exports =
  ABRouter: ABRouter
