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

**
** Actor is a worker who processes messages asynchronously.
**
** See [docLang::Actors]`docLang::Actors` and
** [examples]`examples::concurrent-actors`.
**
@Js
native const class Actor
{

//////////////////////////////////////////////////////////////////////////
// Construction
//////////////////////////////////////////////////////////////////////////

  **
  ** Create an actor whose execution is controlled by the given ActorPool.
  ** If receive is non-null, then it is used to process messages sent to
  ** this actor.  If receive is specified then it must be an immutable
  ** function (it cannot capture state from the calling thread), otherwise
  ** NotImmutableErr is thrown.  If receive is null, then you must subclass
  ** Actor and override the `receive` method.
  **
  new make(ActorPool pool, |Obj? -> Obj?|? receive := null)

  **
  ** Create an actor with a coalescing message loop.  This constructor
  ** follows the same semantics as `make`, but has the ability to coalesce
  ** the messages pending in the thread's message queue.  Coalesced
  ** messages are merged into a single pending message with a shared
  ** Future.
  **
  ** The 'toKey' function is used to derive a key for each message,
  ** or if null then the message itself is used as the key.  If the 'toKey'
  ** function returns null, then the message is not considered for coalescing.
  ** Internally messages are indexed by key for efficient coalescing.
  **
  ** If an incoming message has the same key as a pending message
  ** in the queue, then the 'coalesce' function is called to coalesce
  ** the messages into a new merged message.  If 'coalesce' function itself
  ** is null, then we use the incoming message (last one wins).  The coalesced
  ** message occupies the same position in the queue as the original
  ** and reuses the original message's Future instance.
  **
  ** Both the 'toKey' and 'coalesce' functions are called while holding
  ** an internal lock on the queue.  So the functions must be efficient
  ** and never attempt to interact with other actors.
  **
  new makeCoalescing(ActorPool pool,
                     |Obj? msg -> Obj?|? toKey,
                     |Obj? orig, Obj? incoming -> Obj?|? coalesce,
                     |Obj? -> Obj? |? receive := null)

//////////////////////////////////////////////////////////////////////////
// Messaging
//////////////////////////////////////////////////////////////////////////

  **
  ** The pool used to control execution of this actor.
  **
  ActorPool pool()

  **
  ** Asynchronously send a message to this actor for processing.
  ** If msg is not immutable, then NotImmutableErr is thrown.
  ** Throw Err if this actor's pool has been stopped.  Return
  ** a future which may be used to obtain the result once it the
  ** actor has processed the message.  If the message is coalesced
  ** then this method returns the original message's future reference.
  ** Also see `sendLater` and `sendWhenComplete`.
  **
  Future send(Obj? msg)

  **
  ** Schedule a message for delivery after the specified period of
  ** duration has elapsed.  Once the period has elapsed the message is
  ** appended to the end of this actor's queue.  Accuracy of scheduling
  ** is dependent on thread coordination and pending messages in the queue.
  ** Scheduled messages are not guaranteed to be processed if the
  ** actor's pool is stopped.  Scheduled messages are never coalesced.
  ** Also see `send` and `sendWhenComplete`.
  **
  Future sendLater(Duration d, Obj? msg)

  **
  ** Schedule a message for delivery after the given future has completed.
  ** Completion may be due to the future returning a result, throwing an
  ** exception, or cancellation.  Send-when-complete messages are never
  ** coalesced.  The given future must be an actor based future.  Also
  ** see `send` and `sendLater`.
  **
  Future sendWhenComplete(Future f, Obj? msg)

  ** Obsolete - use `sendWhenComplete`
  @NoDoc @Deprecated { msg = "Use sendWhenComplete" }
  Future sendWhenDone(Future f, Obj? msg)

  **
  ** The receive behavior for this actor is handled by overriding
  ** this method or by passing a function to the constructor.  Return
  ** the result made available by the Future.  If an exception
  ** is raised by this method, then it is raised by 'Future.get'.
  **
  protected virtual Obj? receive(Obj? msg)

//////////////////////////////////////////////////////////////////////////
// Diagnostics
//////////////////////////////////////////////////////////////////////////

  // NOTE: these methods are marked as NoDoc, they are provided for
  // low level access to monitor the actor, but they are subject to change.

  **
  ** Return debug string for the current state of this actor:
  **   - idle: no messages queued
  **   - running: currently assigned a thread and processing messages
  **   - pending: messages are queued and waiting for thread
  **
  @NoDoc Str threadState()

  **
  ** Return if queueSize is equal to or greater the pool's max queue size.
  **
  @NoDoc Bool isQueueFull()

  **
  ** Get the current number of messages pending on the message queue.
  **
  @NoDoc Int queueSize()

  **
  ** Get the peak number of messages queued.
  **
  @NoDoc Int queuePeak()

  **
  ** Get the total number of messages processed by receive method.
  **
  @NoDoc Int receiveCount()

  **
  ** Get the total number of nanosecond ticks spent in the receive
  ** method processing messages.  Note that this value might lag until
  ** the actor yields it thread.
  **
  @NoDoc Int receiveTicks()

//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////

  **
  ** Put the currently executing actor thread to sleep for the
  ** specified period.  If the thread is interrupted for any
  ** reason while sleeping, then InterruptedErr is thrown.
  **
  static Void sleep(Duration duration)

  **
  ** Return the map of "global" variables visibile only to the current
  ** actor (similar to how thread locals work in Java).  These variables
  ** are keyed by a string name - by convention use a dotted notation
  ** beginning with your pod name to avoid naming collisions.
  **
  static Str:Obj? locals()

}