import cake            from '../cake'
import invokeAsync     from './async'
import invokeGenerator from './generator'
import invokeSync      from './sync'
import log             from '../log'
import serial          from './serial'
import tasks           from '../tasks'
import {isFunction, isGeneratorFn} from '../utils'

invoked = {}

# Invoke delegates to one of the above
invoke = (name, opts, cb) ->
  log.debug 'invoke'

  # Prevent recursive calls
  return if invoked[name]
  invoked[name] = true

  # Calling cake's invoke ensures that options are passed to us correctly as
  # well as ensuring the normal missing task error is shown.
  cake.invoke name

  # Grab task action, any deps and parsed options
  {action, deps, options} = tasks[name]

  # Extend caller provided parsed options
  opts = Object.assign options, opts

  done = (err) ->
    invoked = {}
    (cb err) if isFunction cb

  invokeAction = (err) ->
    return done err if err?

    # Is a generator task
    if isGeneratorFn action
      return invokeGenerator name, action, opts, done

    # Two arguments, action expects callback
    if action.length == 2
      return invokeAsync name, action, opts, done

    # Single argument, detected callback
    if /^function \((callback|cb|done|next)\)/.test action
      return invokeAsync name, action, null, done

    # 0 or 1 argument action, no callback detected
    invokeSync name, action, opts, done

  # Process deps first if any
  return serial deps, opts, invokeAction

  invokeAction()

export default invoke
