## Compile-time configuration options for chronos that control the availability
## of various strictness and debuggability options. In general, debug helpers
## are enabled when `debug` is defined while strictness options are introduced
## in transition periods leading up to a breaking release that starts enforcing
## them and removes the option.
##
## `chronosPreviewV4` is a preview flag to enable v4 semantics - in particular,
## it enables strict exception checking and disables parts of the deprecated
## API and other changes being prepared for the upcoming release
##
## `chronosDebug` can be defined to enable several debugging helpers that come
## with a runtime cost - it is recommeneded to not enable these in production
## code.
##
## In this file we also declare feature flags starting with `chronosHas...` -
## these constants are declared when a feature exists in a particular release -
## each flag is declared as an integer starting at 0 during experimental
## development, 1 when feature complete and higher numbers when significant
## functionality has been added. If a feature ends up being removed (or changed
## in a backwards-incompatible way), the feature flag will be removed or renamed
## also - you can use `when declared(chronosHasXxx): when chronosHasXxx >= N:`
## to require a particular version.
const
  chronosHandleException* {.booldefine.}: bool = false
    ## Remap `Exception` to `AsyncExceptionError` for all `async` functions.
    ##
    ## This modes provides backwards compatibility when using functions with
    ## inaccurate `{.raises.}` effects such as unannotated forward declarations,
    ## methods and `proc` types - it is recommened to annotate such code
    ## explicitly as the `Exception` handling mode may introduce surprising
    ## behavior in exception handlers, should `Exception` actually be raised.
    ##
    ## The setting provides the default for the per-function-based
    ## `handleException` parameter which has precedence over this global setting.
    ##
    ## `Exception` handling may be removed in future chronos versions.

  chronosStrictFutureAccess* {.booldefine.}: bool = defined(chronosPreviewV4)

  chronosStackTrace* {.booldefine.}: bool = defined(chronosDebug)
    ## Include stack traces in futures for creation and completion points

  chronosFutureId* {.booldefine.}: bool = defined(chronosDebug)
    ## Generate a unique `id` for every future - when disabled, the address of
    ## the future will be used instead

  chronosFutureTracking* {.booldefine.}: bool = defined(chronosDebug)
    ## Keep track of all pending futures and allow iterating over them -
    ## useful for detecting hung tasks

  chronosDumpAsync* {.booldefine.}: bool = defined(nimDumpAsync)
    ## Print code generated by {.async.} transformation

  chronosProcShell* {.strdefine.}: string =
    when defined(windows):
      "cmd.exe"
    else:
      when defined(android):
        "/system/bin/sh"
      else:
        "/bin/sh"
    ## Default shell binary path.
    ##
    ## The shell is used as command for command line when process started
    ## using `AsyncProcessOption.EvalCommand` and API calls such as
    ## ``execCommand(command)`` and ``execCommandEx(command)``.

  chronosEventsCount* {.intdefine.} = 64
    ## Number of OS poll events retrieved by syscall (epoll, kqueue, poll).

  chronosInitialSize* {.intdefine.} = 64
    ## Initial size of Selector[T]'s array of file descriptors.

  chronosEventEngine* {.strdefine.}: string =
    when defined(nimdoc):
      ""
    elif defined(linux) and not(defined(android) or defined(emscripten)):
      "epoll"
    elif defined(macosx) or defined(macos) or defined(ios) or
          defined(freebsd) or defined(netbsd) or defined(openbsd) or
          defined(dragonfly):
      "kqueue"
    elif defined(android) or defined(emscripten):
      "poll"
    elif defined(posix):
      "poll"
    else:
      ""
    ## OS polling engine type which is going to be used by chronos.

  chronosHasRaises* = 0
    ## raises effect support via `async: (raises: [])`

  chronosTransportDefaultBufferSize* {.intdefine.} = 16384
    ## Default size of chronos transport internal buffer.

  chronosStreamDefaultBufferSize* {.intdefine.} = 16384
    ## Default size of chronos async stream internal buffer.

  chronosTLSSessionCacheBufferSize* {.intdefine.} = 4096
    ## Default size of chronos TLS Session cache's internal buffer.

when defined(chronosStrictException):
  {.warning: "-d:chronosStrictException has been deprecated in favor of handleException".}
  # In chronos v3, this setting was used as the opposite of
  # `chronosHandleException` - the setting is deprecated to encourage
  # migration to the new mode.

when defined(debug) or defined(chronosConfig):
  import std/macros

  static:
    hint("Chronos configuration:")
    template printOption(name: string, value: untyped) =
      hint(name & ": " & $value)
    printOption("chronosHandleException", chronosHandleException)
    printOption("chronosStackTrace", chronosStackTrace)
    printOption("chronosFutureId", chronosFutureId)
    printOption("chronosFutureTracking", chronosFutureTracking)
    printOption("chronosDumpAsync", chronosDumpAsync)
    printOption("chronosProcShell", chronosProcShell)
    printOption("chronosEventEngine", chronosEventEngine)
    printOption("chronosEventsCount", chronosEventsCount)
    printOption("chronosInitialSize", chronosInitialSize)
    printOption("chronosTransportDefaultBufferSize",
      chronosTransportDefaultBufferSize)
    printOption("chronosStreamDefaultBufferSize",
      chronosStreamDefaultBufferSize)
    printOption("chronosTLSSessionCacheBufferSize",
      chronosTLSSessionCacheBufferSize)

# In nim 1.6, `sink` + local variable + `move` generates the best code for
# moving a proc parameter into a closure - this only works for closure
# procedures however - in closure iterators, the parameter is always copied
# into the closure (!) meaning that non-raw `{.async.}` functions always carry
# this overhead, sink or no. See usages of chronosMoveSink for examples.
# In addition, we need to work around https://github.com/nim-lang/Nim/issues/22175
# which has not been backported to 1.6.
# Long story short, the workaround is not needed in non-raw {.async.} because
# a copy of the literal is always made.
# TODO review the above for 2.0 / 2.0+refc
type
  SeqHeader = object
    length, reserved: int

proc isLiteral(s: string): bool {.inline.} =
  when defined(gcOrc) or defined(gcArc):
    false
  else:
    s.len > 0 and (cast[ptr SeqHeader](s).reserved and (1 shl (sizeof(int) * 8 - 2))) != 0

proc isLiteral[T](s: seq[T]): bool {.inline.} =
  when defined(gcOrc) or defined(gcArc):
    false
  else:
    s.len > 0 and (cast[ptr SeqHeader](s).reserved and (1 shl (sizeof(int) * 8 - 2))) != 0

template chronosMoveSink*(val: auto): untyped =
  bind isLiteral
  when not (defined(gcOrc) or defined(gcArc)) and val is seq|string:
    if isLiteral(val):
      val
    else:
      move(val)
  else:
    move(val)
