#!/usr/bin/env bash


function glibc () {(

  # a dispatching function for
  local mode="${1}" ; shift
  case "${mode}" in
    ## 1. detecting the glibc version
    "detect" )
      glibc-detect "${@}"
    ;;
    ## 2. checking a pair of glibc versions for compatibility
    "check" )
      glibc-check "${@}"
    ;;
    "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
    ## 1. "env" - unpack the value of a named variable
    ### e.g. 'glibc-detect "env" "c_stdlib_version"'
    #### returns the value of "c_stdlib_version"
    #### it is equivalent to echo "${c_stdlib_version}"
    "env" )
      local named_variable="${1}"
      echo "${!named_variable}"
    ;;
    ## 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"
        return 0
      fi
      if [[ "${first}" == "${sorted[0]}" ]] ; then
        echo "false"
        return 1
      else
        echo "true"
        return 0
      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
        echo "true"
        return 0
      fi
      if [[ "${first}" == "${sorted[0]}" ]] ; then
        echo "false"
        return 1
      else
        echo "true"
        return 0
      fi
    ;;
    ## 3. "system" - check if the system glibc version is compatible with a library
    ### e.g. 'glibc-check "system" "/path/to/library"'
    #### returns 0 if the system glibc version is compatible with the library
    #### returns 1 if the system glibc version is not compatible with the library
    "system" )
      glibc-check "status" \
        "$(glibc-detect "system")" "${@}"
    ;;
    ## 4. "library" - check if a library glibc version is compatible with a library
    ### for each detected glibc symbol in library, compare with the input version of glibc
    ### e.g. 'glibc-check "library" "2.17" "/path/to/library"'
    #### returns 0 if the library glibc version is compatible with the input version ("2.17")
    "library" )
      local version
      local glibc_version="${1}" ; shift
      while read -r version ; do
        local -a command=(
          "glibc-check" "status"
          "${glibc_version}" "${version}"
        )
        if "${command[@]}" ; then
          return 0
        else
          return 1
        fi
      done < <(glibc-detect "library" "${@}")
    ;;
    "env:library" )
      local named_variable="${1}" ; shift
      glibc-check "library" "${!named_variable}" "${@}"
    ;;
  esac
)}
