dnode    = require 'dnode'
http     = require 'http'
shoe     = require 'shoe'
spawn    = require 'win-spawn'

# the list of phantomjs RPC wrapper
phanta = []

# @Description: starts and returns a child process running phantomjs
# @param: port:int
# @args: args:object
# @return: ps:object
startPhantomProcess = (binary, port, hostname, args) ->
  spawn binary, args.concat([
    __dirname + '/shim.js'
    port
    hostname
  ])

# @Description: kills off all phantom processes within spawned by this parent process when it is exits

cleanUp = ->
  phantom.exit() for phantom in phanta

onSignalClean = (signal) ->
  return ->
    if process.listeners(signal).length == 1
      process.exit(0)

process.on('exit', cleanUp)
process.on(signal, onSignalClean(signal)) for signal in ['SIGINT', 'SIGTERM']

# @Description: We need this because dnode does magic clever stuff with functions, but we want the function to make it intact to phantom
wrap = (ph) ->
  ph.callback = (fn) ->
    return '__phantomCallback__'+fn.toString()
  ph._createPage = ph.createPage
  ph.createPage = (cb) ->
    ph._createPage (page) ->
      page._evaluate = page.evaluate
      page.evaluate = (fn, cb, args...) -> page._evaluate.apply(page, [fn.toString(), cb].concat(args))
      page._onResourceRequested = page.onResourceRequested
      # can apply extra args which will be passed to phantomjs onResourceRequested scope
      page.onResourceRequested = (fn, cb, args...) -> page._onResourceRequested.apply(page, [fn.toString(), cb].concat(args))

      page._onResourceReceived = page.onResourceReceived
      page.onResourceReceived = (fn, cb, args...) -> page._onResourceReceived.apply(page, [fn.toString(), cb].concat(args))

      page._onResourceTimeout = page.onResourceTimeout
      page.onResourceTimeout = (fn, cb, args...) -> page._onResourceTimeout.apply(page, [fn.toString(), cb].concat(args))

      page._onResourceError = page.onResourceError
      page.onResourceError = (fn, cb, args...) -> page._onResourceError.apply(page, [fn.toString(), cb].concat(args))

      page._onConosleMessage = page.onConosleMessage
      page.onConosleMessage = (cb) -> page._onConosleMessage.apply(page, [cb])

      page._onError = page.onError
      page.onError = (cb) -> page._onError.apply(page, [cb])

      page._onCallback = page.onCallback
      page.onCallback = (cb) -> page._onCallback.apply(page, [cb])

      page._onLoadFinished = page.onLoadFinished
      page.onLoadFinished = (fn, cb, args...) -> page._onLoadFinished.apply(page, [fn.toString(), cb].concat(args))

      cb page

module.exports =
  create: ->
    args = []
    options = {}
    for arg in arguments
      switch typeof arg
        when 'function' then cb = arg
        when 'string' then args.push arg
        when 'object' then options = arg
    if typeof options.parameters is 'object'
      for key, value of options.parameters
        args.push '--'+key+'='+value
    options.path ?= ''
    options.binary ?= options.path+'phantomjs'
    options.port ?= 0
    options.hostname ?= 'localhost'
    options.dnodeOpts ?= {}

    ps = null;
    phantom = null

    httpServer = http.createServer()
    httpServer.listen options.port, options.hostname

    httpServer.on "error", (err) ->
        if cb?
            cb null, err
        else
            throw err

    httpServer.on 'listening', () ->
      port = httpServer.address().port
      hostname = httpServer.address().address
      ps = startPhantomProcess options.binary, port, hostname, args

      ps.stdout.on 'data', options.onStdout || (data) -> console.log "phantom stdout: #{data}"

      ps.stderr.on 'data', options.onStderr || (data) -> module.exports.stderrHandler(data.toString('utf8'))

      ps.on 'error', (err) ->
        httpServer.close()
        if err?.code is 'ENOENT'
          console.error "phantomjs-node: You don't have 'phantomjs' installed"
        if cb?
            cb null, err
        else
            throw err

      # @Description: when the background phantomjs child process exits or crashes
      #   removes the current dNode phantomjs RPC wrapper from the list of phantomjs RPC wrapper
      ps.on 'exit', (code, signal) ->
        httpServer.close()
        if phantom
          phantom.onExit?()
          phanta = (p for p in phanta when p isnt phantom)

        if options.onExit
          options.onExit code, signal
        else
          console.assert not signal?, "signal killed phantomjs: #{signal}"
          if code != 0
            process.exit code

    sock = shoe (stream) ->

      d = dnode({}, options.dnodeOpts)

      d.on 'remote', (_phantom) ->
        phantom = _phantom
        wrap phantom
        phantom.process = ps
        phanta.push phantom
        cb? phantom, null

      d.pipe stream
      stream.pipe d

    sock.install httpServer, '/dnode'

  stderrHandler: (message) ->
    return if message.match /(No such method.*socketSentData)|(CoreText performance note)/ #Stupid, stupid QTWebKit
    console.warn "phantom stderr: #{message}"
