//
// Copyright (c) 2013, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   2 Mar 2013  Andy Frank  Creation
//

**
** AsyncTask models an asynchronous operation's progress and provides
** a list of callback handlers when the task completes (either successfully
** or fails).
**
@Js
class AsyncTask
{

//////////////////////////////////////////////////////////////////////////
// Callbacks
//////////////////////////////////////////////////////////////////////////

  ** Add a callback to be invoked when this task completes successfully.
  This onDone(|Obj?| doneCallback)
  {
    if (isDone)
    {
      // invoke immediately
      doneCallback(arg)
    }
    else
    {
      // queue callback
      (parent?._onDone ?: _onDone).add(doneCallback)
    }
    return this
  }

  ** Add a callback to be invoked when this task fails.
  This onErr(|Obj?| errCallback)
  {
    if (isErr)
    {
      // invoke immediately
      errCallback(arg)
    }
    else
    {
      // queue callback
      (parent?._onErr ?: _onErr).add(errCallback)
    }
    return this
  }

  ** Add a callback that will always be invoked, regardless
  ** if task completes successfully or fails.
  This onDoneOrErr(|Obj?| doneOrErrCallback)
  {
    if (!isPending)
    {
      // invoke immediately
      doneOrErrCallback(arg)
    }
    else
    {
      // queue callback
      (parent?._onDoneOrErr ?: _onDoneOrErr).add(doneOrErrCallback)
    }
    return this
  }

  ** Convenience for `onDone` and `onErr` together.
  This then(|Obj?| doneCallback, |Obj?|? errCallback := null)
  {
    onDone(doneCallback)
    if (errCallback != null) onErr(errCallback)
    return this
  }

//////////////////////////////////////////////////////////////////////////
// State
//////////////////////////////////////////////////////////////////////////

  ** True until `markDone` or `markErr` invoked.
  Bool isPending() { parent==null ? (!isDone && !isErr) : parent.isPending }

  ** True if `markDone` has been invoked.
  Bool isDone() { parent==null ? state === "done" : parent.isDone }

  ** True if `markErr` has been invoked.
  Bool isErr() { parent==null ? state === "err" : parent.isErr }

//////////////////////////////////////////////////////////////////////////
// Actions
//////////////////////////////////////////////////////////////////////////

  ** Mark this Task as 'done' and invoke all its `onDone`
  ** callbacks with given argment.
  Void markDone(Obj? obj := null)
  {
    if (isRO) throw Err("Task is readonly")
    if (!isPending) throw Err("Task already completed as '$state'")
    state = "done"
    arg   = obj
    _onDone.each |f| { f(obj) }
    _onDoneOrErr.each |f| { f(obj) }
  }

  ** Mark this Task as 'erred' and invoke all its `onErr`
  ** callbacks with given argment.
  Void markErr(Obj? obj := null)
  {
    if (isRO) throw Err("Task is readonly")
    if (!isPending) throw Err("Task already completed as '$state'")
    state = "err"
    arg   = obj
    _onErr.each |f| { f(obj) }
    _onDoneOrErr.each |f| { f(obj) }
  }

//////////////////////////////////////////////////////////////////////////
// RO
//////////////////////////////////////////////////////////////////////////

  ** Is this instance read-only.  Read-only tasks may not
  ** be marked as done or err.
  Bool isRO() { parent != null }

  ** Return a new read-only wrapper for this task.  Read-only
  ** tasks may not be marked as done or err.
  AsyncTask ro() { parent == null ? AsyncTask { it.parent=this } : this }

//////////////////////////////////////////////////////////////////////////
// Private
//////////////////////////////////////////////////////////////////////////

  private AsyncTask? parent   // parent task for ro wrapper

  private Str state := "pending"    // pending, done, err
  private Obj? arg := null          // cached done/err arg

  private Func[] _onDone      := [,]
  private Func[] _onErr       := [,]
  private Func[] _onDoneOrErr := [,]
}