#! /bin/sh
#
# Giraffe Library configure script
# This script generates config.mk that is included by Makefile.
#
# Copyright (C) 2015, 2020 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 "$1\n" ; 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 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 that pkg-config is present
if ! which pkg-config > /dev/null 2>&1
then
  echo
  echo "error: pkg-config command not found"
  exit 1
fi


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

MIN_MLTON_VERSION="20130715"
MIN_POLYML_VERSION="5.5.2"

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 "${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 20130715 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 pkg-config --exists polyml
  then
    POLYML_VERSION_EXPECTED=`pkg-config --modversion polyml`
    echo "found version ${POLYML_VERSION_EXPECTED} using pkg-config"

    HAVE_POLYML="1"
    POLYML_BINDIR=`pkg-config --variable=bindir polyml`
    POLYML_LIBDIR=`pkg-config --variable=libdir polyml`
    POLYML_INCLUDEDIR=`pkg-config --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 "${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`

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 ${NAME}... "

    if pkg-config --exists "${PKGNAME}"
    then
      MODVERSION=`pkg-config --modversion "${PKGNAME}"`
      printf "found version ${MODVERSION}"

      USE_LIB=
      if [ -n "${VERSION}" ]
      then
        # check installed version is new enough
        if pkg-config --atleast-version="${VERSION}" "${PKGNAME}"
        then
          echo " >= ${VERSION}"
          ALL_LIB_NAMES=`echo "${ALL_LIB_NAMES}" | prepend "${NAME}=${MODVERSION}"`
          USE_LIB=Y
        else
          echo " < ${VERSION} (GIRAFFE VERSION)"

          # for now, use old version but warn that build may fail
          ALL_LIB_NAMES=`echo "${ALL_LIB_NAMES}" \
                    | prepend "${NAME}=${MODVERSION} < ${VERSION} (GIRAFFE VERSION), BUILD MAY FAIL"`
          USE_LIB=Y
        fi
      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"`
    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 package version"
echo "${ALL_LIB_NAMES}" | keys | sort |
  while read NAME
  do
    VERSION=`echo "${ALL_LIB_NAMES}" | lookup "${NAME}"`
    printf "    %-24s%s\n" "${NAME}" "${VERSION}"
  done
echo


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

OUTFILE="config.mk"

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

write_line () { echo "$@" >> "${OUTFILE}" ; }

write_line "PREFIX := ${PREFIX}"
write_line "SML_LIB_NAMES :=" `echo "${SML_LIB_NAMES}" | keys | sort`
write_line "MLTON_VERSION = ${MLTON_VERSION}"
if [ -n "${HAVE_MLTON}" ]
then
  write_line "MLTON_LIB_NAMES :=" `echo "${MLTON_LIB_NAMES}" | keys | sort`
  write_line "MLTON_MLTON := ${MLTON_MLTON}"
  write_line "MLTON_BINDIR := ${MLTON_BINDIR}"
  write_line "MLTON_INCLUDEDIR := ${MLTON_INCLUDEDIR}"
fi
write_line "POLYML_VERSION := ${POLYML_VERSION}"
if [ -n "${HAVE_POLYML}" ]
then
  write_line "POLYML_LIB_NAMES :=" `echo "${POLYML_LIB_NAMES}" | keys | sort`
  write_line "POLYML_POLY := ${POLYML_POLY}"
  write_line "POLYML_POLYC := ${POLYML_POLYC}"
  write_line "POLYML_BINDIR := ${POLYML_BINDIR}"
  write_line "POLYML_LIBDIR := ${POLYML_LIBDIR}"
fi
