#! /bin/sh
#
# Giraffe Library configure script
# This script generates config.mk that is included by Makefile.
#
# Copyright (C) 2015, 2020-2021, 2024-2025 Phil Clayton <phil.clayton@veonix.com>
#
# This file is part of the Giraffe Library runtime.  For your rights to use
# this file, see the file 'LICENCE.RUNTIME' distributed with Giraffe Library
# or visit <http://www.giraffelibrary.org/licence-runtime.html>.


################################################################################
# Portable support for maps
#
# Associative arrays are not available in (at least) Dash or Bash v3.

size ()
{
  # size is the number of newline characters

  wc -l
}

empty ()
{
  # a dummy character is added as a terminator to avoid a trailing newline
  # (that would be removed by command substitution)

  printf "-"
}

prepend ()
{
  # $1 has the format <key>=<value> where <key> does not contain '='

  printf "%s\n" "$1" ; cat -
}

lookup ()
{
  # $1 has the form <key>

  if [ $# -eq 0 ]
  then
    echo "lookup: argument required"
    exit 1
  fi

  grep "^$1=" | sed -e "s/^$1=//"
}

keys ()
{
  sed -n -e "s/^\([^=]*\)=.*$/\1/p"
}

values ()
{
  sed -n -e "s/^[^=]*=\(.*\)$/\1/p"
}


################################################################################
# Process arguments

# Use GIRAFFEHOME for installation directory if it is defined
DEFAULT_PREFIX="/opt/giraffe"
PREFIX=${GIRAFFEHOME:-"${DEFAULT_PREFIX}"}

SHOWHELP=
WITH_MLTON=
WITH_POLYML=

while test $# != 0
do
  case "$1" in
  --prefix)
    shift
    if [ -n "$1" ]
    then
      PREFIX="$1"
    else
      echo "error: argument expected after --prefix"
      SHOWHELP=1
    fi
    shift
    ;;
  --without-mlton)
    WITH_MLTON=N
    shift
    ;;
  --without-polyml)
    WITH_POLYML=N
    shift
    ;;
  --with-mlton)
    WITH_MLTON=Y
    shift
    ;;
  --with-polyml)
    WITH_POLYML=Y
    shift
    ;;
  --mlton-prefix)
    shift
    if [ -n "$1" ]
    then
      MLTON_PREFIX="$1"
    else
      echo "error: argument expected after --mlton-prefix"
      SHOWHELP=1
    fi
    shift
    ;;
  --mlton-bindir)
    shift
    if [ -n "$1" ]
    then
      MLTON_BINDIR="$1"
    else
      echo "error: argument expected after --mlton-bindir"
      SHOWHELP=1
    fi
    shift
    ;;
  --mlton-includedir)
    shift
    if [ -n "$1" ]
    then
      MLTON_INCLUDEDIR="$1"
    else
      echo "error: argument expected after --mlton-includedir"
      SHOWHELP=1
    fi
    shift
    ;;
  --polyml-prefix)
    shift
    if [ -n "$1" ]
    then
      POLYML_PREFIX="$1"
    else
      echo "error: argument expected after --polyml-prefix"
      SHOWHELP=1
    fi
    shift
    ;;
  --polyml-bindir)
    shift
    if [ -n "$1" ]
    then
      POLYML_BINDIR="$1"
    else
      echo "error: argument expected after --polyml-bindir"
      SHOWHELP=1
    fi
    shift
    ;;
  --polyml-libdir)
    shift
    if [ -n "$1" ]
    then
      POLYML_LIBDIR="$1"
    else
      echo "error: argument expected after --polyml-libdir"
      SHOWHELP=1
    fi
    shift
    ;;
  --help)
    SHOWHELP=${SHOWHELP:-0}
    shift
    ;;
  *)
    echo "error: unrecognized option \"$1\""
    SHOWHELP=1
    shift
    ;;
  esac
done

if [ -n "$SHOWHELP" ]
then
  cat << EOF

Usage: $0 [OPTION]...

Installation directories:
  --prefix PREFIX         install to directory PREFIX
                          By default, install to GIRAFFEHOME, if set
                           and ${DEFAULT_PREFIX}, otherwise.

Compilers:
  --without-COMPILER      exclude support for COMPILER
  --with-COMPILER         include support for COMPILER
                          COMPILER is either 'mlton' or 'polyml'.
                          By default, install support for all compilers
                           either specified (using compiler directory options)
                           or found (using PATH or pkg-config).

Compiler directories:
  --mlton-prefix DIR      MLton home directory
  --mlton-bindir DIR      MLton bin directory
  --mlton-includedir DIR  MLton include directory
  --polyml-prefix DIR     Poly/ML home directory
  --polyml-bindir DIR     Poly/ML bin directory
  --polyml-libdir DIR     Poly/ML lib directory

Configuration:
  --help                  display this help and exit

EOF
  exit $SHOWHELP
fi

# Check for required utilities

if ! command -v which > /dev/null
then
  echo
  echo "error: 'which' command not found"
  exit 1
fi

pathchk=$(command which pathchk 2> /dev/null)
if [ -z "${pathchk}" ]
then
  pathchk=":"
fi

pkgconfig=$(command which pkg-config 2> /dev/null)
if [ -z "${pkgconfig}" ]
then
  echo
  echo "error: 'pkg-config' command not found"
  exit 1
fi

# Check that PREFIX is absolute and contains a usable path.
case "${PREFIX}" in
(/*)
  if ! pathchk "${PREFIX}"
  then
    echo
    echo "error: installation directory \"${PREFIX}\" is not a usable path"
    exit 1
  fi
  ;;
(*)
  echo
  echo "error: installation directory \"${PREFIX}\" is not absolute (does not start with a '/')"
  exit 1
  ;;
esac

# Check that PREFIX does not contain whitespace because such a path does not
# appear to work as a value for the MLton option '-mlb-path-var name value'.
if echo "${PREFIX}" | grep "[[:space:]]" > /dev/null
then
  echo
  echo "error: installation directory \"${PREFIX}\" contains whitespace"
  echo
  echo "The installation directory must not contain whitespace because such a path does not work as a value for the MLton option '-mlb-path-var name value'"
  exit 1
fi


################################################################################
# Check SML compilers

MIN_MLTON_VERSION="20180207"
MIN_POLYML_VERSION="5.6"

echo
echo "== Checking SML compilers =="


# MLton

find_mlton_includedir ()
{
  MLTON_PREFIX="$1"
  if [ -d "${MLTON_PREFIX}/lib/include" ]
  then
    MLTON_INCLUDEDIR="${MLTON_PREFIX}/lib/include"
  elif [ -d "${MLTON_PREFIX}/lib/mlton/include" ]
  then
    MLTON_INCLUDEDIR="${MLTON_PREFIX}/lib/mlton/include"
  elif [ -d "${MLTON_PREFIX}/lib64/include" ]
  then
    MLTON_INCLUDEDIR="${MLTON_PREFIX}/lib64/include"
  elif [ -d "${MLTON_PREFIX}/lib64/mlton/include" ]
  then
    MLTON_INCLUDEDIR="${MLTON_PREFIX}/lib64/mlton/include"
  fi
}

if test "X${WITH_MLTON}" != "XN"  # check for MLton if not excluded
then
  printf "Checking MLton... "

  # check for user-specified directories
  if [ -n "${MLTON_PREFIX}" ] || [ -n "${MLTON_BINDIR}" ] || [ -n "${MLTON_INCLUDEDIR}" ]
  then
    MLTON_VERSION_EXPECTED=
    echo "using user-specified directories"

    HAVE_MLTON="1"
    MLTON_BINDIR="${MLTON_BINDIR:-${MLTON_PREFIX%/}/bin}"
    if [ -n "${MLTON_PREFIX}" ] && [ -z "${MLTON_INCLUDEDIR}" ]
    then
      find_mlton_includedir "${MLTON_PREFIX%/}"
    fi

    if [ -z "${MLTON_BINDIR}" ]
    then
      echo "error: MLton bin directory not determined (either bin or prefix directory must be specified)"
      exit 1
    fi
    if [ -z "${MLTON_INCLUDEDIR}" ]
    then
      echo "error: MLton include directory not determined (either include or prefix directory must be specified)"
      exit 1
    fi

    echo "  bin directory:     ${MLTON_BINDIR}"
    echo "  include directory: ${MLTON_INCLUDEDIR}"

  else
    # check in PATH
    MLTON=$(which mlton 2> /dev/null)

    if [ -n "${MLTON}" ]
    then
      MLTON_VERSION_EXPECTED=
      echo "found using PATH"

      HAVE_MLTON="1"
      MLTON_BINDIR=$(dirname "${MLTON}" 2> /dev/null)
      MLTON_PREFIX=$(dirname "${MLTON_BINDIR}" 2> /dev/null)
      find_mlton_includedir "${MLTON_PREFIX}"

      if [ -z "${MLTON_INCLUDEDIR}" ]
      then
        echo "error: MLton include directory not determined (either include or prefix directory must be specified)"
        exit 1
      fi

      echo "  bin directory:     ${MLTON_BINDIR}"
      echo "  include directory: ${MLTON_INCLUDEDIR}"

    else
      # give up
      echo "NOT FOUND (using PATH)"
      HAVE_MLTON=
    fi
  fi
else
  echo "Not checking MLton"
  HAVE_MLTON=
fi

# check directories
if [ -n "${HAVE_MLTON}" ]
then
  MLTON_MLTON="${MLTON_BINDIR%/}/mlton"

  if [ ! -d "${MLTON_BINDIR}" ]
  then
    echo "error: MLton bin directory ${MLTON_BINDIR} does not exist"
    exit 1
  fi
  if [ ! -x "${MLTON_MLTON}" ]
  then
    echo "error: MLton executable file ${MLTON_MLTON} does not exist"
    exit 1
  fi

  if [ ! -d "${MLTON_INCLUDEDIR}" ]
  then
    echo "error: MLton include directory ${MLTON_INCLUDEDIR} does not exist"
    exit 1
  fi

  printf "  version:           "
  if MLTON_VERSION=$(${MLTON_MLTON} | cut -d " " -f2)
  then
    printf "%s" "${MLTON_VERSION}"
  else
    echo
    echo "error: failed to run ${MLTON_MLTON}"
    exit 1
  fi

  if [ -n "${MLTON_VERSION_EXPECTED}" ]
  then
    if test "${MLTON_VERSION}" != "${MLTON_VERSION_EXPECTED}"
    then
      echo
      echo "error: mlton version does not match expected version ${MLTON_VERSION_EXPECTED}"
      exit 1
    fi
  fi

  if test "${MLTON_VERSION}" \< "${MIN_MLTON_VERSION}"
  then
    echo " < ${MIN_MLTON_VERSION} (REQUIRED VERSION)"
    echo "error: MLton version ${MIN_MLTON_VERSION} or later is required"
    exit 1
  else
    echo " >= ${MIN_MLTON_VERSION}"
  fi

  MLTON_VERSION_TEXT="${MLTON_VERSION}"
else
  MLTON_VERSION_TEXT="NOT AVAILABLE"
fi


# Poly/ML

# find directories
if test "X${WITH_POLYML}" != "XN"  # check for Poly/ML if not excluded
then
  printf "Checking Poly/ML... "

  # check for user-specified directories
  if [ -n "${POLYML_PREFIX}" ] || [ -n "${POLYML_BINDIR}" ] || [ -n "${POLYML_LIBDIR}" ]
  then
    POLYML_VERSION_EXPECTED=
    echo "using user-specified directories"

    HAVE_POLYML="1"
    POLYML_BINDIR="${POLYML_BINDIR:-${POLYML_PREFIX%/}/bin}"
    POLYML_LIBDIR="${POLYML_LIBDIR:-${POLYML_PREFIX%/}/lib}"
    POLYML_INCLUDEDIR="${POLYML_INCLUDEDIR:-${POLYML_PREFIX%/}/include}"  # not used

    if [ -z "${POLYML_BINDIR}" ]
    then
      echo "error: Poly/ML bin directory not determined (either bin or prefix directory must be specified)"
      exit 1
    fi
    if [ -z "${POLYML_LIBDIR}" ]
    then
      echo "error: Poly/ML lib directory not determined (either lib or prefix directory must be specified)"
      exit 1
    fi

    echo "  bin directory:     ${POLYML_BINDIR}"
    echo "  lib directory:     ${POLYML_LIBDIR}"

  # check with pkg-config
  elif "${pkgconfig}" --exists polyml
  then
    POLYML_VERSION_EXPECTED=$("${pkgconfig}" --modversion polyml)
    echo "found version ${POLYML_VERSION_EXPECTED} using pkg-config"

    HAVE_POLYML="1"
    POLYML_BINDIR=$("${pkgconfig}" --variable=bindir polyml)
    POLYML_LIBDIR=$("${pkgconfig}" --variable=libdir polyml)
    POLYML_INCLUDEDIR=$("${pkgconfig}" --variable=includedir polyml)  # not used

    echo "  bin directory:     ${POLYML_BINDIR}"
    echo "  lib directory:     ${POLYML_LIBDIR}"

  else
    # check in PATH
    WHICH_POLY=$(which poly 2> /dev/null)

    if [ -n "${WHICH_POLY}" ]
    then
      POLYML_VERSION_EXPECTED=
      echo "found using PATH"

      HAVE_POLYML="1"
      POLYML_BINDIR=$(dirname "${WHICH_POLY}" 2> /dev/null)
      POLYML_PREFIX=$(dirname "${POLYML_BINDIR}" 2> /dev/null)
      POLYML_LIBDIR="${POLYML_PREFIX}/lib"

      echo "  bin directory:     ${POLYML_BINDIR}"
      echo "  lib directory:     ${POLYML_LIBDIR}"

    else
      # give up
      echo "NOT FOUND (using PKG_CONFIG_PATH or PATH)"
      HAVE_POLYML=
    fi
  fi

else
  echo "Not checking Poly/ML"
  HAVE_POLYML=
fi

# check directories
if [ -n "${HAVE_POLYML}" ]
then
  POLYML_POLY="${POLYML_BINDIR%/}/poly"
  POLYML_POLYC="${POLYML_BINDIR%/}/polyc"

  if [ ! -d "${POLYML_BINDIR}" ]
  then
    echo "error: Poly/ML bin directory ${POLYML_BINDIR} does not exist"
    exit 1
  fi
  if [ ! -x "${POLYML_POLY}" ]
  then
    echo "error: Poly/ML executable file ${POLYML_POLY} does not exist"
    exit 1
  fi

  if [ ! -d "${POLYML_LIBDIR}" ]
  then
    echo "error: Poly/ML lib directory ${POLYML_LIBDIR} does not exist"
    exit 1
  fi

  printf "  version:           "
  if POLYML_VERSION=$(${POLYML_POLY} -v | cut -d " " -f2)
  then
    printf "%s" "${POLYML_VERSION}"
  else
    echo
    echo "error: failed to run ${POLYML_POLY}"
    exit 1
  fi

  if [ -n "${POLYML_VERSION_EXPECTED}" ]
  then
    if test "${POLYML_VERSION}" != "${POLYML_VERSION_EXPECTED}"
    then
      echo
      echo "error: poly version does not match expected version ${POLYML_VERSION_EXPECTED}"
      exit 1
    fi
  fi

  A1=$(echo "${POLYML_VERSION}." | cut -d "." -f 1)
  AS=$(echo "${POLYML_VERSION}." | cut -d "." -f 2-)
  B1=$(echo "${MIN_POLYML_VERSION}." | cut -d "." -f 1)
  BS=$(echo "${MIN_POLYML_VERSION}." | cut -d "." -f 2-)
  while [ -n "${A1}" ] && [ "${A1}" -eq "${B1}" ]
  do
    A1=$(echo "${AS}" | cut -d "." -f 1)
    AS=$(echo "${AS}" | cut -d "." -f 2-)
    B1=$(echo "${BS}" | cut -d "." -f 1)
    BS=$(echo "${BS}" | cut -d "." -f 2-)
  done
  if [ -n "${B1}" ] && [ 0 -lt "${B1}" ] && { [ -z "${A1}" ] || [ "${A1}" -lt "${B1}" ] ; }
  then
    echo " < ${MIN_POLYML_VERSION} (REQUIRED VERSION)"
    echo "error: Poly/ML version ${MIN_POLYML_VERSION} or later is required"
    exit 1
  else
    echo " >= ${MIN_POLYML_VERSION}"
  fi

  if [ ! -x "${POLYML_POLYC}" ]
  then
    echo "error: Poly/ML executable file ${POLYML_POLYC} does not exist"
    exit 1
  fi

  POLYML_VERSION_TEXT="${POLYML_VERSION}"
else
  POLYML_VERSION_TEXT="NOT AVAILABLE"
fi


################################################################################
# Check libraries

echo
echo "== Checking libraries =="

ALL_LIB_NAMES=$(empty)
SML_LIB_NAMES=$(empty)
POLYML_LIB_NAMES=$(empty)
MLTON_LIB_NAMES=$(empty)
EXCLUDED_LIB_NAMES=$(empty)
HAVE_OLDER_LIBS=

for DIR in src/*-[0-9]*
do
  if [ -f "${DIR}/package" ]
  then
    PKGNAME=$(head -n 1 "${DIR}/package")
  else
    PKGNAME=
    echo
    echo "error: ${DIR}/package not found"
  fi

  if [ -f "${DIR}/version" ]
  then
    VERSION=$(cat "${DIR}/version")
  else
    VERSION=
  fi

  NAME=$(basename "${DIR}")

  if [ -n "${PKGNAME}" ]
  then
    printf "Checking %s... " "${NAME}"

    if "${pkgconfig}" --exists "${PKGNAME}"
    then
      MODVERSION=$("${pkgconfig}" --modversion "${PKGNAME}")
      echo "found version ${MODVERSION}"

      USE_LIB=
      if [ -n "${VERSION}" ]
      then
        # check installed version is new enough
        if "${pkgconfig}" --atleast-version="${VERSION}" "${PKGNAME}"
        then
          REL=">="
        else
          REL="<"
          HAVE_OLDER_LIBS="1"
        fi
        ALL_LIB_VERSION=$(printf "%-16s%-8s%s" "${MODVERSION}" "${REL}" "${VERSION}")
        ALL_LIB_NAMES=$(echo "${ALL_LIB_NAMES}" | prepend "${NAME}=${ALL_LIB_VERSION}")
        USE_LIB=Y
      else
        # no version check, just use it
        echo
        ALL_LIB_NAMES=$(echo "${ALL_LIB_NAMES}" | prepend "${NAME}=${MODVERSION}")
        USE_LIB=Y
      fi

      if [ -n "${USE_LIB}" ]
      then
        SML_LIB_NAMES=$(echo "${SML_LIB_NAMES}" | prepend "${NAME}=${MODVERSION}")

        if [ -n "${HAVE_MLTON}" ] && [ -f "src/mlton/giraffe-${NAME}.c" ]
        then
          MLTON_LIB_NAMES=$(echo "${MLTON_LIB_NAMES}" | prepend "${NAME}=")
        fi
        if [ -n "${HAVE_POLYML}" ] && [ -f "src/polyml/giraffe-${NAME}.c" ]
        then
          POLYML_LIB_NAMES=$(echo "${POLYML_LIB_NAMES}" | prepend "${NAME}=")
        fi
      fi
    else
      echo "NOT FOUND"
      ALL_LIB_NAMES=$(echo "${ALL_LIB_NAMES}" | prepend "${NAME}=NOT AVAILABLE")
      EXCLUDED_LIB_NAMES=$(echo "${EXCLUDED_LIB_NAMES}" | prepend "${NAME}=")
    fi
  else
    # always include if no package dependency
    SML_LIB_NAMES=$(echo "${SML_LIB_NAMES}" | prepend "${NAME}=0")
  fi
done


################################################################################
# Check configuration consistency

echo

if test -z "${HAVE_MLTON}" -a -z "${HAVE_POLYML}"
then
  echo "error: no Standard ML compilers found"
  exit 1
fi

if test "X${WITH_MLTON}" = "XY" -a -z "${HAVE_MLTON}"
then
  echo "error: MLton required but not found"
  exit 1
fi

if test "X${WITH_POLYML}" = "XY" -a -z "${HAVE_POLYML}"
then
  echo "error: Poly/ML required but not found"
  exit 1
fi


################################################################################
# Summary

echo
echo "Configuration summary"
echo
echo "  Installation directory    ${PREFIX}"
echo
echo "  Standard ML compilers   Using version"
if test "X${WITH_MLTON}" != "XN"
then
  printf "    %-24s%s\n" "MLton" "${MLTON_VERSION_TEXT}"
fi
if test "X${WITH_POLYML}" != "XN"
then
  printf "    %-24s%s\n" "Poly/ML" "${POLYML_VERSION_TEXT}"
fi
echo
echo "  Libraries               Using version           Giraffe Library API version"
echo "${ALL_LIB_NAMES}" | keys | sort |
  while read -r NAME
  do
    VERSION=$(echo "${ALL_LIB_NAMES}" | lookup "${NAME}")
    printf "    %-24s%s\n" "${NAME}" "${VERSION}"
  done
if test -n "${HAVE_OLDER_LIBS}"
then
  echo
  cat << EOF
Note

  Some libraries are older than the version on which the Giraffe Library API
  is based and may be missing parts of the API.  Even so, Giraffe Library can
  be installed and used to build applications that depend only on parts of the
  API provided by the older libraries.  An application that depends on parts
  of the API missing from the older libraries will still compile but will fail
  to link.

  If these libraries are subsequently updated, there is no need to re-install
  Giraffe Library.
EOF
fi
echo


################################################################################
# Write config.mk

OUTFILE="config.mk"

# Try to clear ${OUTFILE}
if ! (true > "${OUTFILE}") 2> /dev/null
then
  echo "cannot write to ${OUTFILE}"
  exit 1
fi

print_names () {
  while read -r NAME
  do
    printf " %s" "${NAME}"
  done
  printf "\n"
}

{
  printf "PREFIX := %s\n" "${PREFIX}"
  printf "SML_LIB_NAMES :=" ; echo "${SML_LIB_NAMES}" | keys | sort | print_names
  printf "EXCLUDED_LIB_NAMES :=" ; echo "${EXCLUDED_LIB_NAMES}" | keys | sort | print_names
  printf "MLTON_VERSION := %s\n" "${MLTON_VERSION}"
  if [ -n "${HAVE_MLTON}" ]
  then
    printf "MLTON_LIB_NAMES :=" ; echo "${MLTON_LIB_NAMES}" | keys | sort | print_names
    printf "MLTON_MLTON := %s\n" "${MLTON_MLTON}"
    printf "MLTON_BINDIR := %s\n" "${MLTON_BINDIR}"
    printf "MLTON_INCLUDEDIR := %s\n" "${MLTON_INCLUDEDIR}"
  fi
  printf "POLYML_VERSION := %s\n" "${POLYML_VERSION}"
  if [ -n "${HAVE_POLYML}" ]
  then
    printf "POLYML_LIB_NAMES :=" ; echo "${POLYML_LIB_NAMES}" | keys | sort | print_names
    printf "POLYML_POLY := %s\n" "${POLYML_POLY}"
    printf "POLYML_POLYC := %s\n" "${POLYML_POLYC}"
    printf "POLYML_BINDIR := %s\n" "${POLYML_BINDIR}"
    printf "POLYML_LIBDIR := %s\n" "${POLYML_LIBDIR}"
  fi
} >> "${OUTFILE}"
