#!/bin/sh

# nanonext configure (POSIX / macOS / Emscripten).
#
# Uses a system 'libnng' >= 1.12 and 'libmbedtls' >= 3.0 when present; otherwise
# compiles the bundled sources directly into nanonext.so via the explicit
# per-object rules in src/Makevars.in -- no cmake, no static archives, no
# install step. The only genuinely platform-specific work is replicating NNG's
# POSIX feature probes (ported below from its CMake checks), used to set the
# NNG_HAVE_* macros and to select the poller / random-source variant.

# Initialise
PKG_CPPFLAGS=""
PKG_LIBS=""
NNG_DEFS=""
NNG_OBJECTS=""
MBEDTLS_OBJECTS=""

# Append a link flag to PKG_LIBS, deduplicated.
add_lib() {
  case " $PKG_LIBS " in
    *" $1 "*) ;;
    *) PKG_LIBS="$PKG_LIBS $1" ;;
  esac
}

# Append -D<macro> to the NNG define set.
add_def() { NNG_DEFS="$NNG_DEFS -D$1"; }

# Read a tools/*.list source list, stripping comment and blank lines.
read_list() { grep -v '^#' "$1" | grep -v '^[[:space:]]*$'; }

# Emit a non-functional stub library source (used by the unsupported-platform
# fallback below). Every native routine registered in src/init.c is re-registered
# here bound to a single function that errors when called, so the package installs
# and loads but is inert. The routine list is derived from src/init.c so it cannot
# drift from the real registration table.
generate_stub() {
  cat > "$1" <<'EOF'
/* Generated by configure -- STUB BUILD. Do not edit by hand.
 *
 * This platform cannot build the bundled NNG, so nanonext is built as a stub:
 * it installs and loads, but every native call errors. This keeps packages that
 * depend on nanonext installable here. The registered routine list mirrors
 * src/init.c (generated, so it cannot drift).
 */
#include <R.h>
#include <Rinternals.h>
#include <R_ext/Rdynload.h>
#include <R_ext/Visibility.h>

static SEXP rnng_stub(void) {
  Rf_error("nanonext is not functional on this platform: it was installed as a "
           "stub because the system lacks the POSIX threading/socket layer "
           "required to build NNG");
  return R_NilValue;
}

static const R_CallMethodDef callMethods[] = {
EOF
  grep -E '\(DL_FUNC\)' src/init.c \
    | sed -E 's/^[[:space:]]*\{"([^"]+)".*/  {"\1", (DL_FUNC) \&rnng_stub, -1},/' >> "$1"
  cat >> "$1" <<'EOF'
  {NULL, NULL, 0}
};

void attribute_visible R_init_nanonext(DllInfo *dll) {
  R_registerRoutines(dll, NULL, callMethods, NULL, NULL);
  R_useDynamicSymbols(dll, FALSE);
  R_forceSymbols(dll, TRUE);
}
EOF
}

# Helper function: find library paths
# Usage: find_lib_paths <lib_name> <default_libs>
# Sets: LIB_CFLAGS, LIB_LIBS
find_lib_paths() {
  lib_name=$1
  default_libs=$2
  LIB_CFLAGS=""
  LIB_LIBS="$default_libs"

  if [ "$INCLUDE_DIR" ] || [ "$LIB_DIR" ]; then
    LIB_CFLAGS="-I$INCLUDE_DIR"
    LIB_LIBS="-L$LIB_DIR $LIB_LIBS"
    echo "Found INCLUDE_DIR $INCLUDE_DIR"
    echo "Found LIB_DIR $LIB_DIR"
  elif [ -d "/usr/local/include/$lib_name" ]; then
    LIB_CFLAGS="-I/usr/local/include"
    LIB_LIBS="-L/usr/local/lib $LIB_LIBS"
  elif [ -d "/usr/include/$lib_name" ]; then
    LIB_CFLAGS="-I/usr/include"
    LIB_LIBS="-L/usr/lib $LIB_LIBS"
  elif [ -d "/usr/local/opt/$lib_name" ]; then
    LIB_CFLAGS="-I/usr/local/opt/$lib_name/include"
    LIB_LIBS="-L/usr/local/opt/$lib_name/lib $LIB_LIBS"
  fi
}

# --- NNG feature probes (ported from src/nng/.../CMakeLists.txt) -------------
# Each probe compiles + links a small program with the same feature-test defines
# as the real build, mirroring CMake's check_function_exists / check_symbol_exists
# / check_library_exists / check_struct_has_member. On success it sets a shell
# variable named after the macro (e.g. NNG_HAVE_KQUEUE=yes) and adds the define;
# check_lib additionally adds -l<lib> to the link flags.

# check_function_exists: link test, self-declared symbol (no header).
nng_check_func() {
  printf 'char %s();\nint main(void){ return (int) %s(); }\n' "$1" "$1" > "$CONFTEST_C"
  if ${CC} ${PROBE_CFLAGS} "$CONFTEST_C" -o "$CONFTEST_X" > /dev/null 2>&1; then
    add_def "$2=1"; eval "$2=yes"
  else
    eval "$2=no"
  fi
}

# check_symbol_exists: compile + link, include header, reference symbol. Handles
# both macros and functions/variables exactly as CMake's CheckSymbolExists.
nng_check_sym() {
  {
    printf '#include <%s>\n' "$2"
    printf 'int main(int argc, char **argv){ (void)argv;\n'
    printf '#ifndef %s\n  return ((int *)(&%s))[argc];\n#else\n  (void)argc; return 0;\n#endif\n}\n' "$1" "$1"
  } > "$CONFTEST_C"
  if ${CC} ${PROBE_CFLAGS} "$CONFTEST_C" -o "$CONFTEST_X" > /dev/null 2>&1; then
    add_def "$3=1"; eval "$3=yes"
  else
    eval "$3=no"
  fi
}

# check_library_exists: link test adding -l<lib>; defines the macro and links the
# library on success.
nng_check_lib() {
  printf 'char %s();\nint main(void){ return (int) %s(); }\n' "$2" "$2" > "$CONFTEST_C"
  if ${CC} ${PROBE_CFLAGS} "$CONFTEST_C" "-l$1" -o "$CONFTEST_X" > /dev/null 2>&1; then
    add_def "$3=1"; eval "$3=yes"; add_lib "-l$1"
  else
    eval "$3=no"
  fi
}

# check_struct_has_member: compile + link, reference struct member.
nng_check_struct_member() {
  {
    printf '#include <%s>\n' "$3"
    printf 'int main(void){ struct %s s; (void) (s.%s); return 0; }\n' "$1" "$2"
  } > "$CONFTEST_C"
  if ${CC} ${PROBE_CFLAGS} "$CONFTEST_C" -o "$CONFTEST_X" > /dev/null 2>&1; then
    add_def "$4=1"; eval "$4=yes"
  else
    eval "$4=no"
  fi
}

# Find compiler and export flags
CC=`"${R_HOME}/bin/R" CMD config CC`
CFLAGS=`"${R_HOME}/bin/R" CMD config CFLAGS`
LDFLAGS=`"${R_HOME}/bin/R" CMD config LDFLAGS`
export CC CFLAGS LDFLAGS

# Detect -latomic linker flag for ARM architectures (Raspberry Pi etc.)
# Skip for Emscripten: wasm has no libatomic and emcc rejects -o /dev/null
case "$CC" in
  *emcc*) ;;
  *)
    echo "#include <stdint.h>
uint64_t v;
int main() {
    return (int)__atomic_load_n(&v, __ATOMIC_ACQUIRE);
}" | ${CC} -xc - -o /dev/null > /dev/null 2>&1
    if [ $? -ne 0 ]; then
      echo "Adding -latomic linker flag ..."
      add_lib -latomic
    fi
    ;;
esac

# Inject Emscripten link-time tuning when building pthread-WASM
case "$CC" in
  *emcc*)
    if echo " $CFLAGS $PKG_CFLAGS " | grep -q ' -pthread '; then
      echo "Detected pthread-WASM target"
      PKG_LIBS="$PKG_LIBS -s PTHREAD_POOL_SIZE=16 -s ALLOW_MEMORY_GROWTH=1 -s INITIAL_MEMORY=33554432"
    fi
    ;;
esac

# Determine whether to compile bundled libraries
compile_mbedtls=0
compile_nng=0
MBEDTLS_SYS_CFLAGS=""
MBEDTLS_SYS_LIBS=""
NNG_SYS_CFLAGS=""
NNG_SYS_LIBS=""

if [ -n "$NANONEXT_LIBS" ]; then
  echo "NANONEXT_LIBS is set... building from source"
  compile_mbedtls=1
  compile_nng=1
else
  # Find MbedTLS
  find_lib_paths "mbedtls" "-lmbedtls -lmbedx509 -lmbedcrypto"
  MBEDTLS_SYS_CFLAGS="$LIB_CFLAGS"
  MBEDTLS_SYS_LIBS="$LIB_LIBS"

  echo "#include <mbedtls/version.h>
int main() {
#if MBEDTLS_VERSION_MAJOR < 3
    *(void *) 0 = 0;
#endif
}" | ${CC} ${MBEDTLS_SYS_CFLAGS} -xc - -o /dev/null > /dev/null 2>&1
  if [ $? -ne 0 ]; then
    compile_mbedtls=1
  else
    echo "Found 'libmbedtls' $MBEDTLS_SYS_CFLAGS"
  fi

  # Find NNG
  find_lib_paths "nng" "-lnng"
  NNG_SYS_CFLAGS="$LIB_CFLAGS"
  NNG_SYS_LIBS="$LIB_LIBS"

  echo "#include <nng/nng.h>
int main() {
#if NNG_MAJOR_VERSION < 1 || NNG_MAJOR_VERSION == 1 && NNG_MINOR_VERSION < 12
    *(void *) 0 = 0;
#endif
}" | ${CC} ${NNG_SYS_CFLAGS} -xc - -o /dev/null > /dev/null 2>&1
  if [ $? -ne 0 ]; then
    compile_nng=1
  else
    echo "Found 'libnng' $NNG_SYS_CFLAGS"
  fi
fi

# --- Unsupported-platform fallback ------------------------------------------
# NNG's bundled POSIX layer needs the pthreads and BSD-socket headers. A target
# that lacks them -- a genuinely non-POSIX OS -- cannot build the bundled NNG.
# Rather than fail the install with an opaque compile error deep in the NNG
# sources, fall back to a stub library: nanonext installs and loads, but its
# functions error when called. This keeps packages that hard-depend on nanonext
# installable everywhere.
#
# The probe is compile-only (header presence), deliberately: WebAssembly /
# Emscripten provides these headers and builds fine, with -pthread only enabling
# threading at runtime -- so a wasm build must NOT stub. Only relevant for the
# bundled build: a detected system libnng was, by definition, already built for
# this platform. Every platform CRAN checks has the POSIX layer, so the stub
# never displaces the real build there.
NANONEXT_STUB=0
if [ "$compile_nng" -eq 1 ]; then
  # Only diagnose a missing POSIX layer when the compiler itself works: a broken
  # / empty CC must fail the real build loudly, not silently stub (stubbing would
  # mask a toolchain problem as an unsupported platform).
  echo 'int main(void){return 0;}' > nano-cc.c
  if ${CC} ${CFLAGS} -c nano-cc.c -o nano-cc.o > /dev/null 2>&1; then
    cat > nano-essential.c <<'EOF'
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <poll.h>
int main(void) {
  (void) &pthread_create;
  (void) &pthread_join;
  (void) &socket;
  (void) &poll;
  (void) AF_INET;
  return 0;
}
EOF
    if ! ${CC} ${CFLAGS} -c nano-essential.c -o nano-essential.o > /dev/null 2>&1; then
      echo "Platform lacks the POSIX threading/socket layer required by NNG."
      NANONEXT_STUB=1
    fi
  fi
  rm -f nano-cc.c nano-cc.o nano-essential.c nano-essential.o
fi

if [ "$NANONEXT_STUB" -eq 1 ]; then
  echo "*** Configuring a NON-FUNCTIONAL stub build of nanonext. ***"
  echo "*** The package will install and load, but its functions error when called. ***"
  generate_stub src/stub.c
  cat > src/Makevars <<'EOF'
# Generated by configure -- STUB BUILD (do not edit by hand).
#
# This platform cannot build the bundled NNG (it lacks the POSIX threading /
# socket headers NNG requires -- a genuinely non-POSIX OS). nanonext is built as
# a stub: it installs and loads, but every native call errors. This keeps
# packages that depend on nanonext installable here.
PKG_CFLAGS = $(C_VISIBILITY)
OBJECTS = stub.o

all: $(SHLIB)
$(SHLIB): $(OBJECTS)
EOF
  exit 0
fi

# --- Mbed TLS: bundled or system --------------------------------------------
# MBEDTLS_INC is the include flag the bundled NNG TLS engine and the package's
# own tls.c compile against (bundled headers, or the detected system headers).
if [ $compile_mbedtls -eq 1 ]; then
  echo "Compiling bundled 'libmbedtls' in place ..."
  MBEDTLS_INC="-Imbedtls/include"
  PKG_CPPFLAGS="$PKG_CPPFLAGS -Imbedtls/include"
  MBEDTLS_OBJECTS=`read_list tools/mbedtls.list | tr '\n' ' ' | sed 's/  */ /g; s/ *$//'`
else
  MBEDTLS_INC="$MBEDTLS_SYS_CFLAGS"
  PKG_CPPFLAGS="$PKG_CPPFLAGS $MBEDTLS_SYS_CFLAGS"
  PKG_LIBS="$MBEDTLS_SYS_LIBS $PKG_LIBS"
fi

# --- NNG: bundled or system --------------------------------------------------
if [ $compile_nng -eq 1 ]; then
  echo "Compiling bundled 'libnng' in place ..."

  # Fixed NNG option set (all protocols / transports on, TLS = mbed, stats on).
  # Identical common set to tools/generate_makevars.sh (Windows path).
  NNG_COMMON_DEFS="-DNNG_STATIC_LIB -DNNG_SUPP_TLS -DNNG_SUPP_HTTP -DNNG_ENABLE_STATS -DNNG_TLS_ENGINE_INIT=nng_tls_engine_init_mbed -DNNG_TLS_ENGINE_FINI=nng_tls_engine_fini_mbed -DNNG_TRANSPORT_INPROC -DNNG_TRANSPORT_IPC -DNNG_TRANSPORT_TCP -DNNG_TRANSPORT_TLS -DNNG_TRANSPORT_WS -DNNG_TRANSPORT_WSS -DNNG_TRANSPORT_FDC"

  # Feature-test macros: needed by glibc / SunOS, harmless elsewhere. Declared
  # unconditionally by NNG's POSIX layer; also used while probing so detection
  # matches compilation.
  POSIX_FT_DEFS="-D_GNU_SOURCE -D_REENTRANT -D_THREAD_SAFE -D_POSIX_PTHREAD_SEMANTICS"

  IS_EMSCRIPTEN=no
  case "$CC" in
    *emcc*) IS_EMSCRIPTEN=yes ;;
  esac

  # Platform -D set, derived from uname (mirrors NNG's CMakeLists platform block).
  if [ "$IS_EMSCRIPTEN" = yes ]; then
    NNG_PLATFORM_DEFS="-DNNG_PLATFORM_POSIX -DNNG_PLATFORM_EMSCRIPTEN -DNNG_USE_GETTIMEOFDAY"
  else
    case `uname -s` in
      Linux)   NNG_PLATFORM_DEFS="-DNNG_PLATFORM_POSIX -DNNG_PLATFORM_LINUX -DNNG_USE_EVENTFD -DNNG_HAVE_ABSTRACT_SOCKETS" ;;
      Darwin)  NNG_PLATFORM_DEFS="-DNNG_PLATFORM_POSIX -DNNG_PLATFORM_DARWIN" ;;
      FreeBSD) NNG_PLATFORM_DEFS="-DNNG_PLATFORM_POSIX -DNNG_PLATFORM_FREEBSD" ;;
      NetBSD)  NNG_PLATFORM_DEFS="-DNNG_PLATFORM_POSIX -DNNG_PLATFORM_NETBSD" ;;
      OpenBSD) NNG_PLATFORM_DEFS="-DNNG_PLATFORM_POSIX -DNNG_PLATFORM_OPENBSD" ;;
      SunOS)   NNG_PLATFORM_DEFS="-DNNG_PLATFORM_POSIX -DNNG_PLATFORM_SUNOS" ;;
      *)       NNG_PLATFORM_DEFS="-DNNG_PLATFORM_POSIX" ;;
    esac
  fi

  # Seed the NNG define set: mbedtls include + fixed options + platform +
  # feature-test macros. The probes below append the NNG_HAVE_* macros.
  NNG_DEFS="$MBEDTLS_INC $NNG_COMMON_DEFS $NNG_PLATFORM_DEFS $POSIX_FT_DEFS"

  # Probe scratch space + flags. Probes use the build CFLAGS plus the feature-test
  # macros so header availability and detection match the real compilation.
  PROBE_DIR=`mktemp -d 2>/dev/null || echo "./nano-conftest.$$"`
  mkdir -p "$PROBE_DIR"
  CONFTEST_C="$PROBE_DIR/conftest.c"
  CONFTEST_X="$PROBE_DIR/conftest"
  PROBE_CFLAGS="$CFLAGS $POSIX_FT_DEFS"

  echo "Probing platform features for bundled NNG ..."

  # core/CMakeLists.txt
  nng_check_sym strlcpy    string.h NNG_HAVE_STRLCPY
  nng_check_sym strnlen    string.h NNG_HAVE_STRNLEN
  nng_check_sym strcasecmp string.h NNG_HAVE_STRCASECMP
  nng_check_sym strncasecmp string.h NNG_HAVE_STRNCASECMP

  # platform/posix/CMakeLists.txt
  nng_check_func lockf          NNG_HAVE_LOCKF
  nng_check_func flock          NNG_HAVE_FLOCK
  nng_check_func getrandom      NNG_HAVE_GETRANDOM
  nng_check_func arc4random_buf NNG_HAVE_ARC4RANDOM

  nng_check_func clock_gettime  NNG_HAVE_CLOCK_GETTIME_LIBC
  if [ "$NNG_HAVE_CLOCK_GETTIME_LIBC" = yes ]; then
    add_def NNG_HAVE_CLOCK_GETTIME; NNG_HAVE_CLOCK_GETTIME=yes
  else
    nng_check_lib rt clock_gettime NNG_HAVE_CLOCK_GETTIME
  fi
  nng_check_lib pthread sem_wait            NNG_HAVE_SEMAPHORE_PTHREAD
  nng_check_lib pthread pthread_atfork      NNG_HAVE_PTHREAD_ATFORK_PTHREAD
  nng_check_lib pthread pthread_set_name_np NNG_HAVE_PTHREAD_SET_NAME_NP
  nng_check_lib pthread pthread_setname_np  NNG_HAVE_PTHREAD_SETNAME_NP
  nng_check_lib nsl     gethostbyname       NNG_HAVE_LIBNSL
  nng_check_lib socket  socket              NNG_HAVE_LIBSOCKET
  nng_check_lib atomic  __atomic_load_1     NNG_HAVE_LIBATOMIC

  nng_check_sym AF_UNIX               sys/socket.h NNG_HAVE_UNIX_SOCKETS
  nng_check_sym backtrace_symbols_fd  execinfo.h   NNG_HAVE_BACKTRACE
  nng_check_struct_member msghdr msg_control sys/socket.h NNG_HAVE_MSG_CONTROL
  nng_check_sym eventfd               sys/eventfd.h NNG_HAVE_EVENTFD
  nng_check_sym kqueue                sys/event.h   NNG_HAVE_KQUEUE
  nng_check_sym port_create           port.h        NNG_HAVE_PORT_CREATE
  nng_check_sym epoll_create          sys/epoll.h   NNG_HAVE_EPOLL
  nng_check_sym epoll_create1         sys/epoll.h   NNG_HAVE_EPOLL_CREATE1
  nng_check_sym getpeereid            unistd.h      NNG_HAVE_GETPEEREID
  nng_check_sym SO_PEERCRED           sys/socket.h  NNG_HAVE_SOPEERCRED
  nng_check_struct_member sockpeercred uid sys/socket.h NNG_HAVE_SOCKPEERCRED
  nng_check_sym LOCAL_PEERCRED        sys/un.h      NNG_HAVE_LOCALPEERCRED
  nng_check_sym LOCAL_PEERPID         sys/un.h      NNG_HAVE_LOCALPEERPID
  nng_check_sym getpeerucred          ucred.h       NNG_HAVE_GETPEERUCRED
  nng_check_sym atomic_flag_test_and_set stdatomic.h NNG_HAVE_STDATOMIC
  nng_check_sym socketpair            sys/socket.h  NNG_HAVE_SOCKETPAIR

  rm -rf "$PROBE_DIR"

  # Select exactly one poller source (avoids duplicate symbols from the
  # unguarded poll fallback). Verbatim from NNG's CMake selection.
  if [ "$NNG_HAVE_PORT_CREATE" = yes ]; then
    POLLER_OBJ='$(NNG)/platform/posix/posix_pollq_port.o'
  elif [ "$NNG_HAVE_KQUEUE" = yes ]; then
    POLLER_OBJ='$(NNG)/platform/posix/posix_pollq_kqueue.o'
  elif [ "$NNG_HAVE_EPOLL" = yes ] && [ "$NNG_HAVE_EVENTFD" = yes ]; then
    POLLER_OBJ='$(NNG)/platform/posix/posix_pollq_epoll.o'
  else
    POLLER_OBJ='$(NNG)/platform/posix/posix_pollq_poll.o'
  fi

  # Select exactly one random source.
  if [ "$IS_EMSCRIPTEN" = yes ]; then
    RAND_OBJ='$(NNG)/platform/posix/posix_rand_urandom.o'
  elif [ "$NNG_HAVE_ARC4RANDOM" = yes ]; then
    RAND_OBJ='$(NNG)/platform/posix/posix_rand_arc4random.o'
  elif [ "$NNG_HAVE_GETRANDOM" = yes ]; then
    RAND_OBJ='$(NNG)/platform/posix/posix_rand_getrandom.o'
  else
    RAND_OBJ='$(NNG)/platform/posix/posix_rand_urandom.o'
  fi

  NNG_OBJECTS=`{ read_list tools/nng_common.list; read_list tools/nng_posix.list; } | tr '\n' ' ' | sed 's/  */ /g; s/ *$//'`
  NNG_OBJECTS="$NNG_OBJECTS $POLLER_OBJ $RAND_OBJ"

  # Package sources compile against the bundled NNG headers as a static library.
  PKG_CPPFLAGS="$PKG_CPPFLAGS -Inng/include -DNNG_STATIC_LIB"
else
  PKG_CPPFLAGS="$PKG_CPPFLAGS $NNG_SYS_CFLAGS"
  PKG_LIBS="$NNG_SYS_LIBS $PKG_LIBS"
fi

# Trim leading/trailing whitespace from substituted values.
PKG_CPPFLAGS=`echo "$PKG_CPPFLAGS" | sed 's/  */ /g; s/^ *//; s/ *$//'`
PKG_LIBS=`echo "$PKG_LIBS" | sed 's/  */ /g; s/^ *//; s/ *$//'`
NNG_DEFS=`echo "$NNG_DEFS" | sed 's/  */ /g; s/^ *//; s/ *$//'`

# Write to Makevars
sed -e "s|@cppflags@|$PKG_CPPFLAGS|" \
    -e "s|@libs@|$PKG_LIBS|" \
    -e "s|@nng_defs@|$NNG_DEFS|" \
    -e "s|@nng_objects@|$NNG_OBJECTS|" \
    -e "s|@mbedtls_objects@|$MBEDTLS_OBJECTS|" \
    src/Makevars.in > src/Makevars

# Success
exit 0
