#!/usr/bin/env bash


function glibc () {(

  # a dispatching function for
  local mode="${1}" ; shift
  case "${mode}" in
    "version-normalize" )
      local version="${1}" ; shift
      IFS="." read -ra components <<< "${version}"
      while [[ "${#components[@]}" -lt "3" ]] ; do
        components+=("0")
      done
      version="$(printf "%s." "${components[@]}")"
      version="${version%"."*}"
      if [[ -n "${version}" ]] ; then
        echo "${version}"
      fi
    ;;
  esac
)}

function glibc-detect () {(
  # a dispatching function for detecting the glibc version
  local mode="${1}" ; shift
  ## modes:
  case "${mode}" in
    ## 2. "req" - detect the glibc version required by a library
    ### e.g. 'glibc-detect "req" "/path/to/library"'
    #### returns the minimum required version of glibc for the library
    "req" )
      glibc-detect "library" "${@}" |
      head --lines=1
    ;;
    ## 3. "system" - detect the version of glibc on the system
    ### e.g. 'glibc-detect "system"'
    #### returns the version of glibc on the system as reported by 'ldd --version'
    "system" )
      ldd --version |
      awk 'NR==1 {print $NF}'
    ;;
    ## 4. "library" - detect all versions of glibc in a library
    ### e.g. 'glibc-detect "library" "/path/to/library"'
    #### returns all versions of glibc in the symbol table of the library
    "library" )
      local version
      local library="${1}"
      if [[ -f "${library}" ]] ; then
        readelf -W --syms "${library}" |
        grep "@GLIBC_" |
        sed -n 's/.*@GLIBC_\([0-9.]*\).*/\1/p' |
        sort -Vur | while read -r version ; do
          glibc version-normalize "${version}"
        done
      fi
    ;;
  esac
)}

function glibc-check () {(
  # a dispatching function for checking a pair of glibc versions for compatibility
  local mode="${1}" ; shift
  ## modes:
  case "${mode}" in
    ## 1. "compatible" - check if two glibc versions are compatible
    ### to accomplish this, the function sorts the two versions
    ### and compares the version provided by "first" with the first
    ### element of the sorted array.
    ### if they are equal: the versions are compatible -- return 0, and echo "true"
    ### if the "first" version is first, the version stored in "first" is older -- return 1, and echo "false"
    ### if the "first" version is second, the version stored in "first" is newer -- return 0, and echo "true"
    ### e.g. 'glibc-check "compatible" "2.17" "2.18"' returns 1, and echoes "false" --
    #### the provider for "second" is not compatible with the provider for "first"
    "compatible" )
      local first second
      local -a sorted
      first="$(glibc version-normalize "${1}")" ; shift
      second="$(glibc version-normalize "${1}")" ; shift
      mapfile -t sorted < <(
        printf "%s\n" "${first}" "${second}" |
        sort -Vr
      )
      if [[ "${first}" == "${second}" ]] ; then
        echo "true"
      fi
      if [[ "${first}" == "${sorted[0]}" ]] ; then
        echo "false"
      else
        echo "true"
      fi
    ;;
    ## 2. "status" - check if two glibc versions are compatible
    ### performs the same operation as (1) but does not echo the boolean statement
    "status" )
      local first second
      local -a sorted
      first="$(glibc version-normalize "${1}")" ; shift
      second="$(glibc version-normalize "${1}")" ; shift
      mapfile -t sorted < <(
        printf "%s\n" "${first}" "${second}" |
        sort -Vr
      )
      if [[ "${first}" == "${second}" ]] ; then
        return 0
      fi
      if [[ "${first}" == "${sorted[0]}" ]] ; then
        return 1
      else
        return 0
      fi
    ;;
  esac
)}

# Argument validation check
if [ "$#" -lt 1 ]; then
    echo "$0: checks whether a conda recipe sets a valid glibc requirement"
    echo ""
    echo "Usage: $0 <file1> <file2> <file3> ..."
    echo ""
    echo "Provide a list of files to check. You can have bash do a glob expansion. Requires environment variable 'c_stdlib_version' is set to the version used by the recipe."
    exit 1
fi

# Check if any of the arguments are valid files
valid_files=0
for file in "$@"; do
    if [[ -f "$file" && ! -L "$file" ]]; then
        valid_files=1
        break
    fi
done

if [ $valid_files -eq 0 ]; then
    echo "No valid files found to check. Exiting."
    exit 0
fi

SYSTEM_GLIBC_VERSION="$(glibc-detect system)"
RECIPE_GLIBC_VERSION="${c_stdlib_version:=0.0.0}"

echo "Detected the system's glibc as ${SYSTEM_GLIBC_VERSION}"

if [ "$c_stdlib_version" = "0.0.0" ]; then
    echo "c_stdlib_version is not set!"
    exit 1
fi

for file in "$@"; do
    if [[ -f "$file" && ! -L "$file" ]]; then  # Ensure it's a file and not a link
      BINARY_GLIBC_VERSION="$(glibc-detect req "$file")"
      if [[ -n "$BINARY_GLIBC_VERSION" ]]; then
        echo "binary glibc ${BINARY_GLIBC_VERSION} <= recipe glibc ${RECIPE_GLIBC_VERSION} $file"
        BINARY_IS_COMPATIBLE="$(glibc-check compatible "$BINARY_GLIBC_VERSION" "$RECIPE_GLIBC_VERSION")"
        if [[ $BINARY_IS_COMPATIBLE == "false" ]] ; then
          echo "The binary is not compatible with the recipe's glibc pinning."
          exit 1
        fi
        echo "The binary is compatible."
      else
        echo "No GLIBC symbols found in $file, skipping..."
      fi
    else
      echo "Skipping $file (not a regular file or is a symlink)"
    fi
done
