#!/bin/sh
## configure: UNIX
##
## details:
##
##  configure will *always* check for a pre-installed
##  TA-Lib using default locations using #include <ta-lib/ta_libc.h>
##  whether it builds, or not, is left to mercy of your installed compiler (gcc).
##  If the routine can't be built (and the reason does not matter) configure
##  will use the vendored TA-Lib located in src/ta-lib
##  This behaviour can be overridden by passing --force-vendor
## 
set -eu

## {pkg} information
RPKGNAME=$(grep "^Package:" DESCRIPTION | sed "s/Package: //")
RPKGVERSION=$(grep "^Version:" DESCRIPTION | sed "s/Version: //")

echo
echo Configure for {$RPKGNAME} v$RPKGVERSION
echo

## utility functions
##
## details:
##  these functions serves absolutely no *real*
##  purpose other than coloring simplifying the
##  messages sent to the host.
##
##  NOTE: defined before the argument parser so the
##        helpers are available if an unknown flag
##        triggers an error message.
error() {
    printf '\e[0;31m%s\033[0m' "Build-error:"
}

url() {
    printf '\e[0;34m%s\033[0m' $1
}

var() {
    printf '\e[0;34m%s\033[0m' $1
}

info() {
    printf '\e[0;34m%s\033[0m' "Build-step:"
}

success() {
    printf '[\e[0;32m%s\033[0m]' "OK"
}

warning() {
    printf '[\e[0;33m%s\033[0m]' "Failed"
}

## details:
##  this function generates a temporary
##  directory for building the libraries
##  and is deleted after configure is done
temporary_directory() {
  dir="$(mktemp -d)" || {
    echo
    printf '%s\n' "$(error) could not create temporary directory"
    exit 1
  }
  printf '%s' "$dir"
}

## configure args and vars
##
## details:
##  args are passed as: --configure-args="--foo-bar"
##  vars are passed as: --configure-vars="FOO='DOTHIS'"
##
##  args:
##      --force-vendor: if passed it will use the vendored
##          TA-Lib via CMake
##
##  any other argument is forwarded verbatim as a C/C++
##  compiler flag to both the vendored TA-Lib (CMake) build
##  and the R wrapper compile step (Makevars). Example:
##
##      R CMD INSTALL . --configure-args="-O3 -march=native"
##
##  NOTE: binaries built with -march=native are tied to the
##        build host's CPU features and are NOT portable.
##
##  vars:
##
FORCE_VENDOR=0
OPTFLAGS=""
for arg in "$@"; do
  case "$arg" in
    --force-vendor)
      FORCE_VENDOR=1
      ;;
    ## autotools-style arguments injected by `emconfigure` (and by other
    ## cross-compile harnesses) — dropped so they don't leak into
    ## CMAKE_C_FLAGS and get handed to the compiler as unknown flags.
    --build=*|--host=*|--target=*|ac_cv_*=*)
      ;;
    *)
      OPTFLAGS="${OPTFLAGS} ${arg}"
      ;;
  esac
done

if [ -n "$OPTFLAGS" ]; then
    printf '%s Forwarding compiler flags:%s\n' "$(info)" "$OPTFLAGS"
fi

## sanity checks
##
## details:
##  taken from the 'Writing R Extensions' these steps
##  should always be included.
printf '%s Checking for %s' "$(info)" "$(var R_HOME)"

## details:
##  check R_HOME
: ${R_HOME=`R RHOME`}
if test -z "${R_HOME}"; then
    printf ' %s\n'"$(warning)"
    printf '%s could not determine %s\n'"$(error)" "$(var R_HOME)"
    printf '\n'
    exit 1
else
    printf ' %s\n' "$(success)"
    R_BIN="${R_HOME}/bin/R"
fi

## R compiler configuration
##
## details:
##  CMake's compiler probe walks $PATH and on macOS picks up
##  /usr/bin/clang from Xcode CLT instead of the R toolchain at
##  /opt/R/<arch>/bin/clang. The resulting libta-lib.a then has
##  a different SDK / deployment target than the .so being
##  linked by R, which manifests on the CRAN macOS builder as
##  an early install failure with no useful surface log.
##
##  WRE Section 1.2.6 ("Using cmake") instructs to propagate CC/CXX/
##  CFLAGS/CXXFLAGS/CPPFLAGS/LDFLAGS from R's config to cmake.
##  We capture them once here and reuse them below — both for
##  the system TA-Lib link probe (so it tests the *same*
##  compiler R will use) and for the vendored CMake build.
R_CC_FULL=$("$R_BIN" CMD config CC)
R_CXX_FULL=$("$R_BIN" CMD config CXX)
R_CFLAGS=$("$R_BIN" CMD config CFLAGS)
R_CXXFLAGS=$("$R_BIN" CMD config CXXFLAGS)
R_CPPFLAGS=$("$R_BIN" CMD config CPPFLAGS)
R_LDFLAGS=$("$R_BIN" CMD config LDFLAGS)

## details:
##  R CMD config returns the compiler with leading flags baked in
##  (e.g. "clang -arch x86_64" or "/opt/R/x86_64/bin/clang -arch
##  arm64"). CMAKE_C_COMPILER expects a bare executable path, so
##  split the first token off as the compiler binary and fold the
##  remainder into the flags.
R_CC=$(echo "$R_CC_FULL" | awk '{print $1}')
R_CC_EXTRA=$(echo "$R_CC_FULL" | cut -s -d' ' -f2-)
R_CXX=$(echo "$R_CXX_FULL" | awk '{print $1}')
R_CXX_EXTRA=$(echo "$R_CXX_FULL" | cut -s -d' ' -f2-)

## details:
##  compile a small C-routine with R CMD SHLIB
##  as a belt and buckles test
printf '%s Compiling C-program with R CMD SHLIB' "$(info)"
SHLIB_TEST=$(temporary_directory)

cat > "${SHLIB_TEST}/conftest.c" <<'EOF'
void foo(void) {}
EOF

if ! ( cd "$SHLIB_TEST" && "$R_BIN" CMD SHLIB conftest.c >/dev/null 2>&1 ); then
  printf ' %s\n' "$(warning)"
  echo $(error) R CMD SHLIB test compilation failed. Build-tools may be missing or misconfigured.
  exit 1
fi

printf ' %s\n' $(success)
rm -rf $SHLIB_TEST

## TA-Lib availability
##
## details:
##  CRAN requires compiled packages with external dependencies to
##  prefer the user's installation. We probe via pkg-config — bare
##  `$CC` only sees default include paths, so a Homebrew install
##  under /opt/homebrew or a MacPorts install under /opt/local would
##  be missed and the package would silently fall back to vendor.
##  pkg-config supplies the right -I and -L; we then compile AND link
##  a test program that calls a real TA-Lib symbol, so a stale header
##  without a resolvable library can't masquerade as a working install.
##
##  Any failure (pkg-config missing, ta-lib.pc absent, link fails)
##  falls through to the vendored build. --force-vendor short-circuits
##  detection entirely so PREINSTALLED stays 1 and the Makevars step
##  picks the vendored static library.
PREINSTALLED=1
TALIB_CFLAGS=""
TALIB_LIBS=""

if [ "$FORCE_VENDOR" -eq 0 ]; then
    printf '%s Probing for system TA-Lib via pkg-config' "$(info)"

    PKG_CONFIG_MISSING=0
    if ! command -v pkg-config >/dev/null 2>&1; then
        PKG_CONFIG_MISSING=1
    elif pkg-config --exists ta-lib; then
        TALIB_CFLAGS="$(pkg-config --cflags ta-lib)"
        TALIB_LIBS="$(pkg-config --libs ta-lib)"

        TALIB_SYSTEM=$(temporary_directory)
        cat > "${TALIB_SYSTEM}/conftest.c" <<'EOF'
#include <ta-lib/ta_libc.h>
int main(void){ TA_Initialize(); TA_Shutdown(); return 0; }
EOF

        ## details:
        ##  $TALIB_CFLAGS / $TALIB_LIBS are intentionally unquoted —
        ##  pkg-config returns space-separated flag lists that must
        ##  word-split to reach the compiler as separate arguments.
        : "${CC:=$R_CC}"
        if ( cd "$TALIB_SYSTEM" && $CC $TALIB_CFLAGS conftest.c $TALIB_LIBS -o conftest >/dev/null 2>&1 ); then
            PREINSTALLED=0
        fi
        rm -rf "$TALIB_SYSTEM"
    fi

    if [ "$PREINSTALLED" -eq 0 ]; then
        printf ' %s\n' "$(success)"
    else
        printf ' %s\n' "$(warning)"
        ## details:
        ##  surface the most likely user-fixable cause of fallback —
        ##  a missing system TA-Lib is normal on CRAN builders, but
        ##  a missing pkg-config on a machine that *does* have ta-lib
        ##  installed is silent and confusing without a hint here.
        if [ "$PKG_CONFIG_MISSING" -eq 1 ]; then
            printf '%s pkg-config not found on PATH — falling back to vendored TA-Lib. Install pkg-config to link against a system install instead.\n' "$(info)"
        fi
    fi
fi

## conditional building using CMake
## for vendored TA-Lib
## 
## details:
##  if --force-vendor is passed FORCE_VENDOR
##  is set to 1, 0 otherwise - so everything
##  is based on the above compilation
if [ "$(( PREINSTALLED + FORCE_VENDOR ))" -ne 0 ]; then
    TALIB="src/ta-lib" # vendor location
    
    ## vendor check start
    ##  it's NOT enough to check for $TALIB as
    ##  the folder gets cloned, but its empty, 
    ##  and therefore never enters the branch.
    ##      - instead of checking for the folder
    ##        we check for CMakeLists.txt inside the
    ##        folder, which is safe because if its not
    ##        there - then {talib} can't build anyways.
    if  [ ! -f "$TALIB/CMakeLists.txt" ]; then

        if ! command -v git > /dev/null 2>&1; then
            printf ' %s\n' "$(warning)"
            echo
            echo $(error) Git is not found on default PATH.
            echo
            echo "=========================================================================="
            echo "Install Git:"
            echo "  Linux:"
            echo "    Debian/Ubuntu: sudo apt-get update && sudo apt-get install -y git"
            echo "    Fedora/RHEL/CentOS: sudo dnf install -y git"
            echo "    Arch: sudo pacman -S --needed git"
            echo "    openSUSE: sudo zypper install -y git"
            echo
            echo "  macOS:"
            echo "    Homebrew: brew install git"
            echo "    MacPorts: sudo port install git"
            echo "    Xcode CLT (also installs Git): xcode-select --install"
            echo 
            echo "After installation, ensure 'git' is on PATH and restart the shell/terminal."
            echo
            echo "=========================================================================="
            echo "Submit bug-reports here: $(url https://github.com/serkor1/ta-lib-R)"
            echo
            exit 1
        fi

        if git -C . status > /dev/null 2>&1; then
            printf ' %s\n' "$(warning)"
            printf "Build-error: $TALIB not found. Either make a new clone, or initialize the submodule.\n"
            printf "Clone:\t\tgit clone --recursive $(url https://github.com/serkor1/ta-lib-R.git)\n"
            printf "Initialize:\tgit submodule update --init --recursive\n"
            exit 1
        
        ## if its NOT a git repository it is safe to assume that the development
        ## version is being installed via {pak} or other tools which does not 
        ## include submodules by default
        else
            printf ' %s\n' "$(warning)"
            echo
            echo "TA-Lib (core) not found. Cloning $(url https://github.com/TA-Lib/ta-lib.git)"
            echo
            git clone https://github.com/TA-Lib/ta-lib.git ${TALIB} || {

                echo $(error) Could not clone $(url https://github.com/TA-Lib/ta-lib.git).
                echo Check your internet connection, or submit a bug-report.
                exit 1
                }
        fi

    fi

    ## vendor check end
    TARGET="$(cd "$TALIB" 2>/dev/null && pwd)/local"

    ## find CMake on PATH before proceeding
    ## with the build - this approach is necessary
    ## for MacOS which in (some) cases are located
    ## in a different PATH than otherwise.
    ##  - This approach is a part 'Writing R Extensions' and
    ##    is also recommended by Dirk Eddelbuettel.
    ## details:
    ##  `|| CMAKE=""` is load-bearing. Without it, `which` returning
    ##  non-zero propagates through `var=$(...)` to the assignment's
    ##  exit status; `set -e` then kills configure silently before the
    ##  explicit "CMake not found on PATH" branch below can print.
    if test -z "${CMAKE:-}"; then
        # Look for a cmake binary in the current path
        CMAKE=`which cmake 2>/dev/null` || CMAKE=""
    fi

    if test -z "${CMAKE:-}"; then
        # Check for a MacOS specific path
        CMAKE=`which /Applications/CMake.app/Contents/bin/cmake 2>/dev/null` || CMAKE=""
    fi
    
    if test -z "${CMAKE:-}"; then  
        ## NOTE: after many attempts I can't make 
        ##       configure "work" using libtools. It either complains about
        ##       missing libraries, or links.
        ##
        ##       CMake just works well here, end of story.
        printf ' %s\n' "$(error)"
        printf 'CMake not found on PATH\n'
        echo "=========================================================================="
        echo "Install CMake:"
        echo "  Linux:"
        echo "    Debian/Ubuntu: sudo apt-get update && sudo apt-get install -y cmake"
        echo "    Fedora/RHEL/CentOS: sudo dnf install -y cmake"
        echo "    Arch: sudo pacman -S --needed cmake"
        echo "    openSUSE: sudo zypper install -y cmake"
        echo
        echo "  macOS:"
        echo "    Homebrew: brew install cmake"
        echo "    MacPorts: sudo port install cmake"
        echo
        echo "Alternatively download the installer: https://cmake.org/download/"
        echo "After installation, restart the shell or add CMake to PATH"
        echo "=========================================================================="
        echo "Submit bug-reports here: $(url https://github.com/serkor1/ta-lib-R)"
        echo
        exit 1
    fi

    ## NOTE: this builds a temporary
    ##       directory to build TA-Lib
    ##       without this step .Rbuildignore will riot.
    ##       $TARGET must NOT be cleaned here — R CMD INSTALL
    ##       links against libta-lib.a inside it after configure
    ##       completes.
    BUILDLOCATION=$(temporary_directory)
    trap 'rm -rf "$BUILDLOCATION"' EXIT

    ## macOS-specific CMake settings
    ##
    ## details:
    ##  CRAN's macOS builders target a fixed deployment SDK (e.g.
    ##  -mmacosx-version-min=11.0 baked into R's CFLAGS) but run on
    ##  a newer host (e.g. Ventura 13.x). Without an explicit
    ##  CMAKE_OSX_DEPLOYMENT_TARGET, CMake uses the host version,
    ##  producing objects whose LC_BUILD_VERSION exceeds the .so's
    ##  target and breaking the link. CMAKE_OSX_ARCHITECTURES is
    ##  set from `uname -m` so the static lib matches the running R
    ##  arch (x86_64 or arm64).
    ##
    ##  The block is no-op on Linux/other Unixes.
    OSX_CMAKE_ARGS=""
    case "$(uname -s)" in
        Darwin)
            OSX_ARCH=$(uname -m)
            OSX_DEPLOY=$(printf '%s' "$R_CFLAGS $R_CXXFLAGS" \
                | sed -n 's/.*-mmacosx-version-min=\([0-9.]*\).*/\1/p' \
                | head -n1)
            OSX_CMAKE_ARGS="-DCMAKE_OSX_ARCHITECTURES=$OSX_ARCH"
            if [ -n "$OSX_DEPLOY" ]; then
                OSX_CMAKE_ARGS="$OSX_CMAKE_ARGS -DCMAKE_OSX_DEPLOYMENT_TARGET=$OSX_DEPLOY"
            fi
            ;;
    esac

    ## Portable parallel-job count
    ##
    ## details:
    ##  `nproc` is GNU coreutils and absent on macOS by default.
    ##  Without inherit_errexit the failed substitution silently
    ##  produced an empty `-j` argument, which Make tolerates but
    ##  Ninja rejects. Use getconf (POSIX) which is present on
    ##  both Linux and macOS, with a safe fallback.
    if command -v getconf >/dev/null 2>&1; then
        NJOBS=$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 2)
    elif command -v nproc >/dev/null 2>&1; then
        NJOBS=$(nproc 2>/dev/null || echo 2)
    else
        NJOBS=2
    fi
    if [ "${NJOBS:-2}" -gt 2 ]; then
        NJOBS=$((NJOBS - 2))
    else
        NJOBS=1
    fi

    if command -v "${CMAKE}" >/dev/null 2>&1; then
        printf '%s Configure and generate TA-Lib\n' "$(info)"

        ${CMAKE} -S "$TALIB" -B "${BUILDLOCATION}" \
            -Wno-dev --log-level=NOTICE \
            -DCMAKE_BUILD_TYPE=Release \
            -DCMAKE_INSTALL_PREFIX="$TARGET" \
            -DBUILD_SHARED_LIBS=OFF \
            -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
            -DCMAKE_C_COMPILER="$R_CC" \
            -DCMAKE_CXX_COMPILER="$R_CXX" \
            -DCMAKE_C_FLAGS="$R_CC_EXTRA $R_CFLAGS $R_CPPFLAGS -w -fPIC ${OPTFLAGS}" \
            -DCMAKE_CXX_FLAGS="$R_CXX_EXTRA $R_CXXFLAGS $R_CPPFLAGS -w -fPIC ${OPTFLAGS}" \
            -DCMAKE_EXE_LINKER_FLAGS="$R_LDFLAGS" \
            -DCMAKE_SHARED_LINKER_FLAGS="$R_LDFLAGS" \
            $OSX_CMAKE_ARGS \
            -DBUILD_DEV_TOOLS=OFF

        printf '%s\n' "$(success)"

        printf '%s Build and install TA-Lib\n' "$(info)"

        ${CMAKE} --build "${BUILDLOCATION}" --target install -- -j"$NJOBS"

        printf '%s\n' "$(success)"
    fi

fi

## Makevars
## 
## details:
##  this is probably the most important step, if done
##  incorrectly the uptream build wont be linked to R
printf '%s Constructing %s and %s' "$(info)" "$(var PKG_CFLAGS)" "$(var PKG_LIBS)"
CFLAGS=""
if [ $PREINSTALLED -eq 0 ]; then
    ## details:
    ##  the pkg-config probe above already captured the cflags and
    ##  libs and proved they link. Re-running pkg-config here would
    ##  duplicate that work and could disagree with what we tested.
    CFLAGS="$TALIB_CFLAGS"
    PKG_LIBS="$TALIB_LIBS"
else
    CFLAGS="-I${TARGET##*/src/}/include -I${TARGET##*/src/}/include/ta-lib"
    SEARCH_DIR="$TALIB/local/lib/"

    ## details
    ##  find the first *.a file to be linked
    ##  to R
    PKG_LIBS=$( find "$SEARCH_DIR" -maxdepth 1 -type f -name '*.a' | head -n 1 )

    if [ -z "$PKG_LIBS" ]; then
       printf ' %s\n' "$(warning)"
        echo $(error) Could not find static libraries in $SEARCH_DIR.
        echo This is a bug, please submit a issue here: $(url https://github.com/serkor1/ta-lib-R)
        exit 1
    fi
    PKG_LIBS="${PKG_LIBS##*src/} -lm"
fi

## Opt-in strict warnings for the R wrapper compile step.
## Triggered by 'make check' via TALIB_STRICT_WARNINGS=1.
## Flags are appended to PKG_CFLAGS only, NOT to the CMake
## build of vendored ta-lib (which keeps -w) — this prevents
## ta-lib's own code from drowning the log.
if [ "${TALIB_STRICT_WARNINGS:-0}" = "1" ]; then
    WARN_FLAGS="-Wall -Wpedantic -Wextra -Wno-unused-parameter -Wno-cast-function-type"
    printf '%s Strict warnings enabled: %s\n' "$(info)" "$WARN_FLAGS"
else
    WARN_FLAGS=""
fi

CFLAGS="$CFLAGS $OPTFLAGS $WARN_FLAGS"
printf ' %s\n' "$(success)"

printf '%s Constructing %s' "$(info)" "$(var Makevars)"

cat > src/Makevars <<EOF
# autogenerated by configure
PKG_CFLAGS = $CFLAGS
PKG_LIBS   = $PKG_LIBS
EOF

printf ' %s\n' "$(success)"

echo
echo Configure status $(success)
echo
