# Copyright (c) 2019-2021 Status Research & Development GmbH
# Licensed under either of
#  * Apache License, version 2.0,
#  * MIT license
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.

when not compileOption("debuginfo"):
  stderr.write("libbacktrace error: no debugging symbols available. Compile with '--debugger:native'.\n")
  stderr.flushFile()

when defined(nimStackTraceOverride) and defined(nimHasStacktracesModule):
  import system/stacktraces

# Don't warn that this module is unused (e.g.: when the Nim compiler supports it
# and users need to import it, even if they don't call getBacktrace() manually).
{.used.}

# There is no "copyMem()" in Nimscript, so "getBacktrace()" will not work in
# there, but we might still want to import this module with a global
# "--import:libbacktrace" Nim compiler flag.
when not (defined(nimscript) or defined(js)):
  import algorithm, libbacktrace_wrapper, os, system/ansi_c

  const installPath = currentSourcePath.parentDir() / "install" / "usr"

  {.passc: "-I" & currentSourcePath.parentDir().}

  when defined(cpp):
    {.passl: installPath / "lib" / "libbacktracenimcpp.a".}
  else:
    {.passl: installPath / "lib" / "libbacktracenim.a".}

  when defined(libbacktraceUseSystemLibs):
    {.passl: "-lbacktrace".}
    when defined(macosx) or defined(windows):
      {.passl: "-lunwind".}
  else:
    {.passc: "-I" & installPath / "include".}
    {.passl: installPath / "lib" / "libbacktrace.a".}
    when defined(macosx) or defined(windows):
      {.passl: installPath / "lib" / "libunwind.a".}

  when defined(windows):
    {.passl: "-lpsapi".}

  proc getBacktrace*(): string {.noinline.} =
    let
      # bt: cstring = get_backtrace_c()
      bt: cstring = get_backtrace_max_length_c(max_length = 128, skip = 3)
      btLen = len(bt)

    result = newString(btLen)
    if btLen > 0:
      copyMem(addr(result[0]), bt, btLen)
    c_free(bt)

  when defined(nimStackTraceOverride) and declared(registerStackTraceOverride):
    registerStackTraceOverride(getBacktrace)

  proc getProgramCounters*(maxLength: cint): seq[cuintptr_t] {.noinline.} =
    result = newSeqOfCap[cuintptr_t](maxLength)

    var
      pcPtr = get_program_counters_c(max_length = maxLength, skip = 2)
      iPtr = pcPtr

    while iPtr[] != 0:
      result.add(iPtr[])
      iPtr = cast[ptr cuintptr_t](cast[uint](iPtr) + sizeof(cuintptr_t).uint)

    c_free(pcPtr)

  when defined(nimStackTraceOverride) and declared(registerStackTraceOverrideGetProgramCounters):
    registerStackTraceOverrideGetProgramCounters(getProgramCounters)

  proc getDebuggingInfo*(programCounters: seq[cuintptr_t], maxLength: cint): seq[StackTraceEntry] {.noinline.} =
    result = newSeqOfCap[StackTraceEntry](maxLength)
    if programCounters.len == 0:
      return

    var
      functionInfoPtr = get_debugging_info_c(unsafeAddr programCounters[0], maxLength)
      iPtr = functionInfoPtr
      res: StackTraceEntry

    while iPtr[].filename != nil:
      # Older stdlib doesn't have this field in "StackTraceEntry".
      when compiles(res.filenameStr):
        let filenameLen = len(iPtr[].filename)
        res.filenameStr = newString(filenameLen)
        if filenameLen > 0:
          copyMem(addr(res.filenameStr[0]), iPtr[].filename, filenameLen)
        res.filename = res.filenameStr

      res.line = iPtr[].lineno

      when compiles(res.procnameStr):
        let functionLen = len(iPtr[].function)
        res.procnameStr = newString(functionLen)
        if functionLen > 0:
          copyMem(addr(res.procnameStr[0]), iPtr[].function, functionLen)
        res.procname = res.procnameStr

      c_free(iPtr[].filename)
      c_free(iPtr[].function)

      iPtr = cast[ptr DebuggingInfo](cast[uint](iPtr) + sizeof(DebuggingInfo).uint)
      result.add(res)

    c_free(functionInfoPtr)

    # Nim convention.
    reverse(result)

  when defined(nimStackTraceOverride) and declared(registerStackTraceOverrideGetDebuggingInfo):
    registerStackTraceOverrideGetDebuggingInfo(getDebuggingInfo)