#!/bin/bash

usage() {
  cat << EOF >&2
usage: libtrixi-init-julia [-h] [-f]
                           [--julia-depot JULIA_DEPOT]
                           [--julia-exec JULIA_EXEC]
                           [--mpi-library MPI_LIBRARY]
                           [--t8code-library T8CODE_LIBRARY]
                           [--p4est-library P4EST_LIBRARY]
                           [--sc-library SC_LIBRARY]
                           [--skip-precompile]
                           PREFIX_OR_REPO

Set up all necessary project files in the current working directory and set appropriate
Julia preferences for using the system-installed MPI and t8code libraries. Furthermore,
this will install all dependencies in a local Julia depot folder (unless changed with the
'--julia-depot' flag).

The following files/folders will be created (or updated, if the '-f' flag is given):
- Project.toml
- Manifest.toml
- LocalPreferences.toml
- julia-depot

Note: Optional arguments with a value can also be set by defining the corresponding
environment variable prefixed by 'LIBTRIXI_'. For example, to use a Julia executable not on
the PATH, run this script as 'LIBTRIXI_JULIA_EXEC=julia-1.9.1 libtrixi-init-julia ...'.

positional arguments:
    PREFIX_OR_REPO  Path to install prefix of a libtrixi installation or, alternatively to
                    the root directory of a libtrixi clone. Note that the files at the
                    path must not be moved while libtrixi is used since that will break
                    library usage.

optional arguments:
    --help, -h      Show this help.

    --force, -f     By default, this tool does nothing if any of the files/folders to be
                    created already exist. With this option given on the command line, it
                    will continue anyways.

    --skip-precompile
                    Do not precompile Julia packages. This can be helpful, e.g., when
                    planning to add more packages manually.

    --julia-depot JULIA_DEPOT
                    Path to the Julia depot to be used, i.e., the folder where all Julia
                    packages, Julia cache files etc. are stored. (default: 'julia-depot')

    --julia-exec JULIA_EXEC
                    Path to Julia executable. (default: 'julia')

    --hdf5-library HDF5_LIBRARY
                    Path to the HDF5 shared library, i.e., something like
                    'path/to/libhdf5.so' or 'path/to/libhdf5.dylib'. If empty, Julia
                    will try to figure out the path automatically. (default: '')

    --mpi-library MPI_LIBRARY
                    Path to the MPI C shared library, i.e., something like
                    'path/to/libmpi.so' or 'path/to/libmpi.dylib'. If empty, Julia will
                    try to figure out the path automatically. (default: '')

    --t8code-library T8CODE_LIBRARY
                    Path to the t8code shared library, i.e., something like
                    'path/to/libt8.so' or 'path/to/libt8.dylib'. If empty, Julia will
                    try to figure out the path automatically. (default: '')

    --p4est-library P4EST_LIBRARY
                    Path to the p4est shared library, i.e., something like
                    'path/to/libp4est.so' or 'path/to/libp4est.dylib'. If omitted, it
                    will be assumed to be located next to the t8code library.
                    (default: '\$(dirname \$T8CODE_LIBRARY)/libp4est.<suffix>')

    --sc-library SC_LIBRARY
                    Path to the sc shared library, i.e., something like
                    'path/to/libsc.so' or 'path/to/libsc.dylib'. If omitted, it will be
                    assumed to be located next to the t8code library.
                    (default: '\$(dirname \$T8CODE_LIBRARY)/libsc.<suffix>')
EOF
}

die() {
  printf 'ERROR: %s\n' "$1" >&2
  if [ -n "$2" ]; then
    exit $2
  else
    exit 1
  fi
}

# Parse arguments (based on https://stackoverflow.com/a/14203146/1329844)
POSITIONAL_ARGS=()
USE_FORCE=0
SKIP_PRECOMPILE=0
if [ -z "$LIBTRIXI_JULIA_EXEC" ]; then
  LIBTRIXI_JULIA_EXEC=julia
fi
if [ -z "$LIBTRIXI_JULIA_DEPOT" ]; then
  # OBS! If you change this value here, you should also update the default value for
  # `default_depot_path` in `src/auxiliary.c` accordingly
  LIBTRIXI_JULIA_DEPOT=julia-depot
fi
while [[ $# -gt 0 ]]; do
  case "$1" in
    -h|--help)
      usage
      exit
      ;;
    -f|--force)
      USE_FORCE=1
      shift
      ;;
    --skip-precompile)
      SKIP_PRECOMPILE=1
      shift
      ;;
    --julia-exec)
      LIBTRIXI_JULIA_EXEC="$2"
      shift 2
      ;;
    --julia-depot)
      LIBTRIXI_JULIA_DEPOT="$2"
      shift 2
      ;;
    --hdf5-library)
      LIBTRIXI_HDF5_LIBRARY="$2"
      shift 2
      ;;
    --mpi-library)
      LIBTRIXI_MPI_LIBRARY="$2"
      shift 2
      ;;
    --t8code-library)
      LIBTRIXI_T8CODE_LIBRARY="$2"
      shift 2
      ;;
    --p4est-library)
      LIBTRIXI_P4EST_LIBRARY="$2"
      shift 2
      ;;
    --sc-library)
      LIBTRIXI_SC_LIBRARY="$2"
      shift 2
      ;;
    -*|--*)
      die "unknown option '$1' (use '-h' for usage information)"
      ;;
    *)
      POSITIONAL_ARGS+=("$1")
      shift
      ;;
  esac
done
set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters

# Verify usage
if [ $# -lt 1 ]; then
  echo "ERROR: missing 'PREFIX_OR_REPO' argument" >&2
  echo >&2
  usage
  exit 2
fi

# Check that Julia executable exists
if ! command -v $LIBTRIXI_JULIA_EXEC &> /dev/null; then
  die "Julia executable '$LIBTRIXI_JULIA_EXEC' not found" 2
fi
julia_exec="$LIBTRIXI_JULIA_EXEC"

# Save Julia depot path
julia_depot="$LIBTRIXI_JULIA_DEPOT"

# Determine platform shared library suffix
case "$(uname -s)" in
  Linux)
    shared_library_suffix=so
    ;;
  Darwin)
    shared_library_suffix=dylib
    ;;
  *)
    shared_library_suffix=so
    ;;
esac

# Check if libhdf5 was given by the user or try to find it using Julia
if [ -z "$LIBTRIXI_HDF5_LIBRARY" ]; then
  hdf5_libdir="$($julia_exec -e 'using Libdl; find_library("libhdf5") |> dlpath |> dirname |> println')"
  if [ -z "$hdf5_libdir" ]; then
    die "location of hdf5 library could not be determined automatically (use '--hdf5-library' instead)"
  fi
else
  hdf5_libdir="$(dirname $LIBTRIXI_HDF5_LIBRARY)"
fi
hdf5_library="$hdf5_libdir/libhdf5.$shared_library_suffix"
if [ ! -f "$hdf5_library" ]; then
  die "hdf5 library '$hdf5_library' not found" 2
fi
hdf5_hl_library="$hdf5_libdir/libhdf5_hl.$shared_library_suffix"
if [ ! -f "$hdf5_hl_library" ]; then
  die "hdf5 library '$hdf5_hl_library' not found" 2
fi

# Check if MPI library was given by the user or try to find it using Julia
if [ -z "$LIBTRIXI_MPI_LIBRARY" ]; then
  mpi_library="$($julia_exec -e 'using Libdl; find_library(["libmpi", "libmpi_ibm", "msmpi", "libmpich", "libmpi_cray", "libmpitrampoline"]) |> dlpath |> println')"
  if [ -z "$mpi_library" ]; then
    die "location of MPI library could not be determined automatically (use '--mpi-library' instead)"
  fi
else
  mpi_library="$LIBTRIXI_MPI_LIBRARY"
fi
if [ ! -f "$mpi_library" ]; then
  die "MPI library '$mpi_library' not found" 2
fi

# Check if libt8 was given by the user or try to find it using Julia
if [ -z "$LIBTRIXI_T8CODE_LIBRARY" ]; then
  t8code_libdir="$($julia_exec -e 'using Libdl; find_library("libt8") |> dlpath |> dirname |> println')"
  if [ -z "$t8code_libdir" ]; then
    die "location of t8code library could not be determined automatically (use '--t8code-library' instead)"
  fi
else
  t8code_libdir="$(dirname $LIBTRIXI_T8CODE_LIBRARY)"
fi
t8code_libdir_abs="$(cd $t8code_libdir && pwd)"
t8code_library="$t8code_libdir_abs/libt8.$shared_library_suffix"
if [ ! -f "$t8code_library" ]; then
  die "t8code library '$t8code_library' not found" 2
fi

# Check if libp4est was given by the user or else assume it is next to libt8
if [ -z "$LIBTRIXI_P4EST_LIBRARY" ]; then
  p4est_libdir="$t8code_libdir"
else
  p4est_libdir="$(dirname $LIBTRIXI_P4EST_LIBRARY)"
fi
p4est_libdir_abs="$(cd $p4est_libdir && pwd)"
p4est_library="$p4est_libdir_abs/libp4est.$shared_library_suffix"
if [ ! -f "$p4est_library" ]; then
  die "p4est library '$p4est_library' not found" 2
fi

# Check if libsc was given by the user or else assume it is next to libt8
if [ -z "$LIBTRIXI_SC_LIBRARY" ]; then
  sc_libdir="$t8code_libdir"
else
  sc_libdir="$(dirname $LIBTRIXI_SC_LIBRARY)"
fi
sc_libdir_abs="$(cd $sc_libdir && pwd)"
sc_library="$sc_libdir_abs/libsc.$shared_library_suffix"
if [ ! -f "$sc_library" ]; then
  die "sc library '$sc_library' not found" 2
fi

# Determine path to LibTrixi.jl
prefix_or_repo="$1"
if [ -d "$prefix_or_repo/LibTrixi.jl" ]; then
  libtrixi_jl_path="$prefix_or_repo/LibTrixi.jl"
elif [ -d "$prefix_or_repo/share/libtrixi/LibTrixi.jl" ]; then
  libtrixi_jl_path="$prefix_or_repo/share/libtrixi/LibTrixi.jl"
else
  echo "ERROR: no valid path to LibTrixi.jl could be found for the given prefix/repo" >&2
  echo "We checked the following:" >&2
  echo "- '$prefix_or_repo/share/libtrixi/LibTrixi.jl' is not a directory"
  echo "- '$prefix_or_repo/LibTrixi.jl' is not a directory"
  exit 2
fi

# Check that no files will be overwritten/modified unless force is used
if [ $USE_FORCE -ne 1 ]; then
  for path in Project.toml Manifest.toml LocalPreferences.toml "$julia_depot"; do
    if [ -d $path ] || [ -f $path ] || [ -L $path ]; then
      die "'$path' already exists in current directory (override with '-f' flag)"
    fi
  done
fi

# Install dependencies for configuration
echo "Install dependencies for configuration..."
JULIA_DEPOT_PATH="$julia_depot" JULIA_PKG_PRECOMPILE_AUTO=0 \
    $julia_exec --project=. \
    -e "
using Pkg
Pkg.add([\"Preferences\", \"UUIDs\", \"MPIPreferences\", \"T8code\", \
         \"P4est\", \"HDF5\", \"MPI\"])"
[ $? -eq 0 ] || die "could not install dependencies"
echo

# Set system MPI library
echo "Use system MPI for Julia... "
JULIA_DEPOT_PATH="$julia_depot" $julia_exec --project=. \
    -e "
using MPIPreferences
MPIPreferences.use_system_binary(; library_names=\"$mpi_library\")
"
[ $? -eq 0 ] || die "could not configure system MPI library for Julia"
echo

# Set system t8code library
echo "Use system t8code for Julia... "
JULIA_DEPOT_PATH="$julia_depot" $julia_exec --project=. \
    -e "
using Preferences, UUIDs
set_preferences!(UUID(\"d0cc0030-9a40-4274-8435-baadcfd54fa1\"),
                 \"libt8\" => \"$t8code_library\",
                 \"libp4est\" => \"$p4est_library\",
                 \"libsc\" => \"$sc_library\",
                 force = true)
"
[ $? -eq 0 ] || die "could not configure system t8code library for Julia"
echo

# Set system p4est library
echo "Use system p4est for Julia... "
JULIA_DEPOT_PATH="$julia_depot" $julia_exec --project=. \
    -e "
using Preferences, UUIDs
set_preferences!(UUID(\"7d669430-f675-4ae7-b43e-fab78ec5a902\"),
                 \"libp4est\" => \"$p4est_library\",
                 \"libsc\" => \"$sc_library\",
                 force = true)
"
[ $? -eq 0 ] || die "could not configure system p4est library for Julia"
echo

# Set system HDF5 library
echo "Use system HDF5 for Julia... "
# Disable preloading libcurl.so if it has been set when running within the GitHub runner,
# since it does not work with the JLL-provided libhdf5.so
if [ "$CI" = "true" ] && [ -n "$LD_PRELOAD" ]; then
  store_ld_preload="$LD_PRELOAD"
  LD_PRELOAD=""
fi
JULIA_DEPOT_PATH="$julia_depot" $julia_exec --project=. \
    -e "
using Preferences, UUIDs
set_preferences!(UUID(\"f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f\"),
                 \"libhdf5\" => \"$hdf5_library\",
                 \"libhdf5_hl\" => \"$hdf5_hl_library\",
                 force = true)
"
# Restore LD_PRELOAD if it had been set before
if [ "$CI" = "true" ] && [ -n "$store_ld_preload" ]; then
  LD_PRELOAD="$store_ld_preload"
fi
[ $? -eq 0 ] || die "could not configure system HDF5 library for Julia"
echo

# Install Trixi.jl and OrdinaryDiffEq.jl first
# Rationale: By first installing Trixi.jl, we ensure that we use its latest version,
# even if it does not play nicely with the latest version of OrdinaryDiffEq.jl.
# xref: https://github.com/trixi-framework/libtrixi/issues/190
echo "Install Trixi.jl and OrdinaryDiffEqLowStorageRK.jl..."
JULIA_DEPOT_PATH="$julia_depot" JULIA_PKG_PRECOMPILE_AUTO=0 \
    $julia_exec --project=. \
    -e "
using Pkg
Pkg.add([\"Trixi\", \"OrdinaryDiffEqLowStorageRK\"])
"
[ $? -eq 0 ] || die "could not install dependencies"
echo

# Develop LibTrixi.jl
echo "Install LibTrixi.jl..."
JULIA_DEPOT_PATH="$julia_depot" JULIA_PKG_PRECOMPILE_AUTO=0 \
    $julia_exec --project=. \
    -e "using Pkg; Pkg.develop(path=\"$libtrixi_jl_path\")"
[ $? -eq 0 ] || die "could not install LibTrixi.jl"
echo

# Install and precompile everything
if [ $SKIP_PRECOMPILE -ne 1 ]; then
  echo "Precompile all packages ..."
  JULIA_DEPOT_PATH="$julia_depot" JULIA_PKG_PRECOMPILE_AUTO=0 \
      $julia_exec --project=. \
      -e "using Pkg; Pkg.precompile(strict=true)"
  [ $? -eq 0 ] || die "could not precompile packages"
  echo
fi


# Congratulate user
echo
echo "SUCCESS: Julia has been set up for libtrixi! 🐳"
echo
echo "Note: When calling libtrixi's 'trixi_initialize' function, you need to provide"
echo
echo "    $PWD"
echo
echo "as the 'project_directory'. The environment variable"
echo
echo "    JULIA_DEPOT_PATH=\"$PWD/$julia_depot\""
echo
echo "will then automatically be set. It may be overridden manually."
