#!/bin/bash -e

# Disabled shellcheck items: SC1091 for external scripts, SC2034 for unused
# variables, SC2124 for concatenating toolchain options with $@, SC2129 for
# individual redirects ">>".
# shellcheck disable=SC1091,SC2034,SC2124,SC2129

[ "${BASH_SOURCE[0]}" ] && SCRIPT_NAME="${BASH_SOURCE[0]}" || SCRIPT_NAME=$0
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_NAME")" && pwd -P)"

# +---------------------------------------------------------------------------+
# |   CP2K: A general program to perform molecular dynamics simulations       |
# |   Copyright 2000-2022 CP2K developers group <https://cp2k.org>            |
# |                                                                           |
# |   SPDX-License-Identifier: GPL-2.0-or-later                               |
# +---------------------------------------------------------------------------+
#
#
# *****************************************************************************
#> \brief    This script will compile and install or link existing tools and
#>           libraries CP2K depends on and generate a set of ARCH files which
#>           can be used to compile CP2K
#> \history  Created on Friday, 2016/02/05
#            Update for Intel (17.01.2022, MK)
#> \author   Lianheng Tong (ltong) lianheng.tong@kcl.ac.uk
# *****************************************************************************

# ------------------------------------------------------------------------
# Work directories and used files
# ------------------------------------------------------------------------
export ROOTDIR="${PWD}"
export SCRIPTDIR="${ROOTDIR}/scripts"
export BUILDDIR="${ROOTDIR}/build"
export INSTALLDIR="${ROOTDIR}/install"
export TOOLKIT_SCRIPT="${SCRIPTDIR}/tool_kit.sh"

# ------------------------------------------------------------------------
# Make a copy of all options for $SETUPFILE
# ------------------------------------------------------------------------
TOOLCHAIN_OPTIONS="$@"

# ------------------------------------------------------------------------
# Exit this script if it is not called from the ./tools/toolchain directory
# ------------------------------------------------------------------------
if [ "${ROOTDIR}" != "${SCRIPT_DIR}" ]; then
  cat << EOF
ERROR: (${SCRIPT_NAME}, line ${LINENO}) Incorrect execution location.
The absolute path of the main toolchain script is at:
  ${SCRIPT_DIR}
Actual working directory where it is currently called:
  ${ROOTDIR}
Please enter the absolute path above before executing the main toolchain script
so that subsequent scripts can be found and files can be placed correctly.
EOF
  exit 1
fi

# ------------------------------------------------------------------------
# Load common variables and tools
# ------------------------------------------------------------------------
source "${SCRIPTDIR}"/common_vars.sh
source "${SCRIPTDIR}"/tool_kit.sh

# ------------------------------------------------------------------------
# Documentation
# ------------------------------------------------------------------------
show_help() {
  cat << EOF

This script will help you prepare the toolchain for compiling and using CP2K.
There are a number of dependency packages for CP2K, which may be downloaded
from internet and freshly installed, or detected from system path and linked.
Once these dependencies are ready, the CMake options for compiling CP2K and
further instructions will be printed at the end of this script's execution.
See README.md under the toolchain directory for more information.

USAGE:

$(basename "$SCRIPT_NAME") [options]

OPTIONS:

  -h, --help              Show this message and exit.
  --help-hpc              Show additional hints for HPC users and exit.
  -j <n>                  Number of processors for parallel compiling.
                          If omitted, the script will automatically try to
                          determine the number of available processors and use
                          all of them by default.
  --install-dir           Set the directory you want to installed toolchain
                          dependencies to. Default is the "install" directory
                          in current path.
  --no-check-certificate  Bypass verification of server's certificate while
                          downloading anything from internet via wget command.
                          In case wget errors about "certificate verification"
                          or "common name doesn't match requested host name"
                          occur while downloading tarballs, the recommended
                          solution is to install the latest wget release;
                          alternatively, this script can be rerun with this
                          option in command line.
                          Security wise this should still be okay as sha256
                          checksums are validated after every tarball
                          download. Nevertheless, use this option at your own
                          risk.
  --install-all           Set value of all --with-PKG options to "install"
                          (except --with-intel, --with-intelmpi, --with-amd).
                          By default, GNU compiler and MPICH are installed.
                          AFTER this option on the command line, you can set
                          specific --with-PKG options to another value again
                          for selective fine controls (see below).
  --mpi-mode              Select which MPI flavour to use. Available options
                          are: mpich, openmpi, intelmpi, and no.
                          If omitted, the script will automatically try to
                          determine the flavour based on the MPI library
                          available in system path; alternatively, if any of
                          --with-mpich, --with-openmpi or --with-intelmpi
                          options (see below) is explicitly set to values
                          other than no, the script will follow.
                          For CRAY (CLE) systems and the case that you use
                          "--with-mpi" but no MPI installation is detected,
                          the default flavour is mpich.
                          By selecting "no", MPI is unsupported and disabled.
  --math-mode             Select which core math library to use. Available
                          options are: acml, cray, mkl, and openblas.
                          If omitted, for non-CRAY systems, mkl is the default
                          when environment variable \$MKLROOT exists, otherwise
                          openblas is the drefault; for CRAY (CLE) systems,
                          cray is the default, corresponding to cray libsci.
                          Alternatively, if any of --with-acml, --with-mkl,
                          or --with-openblas options (see below) is explicitly
                          set to values other than no, the script will follow.
  --target-cpu            Select the target CPU architecture for compiling.
                          This option determines the value of -mtune flag in
                          CFLAGS for compilers. If omitted or set to "native",
                          compiling will be tuned/optimized for the native
                          host system, and some instruction sets will be
                          detected including AVX, AVX2, AVX512,etc.
                          Alternatively this option can be set depending on
                          actual target CPU microarchitecture, e.g. "haswell",
                          "skylake", or just "generic".
                          Default = native
  --gpu-ver               Select the target GPU architecture for compiling.
                          Available options are: K20X, K40, K80, P100, V100,
                          A100, H100, GB10, A40, Mi50, Mi100, Mi250, and no.
                          This option determines the value of nvcc -arch flag.
                          Default = no
  --libint-lmax           Maximum supported angular momentum by libint if the
                          "--with-libint" option is set to "install".
                          Available options are: 4, 5, 6, 7. Higher value will
                          result in more build time and larger library size.
                          Default = 5
  --log-lines             The number of final lines from the log file in the
                          corresponding directory that will be dumped to the
                          command line in case a package build returns a non-
                          zero exit code. Due to limited length, this snippet
                          may not contain the very first warning or error
                          message.
                          Default = 200
  --dry-run               After writing toolchain config and env files to the
                          install directory, just print a list of effective
                          settings after resolving known conflicts, then exit
                          without actually downloading tarballs or building
                          packages.

The --enable-FEATURE options follow the rules:
  --enable-FEATURE=yes    Enable this particular feature.
  --enable-FEATURE=no     Disable this particular feature.
  --enable-FEATURE        The option keyword alone is equivalent to
                          --enable-FEATURE=yes

Specific options of --enable-FEATURE:
  --enable-tsan           Turn on Thread Sanitizer (TSAN) for GNU compiler.
                          Default = no
  --enable-cuda           Turn on GPU (CUDA) support.
                          Can be combined with --enable-opencl.
                          Default = no
  --enable-gauxc-cutlass  Turn on CUTLASS local work driver support when
                          installing GauXC. Requires --with-gauxc=install,
                          --enable-cuda=yes, and CUDA compute capability >= 8.0.
                          Default = no
  --enable-hip            Turn on GPU (HIP) support.
                          Default = no
  --enable-opencl         Turn on OpenCL (GPU) support. Requires the OpenCL
                          development packages and runtime. If combined with
                          --enable-cuda, OpenCL alongside of CUDA is used.
                          Default = no
  --enable-cray           Turn on CRAY Linux Environment (CLE) support.
                          If omitted, the script will automatically try to
                          determine if your system is CLE by detecting
                          environment variable \$CRAY_LD_LIBRARY_PATH and
                          provide support accordingly.

The --with-PKG options follow the rules:
  --with-PKG=no           Do not use the package.
  --with-PKG=install      Download and decompress the package in \$PWD/build,
                          and install the library package in \$PWD/install.
  --with-PKG=system       Find the required libraries of the package from the
                          system path variables such as PATH, LD_LIBRARY_PATH
                          and CPATH etc.
  --with-PKG=<path>       The package will be assumed to be installed in
                          the given <path>, and be linked accordingly.
  --with-PKG              The option keyword alone will be equivalent to
                          --with-PKG=install

Specific options of --with-PKG:
  --with-gcc              Use the GNU compilers (gcc, g++, gfortran) to build
                          newly installed dependencies and CP2K.
                          Only one of --with-gcc, --with-intel and --with-amd
                          should be used.
                          Default = system
  --with-intel            Use the Intel compilers (icx, icpx, ifort) to build
                          newly installed dependencies and CP2K.
                          Default = system
  --with-ifx              If yes, use the new Intel Fortran compiler ifx
                          instead of ifort to compile CP2K.
                          Default = no
  --with-amd              Use the AMD compilers (clang, clang++, flang) to
                          build newly installed dependencies and CP2K.
                          Default = system
  --with-cmake            CMake utilities.
                          Default = install
  --with-ninja            Ninja utilities.
                          Default = install
  --with-openmpi          Use OpenMPI library for parallel versions of
                          newly installed dependencies and CP2K.
                          Only one of --with-openmpi, --with-mpich and
                          --with-intelmpi should be used, and --mpi-mode option
                          (see above) should be consistent.
                          Default = system
  --with-mpich            Use MPICH library for parallel versions of
                          newly installed dependencies and CP2K.
                          Default = system
  --with-mpich-device     Select the MPICH device, implying use of MPICH.
                          Default = ch4
  --with-intelmpi         Use Intel MPI library for parallel versions of
                          newly installed dependencies and CP2K.
                          Default = system
  --with-openblas         Use OpenBLAS, which provides LAPACK and BLAS library,
                          and is also used to get arch information.
                          --math-mode option (see above) should be consistent.
                          Default = install
  --with-mkl              Use Intel Math Kernel Library (MKL), which provides
                          LAPACK and BLAS library.
                          If MKL's FFTW3 interface is suitable (no FFTW-MPI
                          support), it replaces the FFTW library.
                          If the ScaLAPACK component is found, it replaces the
                          one specified by --with-scalapack.
                          Default = system
  --with-acml             Use AMD core maths library (ACML), which provides
                          LAPACK and BLAS library.
                          Default = system
  --with-gmp              Enable GMP library, optional dependency of GreenX.
                          Default = no
  --with-fftw             Enable FFTW3 library for fast fourier transform.
                          Default = install
  --with-libxc            Enable libxc for exchange-correlation in QuickStep
                          DFT (pure and hybrid functionals) calculations.
                          Default = install
  --with-gauxc            Enable GauXC for external exchange-correlation
                          integration. Installing GauXC with Skala
                          support also enables libtorch and installs Skala-1.1.
                          Default = no
  --with-eigen            Enable Eigen3; required by libint and SIRIUS.
                          Default = no
  --with-libint           Enable libint for two-body molecular integrals in
                          Hartree-Fock and hybrid functional calculations.
                          Default = install
  --with-greenx           Enable GreenX library for Minimax grids and Padé
                          analytic continuation in RT-BSE.
                          This package requires CMake, BLAS and LAPACK.
                          Default = no
  --with-cosma            Enable COSMA as a replacement for ScaLAPACK in matrix
                          multiplication. If set to "install", COSTA and TileMM
                          will also be installed; if CUDA and/or HIP support is
                          enabled too, respective versions will all be built.
                          Default = install
  --with-libxsmm          Enable LIBXSMM to provide kernels for LIBXS.
                          Installing is supported on arch x86_64 or arm64.
                          Default = install
  --with-libxs            Enable LIBXS as a small matrix multiplication
                          library and for other low-level operations.
                          Default = install
  --with-libxstream       Enable LIBXSTREAM as an OpenCL-based accelerator
                          backend (requires LIBXS).
                          Default = install
  --with-scalapack        Enable ScaLAPACK for parallel linear algebra
                          calculations.
                          Default = install
  --with-elpa             Enable ELPA library as eigenvalue solver for large
                          parallel jobs.
                          Default = install
  --with-ace              Enable interface to ML-pace library.
                          Default = no
  --with-deepmd           Enable interface to DeePMD-kit library.
                          This does not include other DeepModeling utilities
                          like DP-GEN or dpdata.
                          Default = no
  --with-gsl              Enable the GNU scientific library (GSL).
                          This package is required for PLUMED and SIRIUS.
                          Default = install
  --with-fmt              Enable the formatting C/C++ library.
                          This package is required for SIRIUS.
                          Default = no
  --with-libtorch         Enable libtorch as a machine learning framework.
                          This package is required for NequIP and Allegro, and
                          also for installing DeePMD-kit.
                          Default = no
  --with-plumed           Enable interface to the PLUMED library for enhanced
                          sampling methods.
                          This package requires MPI, GSL and FFTW.
                          Default = no
  --with-hdf5             Enable the hdf5 library for file format support.
                          This package is used by sirius and trexio.
                          Default = no
  --with-libsmeagol       Enable interface to SMEAGOL NEGF library.
                          This package requires MPI.
                          Default = no
  --with-libvdwxc         Enable libvdwxc library for support of Van der Waals
                          interactions in SIRIUS.
                          This library is optinal to SIRIUS.
                          Default = no
  --with-libvori          Enable libvori for the Voronoi integration and the
                          BQB compressed trajectory format.
                          Default = install
  --with-spglib           Enable the spg library for symmetry groups detection.
                          This package depends on CMake.
                          Default = install
  --with-dftd4            Enable the standalone DFTD4 package by Grimme for the
                          DFT-D4 dispersion correction method.
                          This package requires CMake.
                          Default = no
  --with-tblite           Enable the tblite package by Grimme for GFN-xtb and
                          DFT-D4 methods, bundled with multicharge, mctc-lib,
                          mstore, s-dftd3, and toml-f libraries as backends.
                          If tblite is used, standalone DFTD4 package specified
                          by --with-dftd4 will not be used.
                          This package requires CMake.
                          Default = install
  --with-sirius           Enable interface to the plane wave SIRIUS library.
                          This package requires Libxc, ScaLAPACK, ELPA, GSL,
                          libspg, HDF5, pugixml, and libvdwxc.
                          Default = no
  --with-pugixml          Enable pugixml library for XML parsing.
                          This library is required by SIRIUS.
                          Default = no
  --with-spla             Enable the Specialized Parallel Linear Algebra (SPLA)
                          library.
                          This library is required by SIRIUS and is optional
                          for GPU support.
                          Default = no
  --with-spfft            Enable SpFFT for sparse Fourier Transform.
                          This library is required by SIRIUS.
                          Default = no
  --with-trexio           Enable the trexio library for TREXIO file format.
                          Default = no
  --with-libfci           Enable the libfci active-space solver library.
                          Default = no
  --with-mcl              Install MCL library for MiMiC with toolchain.
                          Default = no
  --with-dbcsr            Install DBCSR library with toolchain.
                          Default = install
  --with-cusolvermp       NVIDIA cusolverMp: CUDA library for distributed dense
                          linear algebra.
                          Default = no
  --with-libgint          Enable the use of libGint for the calculation of the Hartree-Fock exchange on (nvidia) GPUs
                          Default = no

FURTHER INSTRUCTIONS

Before running the script, please ensure that several prerequisites including
(but not limited to) wget, bzip2 and make are present, which should be provided
by the system package manager (such as dnf and apt). The install_requirements.sh
script in the toolchain directory can help collect them.

All packages to be installed locally will be downloaded and built inside
./build, and then installed into package specific directories inside ./install.

The directory ./build is safe to delete, as it contains only the files and
directories that are downloaded via this script to install these packages.
However, once the packages are installed and you compiled CP2K then you must
keep ./install in exactly the same location as it was first created, as it
contains tools and libraries your version of CP2K binary will depend on.

It should be safe to terminate running of this script in the middle of a build
process. The script will know if a package has been successfully installed and
will just carry on and recompile and install the last package it is working on.
This is true even if you lose the content of the entire ./build directory.

Some packages are sensitive to the environment variables of their dependencies.
Therefore, if you use --with-PKG_A=system but encounter an error indicating that
PKG_A cannot be found when installing PKG_B, please first check whether the
environment variables set for PKG_A are correctly and fully imported (especially
LIBRARY_PATH and CPATH, which are often overlooked).

For HPC users who wish to install toolchain dependencies and CP2K on public
supercomputer clusters for oneself, it would be helpful to use "--help-hpc"
option to show some hints and observations.

  +----------------------------------------------------------------+
  |  YOU SHOULD ALWAYS SOURCE ./install/setup BEFORE YOU RUN CP2K  |
  |  COMPILED WITH THIS TOOLCHAIN                                  |
  +----------------------------------------------------------------+

EOF
}

show_help_hpc() {
  cat << EOF

For HPC users who wish to install toolchain dependencies and CP2K on public
supercomputer clusters for oneself:

As this is a complicated process, it is strongly advised to contact local
system administrators or managers for timely, specific assistance. Here are
some hints and observations that may be useful:

(1) Please don't forget to use "-h" or "--help" option to see detailed usage of
    the toolchain script!

(2) Generally root or sudo power is not necessary, and a convenient directory
    with read and write permission as well as sufficient disk space should be
    okay when installing toolchain and CP2K for a single user.

(3) The server is very likely to have multiple compilers, MPI libraries, math
    libraries and other packages that are managed by module systems, such as
    LMod and Environment Modules. Users can load or unload modules to control
    active environment variables and paths in runtime without conflicts. It is
    recommended to check for available modules (with "module avail", "module
    show" or similar commands) beforehand, and activate desired packages when
    running the toolchain script with "--with-PKG=system" options so as to
    avoid repeated labour. That said, actual compatibility between modules and
    CP2K to be built may still take rounds of trial-and-error to confirm, and
    resorting to "--with-PKG=install" can sometimes resolve problems if modules
    turn out to be outdated, or compiled inconsistently, or not registering
    complete variables and paths, or not built with GPU support on GPU machine,
    etc. Please forward complaints about faulty modules to whoever responsible
    for the server first before submitting any bug report to program developer.

(4) If no internet connection is available for downloading packages from public
    resources on the server, an offline installation of toolchain and CP2K may
    be carried out by downloading all packages elsewhere, transferring them to
    server and placing them under the ./build directory. The toolchain script
    will not attempt to download packages if they are already present in the
    build directory, with filenames and sha256sum strings matching the records.

(5) An important common feature of clusters is the distinction of node types:
    "login node", where users log in and perform tasks with low workload; and
    "compute node", where resource-intensive computation jobs are carried out.
    They may be hosted on separate machines, and their hardware specifications
    (CPU, RAM, disk space, etc.) may be similar or different. Therefore, care
    must be taken especially for the latter case. For instance, discrepancies
    in CPU architectures and supported instruction sets may cause poor program
    performance or illegal instruction errors if toolchain script is executed
    on login node with "--target-cpu=native" (which is default too if omitted)
    but CP2K is executed on compute node(s) afterwards. In this case, an option
    "--target-cpu=generic" with best portability for compiling programs at the
    cost of reduced (non-optimized) performance may be necessary.

(6) Again, be careful about the environment if CP2K is to be executed with job
    submission scripts to the job queue system handling resource allocation.
    Active environment variables and paths on the login node seen by user (by
    loading modules, sourcing scripts, editing ~/.bashrc or /etc/profile files,
    or entering commands interactively in general) may NOT be effective on the
    compute node where CP2K actually runs, unless all appropriate commands are
    written explicitly in the batch job submission script. For example, a
    frequently encountered scenario with corrupted, interleaved output messages
    stems from incorrect MPI library configuration launching multiple instances
    of CP2K simultaneously instead of multi-process parallel execution of one
    single instance; some possible culprits are that the ./install/setup file
    is not sourced or the wrong module for MPI library is loaded at runtime.

EOF
}

# ------------------------------------------------------------------------
# PACKAGE LIST: register all new dependent tools and libs here. Order
# is important, the first in the list gets installed first
# ------------------------------------------------------------------------
tool_list="gcc intel amd cmake ninja"
mpi_list="mpich openmpi intelmpi"
math_list="mkl acml openblas"
lib_list="fftw eigen libint libxc gauxc libxsmm libxs libxstream cosma scalapack
          elpa dbcsr cusolvermp plumed spfft spla gsl spglib hdf5 libvdwxc sirius
          libvori libtorch deepmd ace dftd4 tblite pugixml libsmeagol fmt trexio
          libfci greenx gmp mcl libgint"
package_list="${tool_list} ${mpi_list} ${math_list} ${lib_list}"
# ------------------------------------------------------------------------

# first set everything to __DONTUSE__
for ii in ${package_list}; do
  eval "with_${ii}=__DONTUSE__"
done

# ------------------------------------------------------------------------
# Work out default settings
# ------------------------------------------------------------------------

# tools to turn on by default:
with_gcc="__SYSTEM__"

# libs to turn on by default:
with_dbcsr="__INSTALL__"
with_fftw="__INSTALL__"
with_eigen="__DONTUSE__"
with_libint="__INSTALL__"
with_libxsmm="__INSTALL__"
with_libxs="__INSTALL__"
with_libxstream="__DONTUSE__"
with_libxc="__INSTALL__"
with_gauxc="__DONTUSE__"
with_scalapack="__INSTALL__"
with_sirius="__DONTUSE__"
with_gsl="__DONTUSE__"
with_fmt="__DONTUSE__"
with_spglib="__INSTALL__"
with_hdf5="__DONTUSE__"
with_trexio="__DONTUSE__"
with_libfci="__DONTUSE__"
with_elpa="__INSTALL__"
with_cusolvermp="__DONTUSE__"
with_libvdwxc="__DONTUSE__"
with_spfft="__DONTUSE__"
with_spla="__DONTUSE__"
with_cosma="__INSTALL__"
with_libvori="__INSTALL__"
with_libtorch="__DONTUSE__"
with_ninja="__DONTUSE__"
with_dftd4="__DONTUSE__"
with_tblite="__INSTALL__"
with_libsmeagol="__DONTUSE__"
with_mcl="__DONTUSE__"
with_libgint="__DONTUSE__"
# the math and mpi libraries are chosen by their respective modes.
# default math library settings, MATH_MODE picks the math library
# to use, and with_* defines the default method of installation if it
# is picked. For non-CRAY systems defaults to mkl if $MKLROOT is
# available, otherwise defaults to openblas
if [ -n "${MKLROOT}" ]; then
  export MATH_MODE="mkl"
  with_mkl="__SYSTEM__"
else
  export MATH_MODE="openblas"
fi
with_acml="__SYSTEM__"
with_openblas="__INSTALL__"

# for MPI, we try to detect system MPI variant
if (command -v mpiexec > /dev/null 2>&1); then
  # check if we are dealing with openmpi, mpich or intelmpi
  if (mpiexec --version 2>&1 | grep -s -q "HYDRA"); then
    echo "MPI is detected and it appears to be MPICH"
    export MPI_MODE="mpich"
    with_mpich="__SYSTEM__"
  elif (mpiexec --version 2>&1 | grep -s -q "OpenRTE"); then
    echo "MPI is detected and it appears to be OpenMPI 4 (or older)"
    export MPI_MODE="openmpi"
    with_openmpi="__SYSTEM__"
  elif (mpiexec --version 2>&1 | grep -s -q "Open MPI"); then
    echo "MPI is detected and it appears to be OpenMPI 5"
    export MPI_MODE="openmpi"
    with_openmpi="__SYSTEM__"
  elif (mpiexec --version 2>&1 | grep -s -q "Intel"); then
    echo "MPI is detected and it appears to be Intel MPI"
    with_gcc="__DONTUSE__"
    with_amd="__DONTUSE__"
    with_intel="__SYSTEM__"
    with_intelmpi="__SYSTEM__"
    export MPI_MODE="intelmpi"
  else # default to mpich
    echo "MPI is detected and defaults to MPICH"
    export MPI_MODE="mpich"
    with_mpich="__SYSTEM__"
  fi
else
  if [ -n "${CRAY_LD_LIBRARY_PATH}" ]; then
    echo "Cray Linux Environment (CLE) is detected with no MPI"
  else
    echo "No MPI installation detected. (Ignore this message if
    a fresh MPI installation is requested.)"
  fi
  export MPI_MODE="no"
fi

# default enable options
dry_run="__FALSE__"
enable_tsan="__FALSE__"
enable_opencl="__FALSE__"
enable_cuda="__FALSE__"
enable_gauxc_cutlass="__FALSE__"
enable_hip="__FALSE__"
export with_ifx="no"
export GPUVER="no"
export MPICH_DEVICE="ch4"
export TARGET_CPU="native"
NPROCS_OVERWRITE="$(get_nprocs)"
export NPROCS_OVERWRITE

# default for libint
export LIBINT_LMAX="5"

# default for log file dump size
export LOG_LINES="200"

# defaults for CRAY Linux Environment
if [ -n "${CRAY_LD_LIBRARY_PATH}" ]; then
  enable_cray="__TRUE__"
  export MATH_MODE="cray"
  # Default MPI used by CLE is assumed to be MPICH, in any case
  # do not use the installers for the MPI libraries
  with_mpich="__DONTUSE__"
  with_openmpi="__DONTUSE__"
  with_intelmpi="__DONTUSE__"
  export MPI_MODE="mpich"
  # set default value for some installers appropriate for CLE
  with_gcc="__DONTUSE__"
  with_amd="__DONTUSE__"
  with_intel="__DONTUSE__"
  with_fftw="__SYSTEM__"
  with_scalapack="__DONTUSE__"
else
  enable_cray="__FALSE__"
fi

# ------------------------------------------------------------------------
# Parse user options
# ------------------------------------------------------------------------
echo "Toolchain script received the following options:"
echo "  ${TOOLCHAIN_OPTIONS}"
echo "Parsing options and resolving conflicts..."
while [ $# -ge 1 ]; do
  case ${1} in
    -j)
      case "${2}" in
        -*)
          NPROCS_OVERWRITE="$(get_nprocs)"
          export NPROCS_OVERWRITE
          ;;
        [0-9]*)
          shift
          export NPROCS_OVERWRITE="${1}"
          ;;
        *)
          report_error ${LINENO} "Non-integer argument ${2} for -j flag found."
          ;;
      esac
      ;;
    -j[0-9]*)
      export NPROCS_OVERWRITE="${1#-j}"
      ;;
    --install-dir=*)
      if [[ "${1#--install-dir=}" != /* ]]; then
        report_error "The path for --install-dir must be an absolute path."
      fi
      export INSTALLDIR="${1#--install-dir=}"
      ;;
    --no-check-certificate)
      export DOWNLOADER_FLAGS="--no-check-certificate"
      ;;
    --install-all)
      # set all package to the default installation status
      for ii in ${package_list}; do
        if [ "${ii}" != "intel" ] &&
          [ "${ii}" != "intelmpi" ] &&
          [ "${ii}" != "amd" ] &&
          [ "${ii}" != "mkl" ] &&
          [ "${ii}" != "acml" ] &&
          [ "${ii}" != "cusolvermp" ]; then
          eval "with_${ii}=__INSTALL__"
        fi
      done
      # Use MPICH as default
      export MPI_MODE="mpich"
      ;;
    --mpi-mode=*)
      user_input="${1#*=}"
      case "$user_input" in
        mpich)
          export MPI_MODE="mpich"
          ;;
        openmpi)
          export MPI_MODE="openmpi"
          ;;
        intelmpi)
          export MPI_MODE="intelmpi"
          ;;
        no)
          export MPI_MODE="no"
          ;;
        *)
          echo "ERROR: Invalid value for --mpi-mode found."
          echo "Currently only one of the following options is supported:
            openmpi, mpich, intelmpi.
Otherwise use option no."
          exit 1
          ;;
      esac
      ;;
    --math-mode=*)
      user_input="${1#*=}"
      case "$user_input" in
        cray)
          export MATH_MODE="cray"
          ;;
        mkl)
          export MATH_MODE="mkl"
          ;;
        acml)
          export MATH_MODE="acml"
          ;;
        openblas)
          export MATH_MODE="openblas"
          ;;
        *)
          echo "ERROR: Invalid value for --math-mode found."
          echo "Currently only one of the following options is supported:
            mkl, acml, openblas, cray."
          exit 1
          ;;
      esac
      ;;
    --gpu-ver=*)
      user_input="${1#*=}"
      case "${user_input}" in
        K20X | K40 | K80 | P100 | V100 | A100 | H100 | GB10 | A40 | Mi50 | Mi100 | Mi250 | no)
          export GPUVER="${user_input}"
          ;;
        *)
          echo "ERROR: Invalid value for --gpu-ver found."
          echo "Currently only one of the following options is supported:
            K20X, K40, K80, P100, V100, A100, H100, GB10, A40, Mi50, Mi100, Mi250.
Otherwise use option no."
          exit 1
          ;;
      esac
      ;;
    --target-cpu=*)
      user_input="${1#*=}"
      export TARGET_CPU="${user_input}"
      ;;
    --log-lines=*)
      user_input="${1#*=}"
      case "${user_input}" in
        [0-9]*)
          export LOG_LINES="${user_input}"
          ;;
        *)
          report_error ${LINENO} "Non-integer ${user_input} for --log-lines."
          ;;
      esac
      ;;
    --libint-lmax=*)
      user_input="${1#*=}"
      export LIBINT_LMAX="${user_input}"
      ;;
    --dry-run)
      dry_run="__TRUE__"
      ;;
    --enable-tsan*)
      enable_tsan=$(read_enable "${1}")
      if [ "${enable_tsan}" = "__INVALID__" ]; then
        report_error "invalid value for --enable-tsan, please use yes or no"
      fi
      ;;
    --enable-cuda*)
      enable_cuda=$(read_enable "${1}")
      if [ "${enable_cuda}" = "__INVALID__" ]; then
        report_error "invalid value for --enable-cuda, please use yes or no"
      fi
      ;;
    --enable-gauxc-cutlass*)
      enable_gauxc_cutlass=$(read_enable "${1}")
      if [ "${enable_gauxc_cutlass}" = "__INVALID__" ]; then
        report_error "invalid value for --enable-gauxc-cutlass, please use yes or no"
      fi
      ;;
    --enable-hip*)
      enable_hip=$(read_enable "${1}")
      if [ "${enable_hip}" = "__INVALID__" ]; then
        report_error "invalid value for --enable-hip, please use yes or no"
      fi
      ;;
    --enable-opencl*)
      enable_opencl=$(read_enable "${1}")
      if [ "${enable_opencl}" = "__INVALID__" ]; then
        report_error "invalid value for --enable-opencl, please use yes or no"
      fi
      ;;
    --enable-cray*)
      enable_cray=$(read_enable "${1}")
      if [ "${enable_cray}" = "__INVALID__" ]; then
        report_error "invalid value for --enable-cray, please use yes or no"
      fi
      ;;
    --with-gcc*)
      with_gcc=$(read_with "${1}")
      ;;
    --with-cmake*)
      with_cmake=$(read_with "${1}")
      ;;
    --with-ninja*)
      with_ninja=$(read_with "${1}")
      ;;
    --with-mpich-device=*)
      user_input="${1#*=}"
      export MPICH_DEVICE="${user_input}"
      export MPI_MODE="mpich"
      ;;
    --with-mpich*)
      with_mpich=$(read_with "${1}")
      if [ "${with_mpich}" != "__DONTUSE__" ]; then
        export MPI_MODE="mpich"
      fi
      ;;
    --with-openmpi*)
      with_openmpi=$(read_with "${1}")
      if [ "${with_openmpi}" != "__DONTUSE__" ]; then
        export MPI_MODE="openmpi"
      fi
      ;;
    --with-intelmpi*)
      with_intelmpi=$(read_with "${1}" "__SYSTEM__")
      if [ "${with_intelmpi}" != "__DONTUSE__" ]; then
        export MPI_MODE="intelmpi"
      fi
      ;;
    --with-amd*)
      with_amd=$(read_with "${1}" "__SYSTEM__")
      ;;
    --with-ifx*)
      with_ifx=$(read_with "${1}" "yes")
      ;;
    --with-intel*)
      with_intel=$(read_with "${1}" "__SYSTEM__")
      ;;
    --with-libint*)
      with_libint=$(read_with "${1}")
      ;;
    --with-libxc*)
      with_libxc=$(read_with "${1}")
      ;;
    --with-gauxc*)
      with_gauxc=$(read_with "${1}")
      ;;
    --with-fftw*)
      with_fftw=$(read_with "${1}")
      ;;
    --with-eigen*)
      with_eigen=$(read_with "${1}")
      ;;
    --with-mkl*)
      with_mkl=$(read_with "${1}" "__SYSTEM__")
      if [ "${with_mkl}" != "__DONTUSE__" ]; then
        export MATH_MODE="mkl"
      fi
      ;;
    --with-acml*)
      with_acml=$(read_with "${1}")
      if [ "${with_acml}" != "__DONTUSE__" ]; then
        export MATH_MODE="acml"
      fi
      ;;
    --with-openblas*)
      with_openblas=$(read_with "${1}")
      if [ "${with_openblas}" != "__DONTUSE__" ]; then
        export MATH_MODE="openblas"
      fi
      ;;
    --with-scalapack*)
      with_scalapack=$(read_with "${1}")
      ;;
    --with-libxsmm*)
      with_libxsmm=$(read_with "${1}")
      ;;
    --with-libxstream*)
      with_libxstream=$(read_with "${1}")
      ;;
    --with-libxs*)
      with_libxs=$(read_with "${1}")
      ;;
    --with-elpa*)
      with_elpa=$(read_with "${1}")
      ;;
    --with-cusolvermp*)
      with_cusolvermp=$(read_with "${1}" "__SYSTEM__")
      ;;
    --with-deepmd*)
      with_deepmd=$(read_with "${1}")
      ;;
    --with-ace*)
      with_ace=$(read_with "${1}")
      ;;
    --with-plumed*)
      with_plumed=$(read_with "${1}")
      ;;
    --with-sirius*)
      with_sirius=$(read_with "${1}")
      ;;
    --with-pugixml*)
      with_pugixml=$(read_with "${1}")
      ;;
    --with-gsl*)
      with_gsl=$(read_with "${1}")
      ;;
    --with-libgint*)
      with_libgint=$(read_with "${1}")
      ;;
    --with-fmt*)
      with_fmt=$(read_with "${1}")
      ;;
    --with-spglib*)
      with_spglib=$(read_with "${1}")
      ;;
    --with-hdf5*)
      with_hdf5=$(read_with "${1}")
      ;;
    --with-libvdwxc*)
      with_libvdwxc=$(read_with "${1}")
      ;;
    --with-spfft*)
      with_spfft=$(read_with "${1}")
      ;;
    --with-cosma*)
      with_cosma=$(read_with "${1}")
      ;;
    --with-libvori*)
      with_libvori=$(read_with "${1}")
      ;;
    --with-libtorch*)
      with_libtorch=$(read_with "${1}")
      ;;
    --with-spla*)
      with_spla=$(read_with "${1}")
      ;;
    --with-dftd4*)
      with_dftd4=$(read_with "${1}")
      ;;
    --with-tblite*)
      with_tblite=$(read_with "${1}")
      ;;
    --with-libsmeagol*)
      with_libsmeagol=$(read_with "${1}")
      ;;
    --with-trexio*)
      with_trexio=$(read_with "${1}")
      ;;
    --with-libfci*)
      with_libfci=$(read_with "${1}")
      ;;
    --with-greenx*)
      with_greenx=$(read_with "${1}")
      ;;
    --with-gmp*)
      with_gmp=$(read_with "${1}")
      ;;
    --with-dbcsr*)
      with_dbcsr=$(read_with "${1}")
      ;;
    --with-mcl*)
      with_mcl=$(read_with "${1}")
      ;;
    --help)
      show_help
      exit 0
      ;;
    -h)
      show_help
      exit 0
      ;;
    --help-hpc)
      show_help_hpc
      exit 0
      ;;
    *)
      report_error ${LINENO} "Unknown flag: ${1}
See help message of this script produced by --help option for supported ones."
      ;;
  esac
  shift
done

# consolidate settings after user input
export ENABLE_TSAN="${enable_tsan}"
export ENABLE_CUDA="${enable_cuda}"
export ENABLE_GAUXC_CUTLASS="${enable_gauxc_cutlass}"
export ENABLE_HIP="${enable_hip}"
export ENABLE_OPENCL="${enable_opencl}"
export ENABLE_CRAY="${enable_cray}"

# ------------------------------------------------------------------------
# Check and solve known conflicts before installations proceed
# ------------------------------------------------------------------------
# Compiler conflicts
if [ "${with_gcc}" = "__INSTALL__" ]; then
  if [ "${with_intel}" != "__DONTUSE__" ]; then
    report_warning ${LINENO} "With Intel compiler, do not install GNU compiler."
    with_gcc="__SYSTEM__"
  elif [ "${with_amd}" != "__DONTUSE__" ]; then
    report_warning ${LINENO} "With AMD compiler, do not install GNU compiler."
    with_gcc="__SYSTEM__"
  fi
fi
if [ "${with_amd}" != "__DONTUSE__" ]; then
  if [ "${with_intel}" != "__DONTUSE__" ]; then
    report_error ${LINENO} "The AMD and Intel compilers can't be used together."
  fi
fi
# MPI library conflicts
if [ "${MPI_MODE}" = "no" ]; then
  if [ "${with_scalapack}" != "__DONTUSE__" ]; then
    report_warning ${LINENO} "Not using MPI, so ScaLAPACK is disabled."
    with_scalapack="__DONTUSE__"
  fi
  if [ "${with_elpa}" != "__DONTUSE__" ]; then
    report_warning ${LINENO} "Not using MPI, so ELPA is disabled."
    with_elpa="__DONTUSE__"
  fi
  if [ "${with_plumed}" != "__DONTUSE__" ]; then
    report_warning ${LINENO} "Not using MPI, so PLUMED is disabled."
    with_plumed="__DONTUSE__"
  fi
  if [ "${with_libsmeagol}" != "__DONTUSE__" ]; then
    report_warning ${LINENO} "Not using MPI, so libsmeagol is disabled."
    with_libsmeagol="__DONTUSE__"
  fi
  if [ "${with_sirius}" != "__DONTUSE__" ]; then
    report_warning ${LINENO} "Not using MPI, so SIRIUS is disabled."
    with_sirius="__DONTUSE__"
  fi
  if [ "${with_spfft}" != "__DONTUSE__" ]; then
    report_warning ${LINENO} "Not using MPI, so SpFFT is disabled."
    with_spfft="__DONTUSE__"
  fi
  if [ "${with_spla}" != "__DONTUSE__" ]; then
    report_warning ${LINENO} "Not using MPI, so SpLA is disabled."
    with_spla="__DONTUSE__"
  fi
  if [ "${with_cosma}" != "__DONTUSE__" ]; then
    report_warning ${LINENO} "Not using MPI, so COSMA is disabled."
    with_cosma="__DONTUSE__"
  fi
  if [ "${with_mcl}" != "__DONTUSE__" ]; then
    report_warning ${LINENO} "Not using MPI, so MCL is disabled."
    with_mcl="__DONTUSE__"
  fi
else
  # if gcc is installed, then mpi needs to be installed too.
  if [ "${with_gcc}" = "__INSTALL__" ]; then
    report_warning ${LINENO} "When installing GNU compiler, Intel compiler and
      Intel MPI are disabled, and MPI library is freshly installed."
    with_intel="__DONTUSE__"
    with_intelmpi="__DONTUSE__"
    case ${MPI_MODE} in
      mpich)
        with_mpich="__INSTALL__"
        with_openmpi="__DONTUSE__"
        ;;
      openmpi)
        with_mpich="__DONTUSE__"
        with_openmpi="__INSTALL__"
        ;;
      intelmpi)
        report_error ${LINENO} "Incompatible --mpi-mode=intelmpi found."
        ;;
    esac
  fi
  # Enable only one MPI implementation.
  case ${MPI_MODE} in
    mpich)
      with_openmpi="__DONTUSE__"
      with_intelmpi="__DONTUSE__"
      if [ "${with_mpich}" = "__DONTUSE__" ]; then
        with_mpich="__INSTALL__"
      fi
      ;;
    openmpi)
      with_mpich="__DONTUSE__"
      with_intelmpi="__DONTUSE__"
      if [ "${with_openmpi}" = "__DONTUSE__" ]; then
        with_openmpi="__INSTALL__"
      fi
      ;;
    intelmpi)
      with_mpich="__DONTUSE__"
      with_openmpi="__DONTUSE__"
      if [ "${with_intelmpi}" = "__DONTUSE__" ]; then
        report_error ${LINENO} "While --mpi-mode=intelmpi is set, no Intel MPI
could be found or linked in the system, and installation by toolchain is not
supported. Please install manually and check executable path before rerunning."
      fi
      ;;
  esac
fi

# If CUDA or HIP are enabled, make sure the GPU version has been defined.
if [ "${ENABLE_CUDA}" = "__TRUE__" ] || [ "${ENABLE_HIP}" = "__TRUE__" ]; then
  if [ "${GPUVER}" = "no" ]; then
    report_error ${LINENO} "Either CUDA or HIP is enabled, but --gpu-ver is not
set to one of the known architectures. See help message of this script produced
by --help option for supported ones."
  fi
fi

if [ "${ENABLE_GAUXC_CUTLASS}" = "__TRUE__" ]; then
  if [ "${ENABLE_CUDA}" != "__TRUE__" ]; then
    report_error ${LINENO} "--enable-gauxc-cutlass requires --enable-cuda=yes."
  fi
  case "${GPUVER}" in
    A100 | A40 | H100 | GB10) ;;
    *)
      report_error ${LINENO} "--enable-gauxc-cutlass requires CUDA compute capability >= 8.0."
      ;;
  esac
  if [ "${with_gauxc}" = "__DONTUSE__" ]; then
    report_warning ${LINENO} "--enable-gauxc-cutlass requires GauXC, enabling --with-gauxc=install."
    with_gauxc="__INSTALL__"
  elif [ "${with_gauxc}" != "__INSTALL__" ]; then
    report_error ${LINENO} "--enable-gauxc-cutlass is only supported with --with-gauxc=install."
  fi
fi

# If OpenCL is enabled, ensure LIBXS and LIBXSTREAM are available.
if [ "${ENABLE_OPENCL}" = "__TRUE__" ]; then
  if [ "${with_libxs}" = "__DONTUSE__" ]; then
    report_warning ${LINENO} "When enabling OpenCL, LIBXS is needed."
    with_libxs="__INSTALL__"
  fi
  if [ "${with_libxstream}" = "__DONTUSE__" ]; then
    report_warning ${LINENO} "When enabling OpenCL, LIBXSTREAM is needed."
    with_libxstream="__INSTALL__"
  fi
else
  if [ "${with_libxstream}" != "__DONTUSE__" ]; then
    report_warning ${LINENO} "LIBXSTREAM is only used by the OpenCL backend; disabling it because OpenCL is not enabled."
    with_libxstream="__DONTUSE__"
  fi
fi

# LIBXSTREAM depends on LIBXS
if [ "${with_libxstream}" != "__DONTUSE__" ]; then
  if [ "${with_libxs}" = "__DONTUSE__" ]; then
    report_warning ${LINENO} "LIBXSTREAM requires LIBXS, enabling LIBXS."
    with_libxs="__INSTALL__"
  fi
fi

# LIBXSMM kernels are delivered through LIBXS
if [ "${with_libxsmm}" != "__DONTUSE__" ] && [ "${with_libxs}" = "__DONTUSE__" ]; then
  report_warning ${LINENO} "LIBXSMM requires LIBXS, disabling LIBXSMM."
  with_libxsmm="__DONTUSE__"
fi

if [ "${with_gauxc}" != "__DONTUSE__" ] &&
  [ "${with_libxc}" = "__DONTUSE__" ]; then
  report_warning ${LINENO} "GauXC requires Libxc through ExchCXX, so Libxc is enabled."
  with_libxc="__INSTALL__"
fi

# Since tblite includes dftd4, a separate dftd4 is not needed.
if [ "${with_tblite}" != "__DONTUSE__" ]; then
  if [ "${with_dftd4}" != "__DONTUSE__" ]; then
    report_warning ${LINENO} "Since tblite includes dft-d4, a standalone dft-d4
package will not be used separately."
    with_dftd4="__DONTUSE__"
  fi
fi

# Require cmake as hard dependency.
if [ "${with_cmake}" = "__DONTUSE__" ]; then
  report_warning ${LINENO} "Installing dependencies and CP2K requires CMake but
CMake is not enabled, so a new copy of CMake will be installed first."
  with_cmake="__INSTALL__"
fi

#libGint installation requires cuda enabled
if [ "${with_libgint}" != "__DONTUSE__" ] && [ "${enable_cuda}" != "__TRUE__" ]; then
  report_warning ${LINENO} "libGint requires the use of cuda. Disabling libGint"
  with_libgint="__DONTUSE__"
fi

# SIRIUS dependencies
if [ "${with_sirius}" = "__INSTALL__" ]; then
  [ "${with_spfft}" = "__DONTUSE__" ] && with_spfft="__INSTALL__"
  [ "${with_spla}" = "__DONTUSE__" ] && with_spla="__INSTALL__"
  [ "${with_gsl}" = "__DONTUSE__" ] && with_gsl="__INSTALL__"
  [ "${with_eigen}" = "__DONTUSE__" ] && with_eigen="__INSTALL__"
  [ "${with_fmt}" = "__DONTUSE__" ] && with_fmt="__INSTALL__"
  [ "${with_libxc}" = "__DONTUSE__" ] && with_libxc="__INSTALL__"
  [ "${with_fftw}" = "__DONTUSE__" ] && with_fftw="__INSTALL__"
  [ "${with_scalapack}" = "__DONTUSE__" ] && with_scalapack="__INSTALL__"
  [ "${with_spglib}" = "__DONTUSE__" ] && with_spglib="__INSTALL__"
  [ "${with_hdf5}" = "__DONTUSE__" ] && with_hdf5="__INSTALL__"
  [ "${with_libvdwxc}" = "__DONTUSE__" ] && with_libvdwxc="__INSTALL__"
  [ "${with_cosma}" = "__DONTUSE__" ] && with_cosma="__INSTALL__"
  [ "${with_pugixml}" = "__DONTUSE__" ] && with_pugixml="__INSTALL__"
elif [ "${with_sirius}" = "__DONTUSE__" ]; then
  with_pugixml="__DONTUSE__"
  with_spfft="__DONTUSE__"
  with_libvdwxc="__DONTUSE__"
  with_fmt="__DONTUSE__"
  [ "${GPUVER}" = "no" ] && with_spla="__DONTUSE__"
fi

if [ "${with_libint}" = "__INSTALL__" ]; then
  [ "${with_eigen}" = "__DONTUSE__" ] && with_eigen="__INSTALL__"
fi

if [ "${with_trexio}" = "__INSTALL__" ]; then
  [ "${with_hdf5}" = "__DONTUSE__" ] && with_hdf5="__INSTALL__"
fi

if [ "${with_plumed}" = "__INSTALL__" ]; then
  [ "${with_gsl}" = "__DONTUSE__" ] && with_gsl="__INSTALL__"
  [ "${with_fftw}" = "__DONTUSE__" ] && with_fftw="__INSTALL__"
fi

if [ "${with_deepmd}" = "__INSTALL__" ]; then
  [ "${with_libtorch}" = "__DONTUSE__" ] && with_libtorch="__INSTALL__"
fi

if [ "${with_gauxc}" = "__INSTALL__" ]; then
  [ "${with_libtorch}" = "__DONTUSE__" ] && with_libtorch="__INSTALL__"
fi

# MKL may provide the FFTW3 interface and ScaLAPACK/BLACS. Resolve these
# choices here so the package plan, toolchain.conf and summary stay in sync.
if [ "${MATH_MODE}" = "mkl" ]; then
  # Use standalone FFTW when FFTW-MPI wrappers are needed. Otherwise, use
  # MKL's FFTW3 interface and do not install a separate FFTW package.
  if [ "${with_libvdwxc}" != "__DONTUSE__" ] && [ "${MPI_MODE}" != "no" ]; then
    report_warning ${LINENO} "libvdwxc with MPI needs FFTW-MPI wrappers; enabling standalone FFTW."
    if [ "${with_fftw}" = "__DONTUSE__" ]; then
      with_fftw="__INSTALL__"
    fi
    export MKL_FFTW="no"
  else
    echo "Using MKL-vendored FFTW"
    with_fftw="__DONTUSE__"
    export MKL_FFTW="yes"
  fi
  # Use MKL-provided ScaLAPACK/BLACS for MPI builds.
  if [ "${MPI_MODE}" != "no" ]; then
    echo "Using MKL-vendored ScaLAPACK"
    with_scalapack="__DONTUSE__"
    export MKL_SCALAPACK="yes"
  fi
  # Block libtorch installation bacause of compatibility issue
  if [ "${with_libtorch}" = "__INSTALL__" ]; then
    report_error ${LINENO} \
      "Installing prebuilt libtorch is disabled for oneMKL builds due to known conflicts between bundled and externally linked oneMKL libraries. Please provide a compatible libtorch installation via --with-libtorch=system or --with-libtorch=<path>."
  fi
fi

# ------------------------------------------------------------------------
# Preliminaries
# ------------------------------------------------------------------------

mkdir -p "${INSTALLDIR}"
export SETUPFILE="${INSTALLDIR}/setup"

if [ "${INSTALLDIR}" != "${ROOTDIR}/install" ]; then
  cp "${SCRIPTDIR}"/tool_kit.sh "${INSTALLDIR}"/
  export TOOLKIT_SCRIPT="${INSTALLDIR}/tool_kit.sh"
fi

# Select the correct compute number based on the GPU architecture
case ${GPUVER} in
  K20X)
    export ARCH_NUM="35"
    ;;
  K40)
    export ARCH_NUM="35"
    ;;
  K80)
    export ARCH_NUM="37"
    ;;
  P100)
    export ARCH_NUM="60"
    ;;
  V100)
    export ARCH_NUM="70"
    ;;
  A100)
    export ARCH_NUM="80"
    ;;
  A40)
    export ARCH_NUM="86"
    ;;
  H100)
    export ARCH_NUM="90"
    ;;
  GB10)
    export ARCH_NUM="121"
    ;;
  Mi50)
    # TODO: export ARCH_NUM=
    ;;
  Mi100)
    # TODO: export ARCH_NUM=
    ;;
  Mi250)
    # TODO: export ARCH_NUM=
    ;;
  no)
    export ARCH_NUM="no"
    ;;
  *)
    echo "ERROR: Invalid value for --gpu-ver found."
    echo "Currently only one of the following options is supported:
      K20X, K40, K80, P100, V100, A100, H100, A40, Mi50, Mi100, Mi250.
Otherwise use option no."
    exit 1
    ;;
esac

# variables used for generating cp2k ARCH file
export CP_DFLAGS=""
export CP_LIBS=""
export CP_CFLAGS=""
export CP_LDFLAGS="-Wl,--enable-new-dtags"

# ------------------------------------------------------------------------
# Special settings for CRAY Linux Environment (CLE)
# TODO: CLE should be handle like gcc or Intel using a with_cray flag and
#       this section should be moved to a separate file install_cray.
# ------------------------------------------------------------------------
if [ "${ENABLE_CRAY}" = "__TRUE__" ]; then
  echo "------------------------------------------------------------------------"
  echo "CRAY Linux Environment (CLE) is detected"
  echo "------------------------------------------------------------------------"
  # add cray paths to system search path
  export LIB_PATHS="CRAY_LD_LIBRARY_PATH ${LIB_PATHS}"
  # set compilers to CLE wrappers
  check_command cc
  check_command ftn
  check_command CC
  export CC="cc"
  export CXX="CC"
  export FC="ftn"
  export F90="${FC}"
  export F77="${FC}"
  export MPICC="${CC}"
  export MPICXX="${CXX}"
  export MPIFC="${FC}"
  export MPIFORT="${MPIFC}"
  export MPIF77="${MPIFC}"
  if [ "$with_intelmpi" = "__DONTUSE__" ]; then
    with_gcc="__DONTUSE__"
    with_intel="__SYSTEM__"
  fi
  check_lib -lz
  check_lib -ldl
  export CRAY_EXTRA_LIBS="-lz -ldl"
  # the space is intentional, so that the variable is non-empty and
  # can pass require_env checks
  export SCALAPACK_LDFLAGS=" "
  export SCALAPACK_LIBS=" "
fi

# ------------------------------------------------------------------------
# Installing tools required for building CP2K and associated libraries
# ------------------------------------------------------------------------

# Write toolchain configurations
cat << EOF > "${ROOTDIR}/toolchain_settings"
#!/bin/bash
export TOOLCHAIN_INSTALL_DIR="${INSTALLDIR}"
export CP2K_TOOLCHAIN_OPTIONS="${TOOLCHAIN_OPTIONS}"
EOF

# Write head of setup file
cat << EOF > "$SETUPFILE"
#!/bin/bash
source "${TOOLKIT_SCRIPT}"
EOF

# Write toolchain environment
write_toolchain_env "${INSTALLDIR}"

# Write toolchain config
echo "tool_list=\"${tool_list}\"" > "${INSTALLDIR}"/toolchain.conf
echo "mpi_mode=\"${MPI_MODE}\"" >> "${INSTALLDIR}"/toolchain.conf
echo "math_mode=\"${MATH_MODE}\"" >> "${INSTALLDIR}"/toolchain.conf
if [ "${MATH_MODE}" = "mkl" ]; then
  echo "MKL_FFTW=\"${MKL_FFTW}\"" >> "${INSTALLDIR}"/toolchain.conf
  echo "MKL_SCALAPACK=\"${MKL_SCALAPACK}\"" >> "${INSTALLDIR}"/toolchain.conf
fi
echo "ENABLE_CUDA=\"${ENABLE_CUDA}\"" >> "${INSTALLDIR}"/toolchain.conf
echo "ENABLE_GAUXC_CUTLASS=\"${ENABLE_GAUXC_CUTLASS}\"" >> "${INSTALLDIR}"/toolchain.conf
echo "ENABLE_HIP=\"${ENABLE_HIP}\"" >> "${INSTALLDIR}"/toolchain.conf
echo "ENABLE_OPENCL=\"${ENABLE_OPENCL}\"" >> "${INSTALLDIR}"/toolchain.conf
if [ "${ENABLE_CUDA}" == "__TRUE__" ] || [ "${ENABLE_HIP}" == "__TRUE__" ]; then
  echo "GPU_VER=\"${GPUVER}\"" >> "${INSTALLDIR}"/toolchain.conf
fi
for ii in ${package_list}; do
  install_mode=$(eval "echo \${with_${ii}}")
  echo "with_${ii}=\"${install_mode}\"" >> "${INSTALLDIR}"/toolchain.conf
done

# ------------------------------------------------------------------------
# Print the resolved toolchain configuration in a user-friendly form. The
# raw with_* variables are written to toolchain.conf, while this report
# groups packages by what the toolchain will actually do with them.
# ------------------------------------------------------------------------

get_effective_package_mode() {
  local pkg="$1"
  local var_name="with_${pkg}"
  local mode="${!var_name}"

  # Only the selected math backend is active. Some non-selected math
  # variables may keep their default value, but they are not used by
  # stage2/install_mathlibs.sh.
  case "${pkg}" in
    mkl | acml | openblas)
      if [ "${MATH_MODE}" != "${pkg}" ]; then
        mode="__DONTUSE__"
      fi
      ;;
  esac

  # Likewise, only the selected MPI implementation is active.
  case "${pkg}" in
    mpich | openmpi | intelmpi)
      if [ "${MPI_MODE}" = "no" ] || [ "${MPI_MODE}" != "${pkg}" ]; then
        mode="__DONTUSE__"
      fi
      ;;
  esac

  printf '%s' "${mode}"
}

append_report_line() {
  local var_name="$1"
  local line="$2"
  local current="${!var_name}"
  if [ -z "${current}" ]; then
    printf -v "${var_name}" '%s' "${line}"
  else
    printf -v "${var_name}" '%s\n%s' "${current}" "${line}"
  fi
}

print_report_group() {
  local title="$1"
  local body="$2"
  local width="${TOOLCHAIN_REPORT_WIDTH:-80}"
  local indent="  "
  local max_cols=5
  local min_col_width=15
  local item line padded
  local n=0 max_len=0 col_width cols rows row col idx
  local items=()

  printf '%s\n' "${title}"
  if [ -z "${body}" ]; then
    printf '  (none)\n'
    return
  fi

  while IFS= read -r item; do
    [ -z "${item}" ] && continue
    item="${item#  }"
    items[n]="${item}"
    [ "${#item}" -gt "${max_len}" ] && max_len="${#item}"
    n=$((n + 1))
  done << EOF
${body}
EOF

  [ "${n}" -eq 0 ] && printf '  (none)\n' && return

  col_width=$((max_len + 2))
  [ "${col_width}" -lt "${min_col_width}" ] && col_width="${min_col_width}"

  cols=$(((width - ${#indent}) / col_width))
  [ "${cols}" -gt "${max_cols}" ] && cols="${max_cols}"
  [ "${cols}" -lt 1 ] && cols=1
  [ "${cols}" -gt "${n}" ] && cols="${n}"

  rows=$(((n + cols - 1) / cols))
  row=0
  while [ "${row}" -lt "${rows}" ]; do
    line="${indent}"
    col=0
    while [ "${col}" -lt "${cols}" ]; do
      idx=$((row * cols + col))
      if [ "${idx}" -lt "${n}" ]; then
        item="${items[idx]}"
        if [ "${col}" -lt $((cols - 1)) ]; then
          printf -v padded '%-*s' "${col_width}" "${item}"
          line="${line}${padded}"
        else
          line="${line}${item}"
        fi
      fi
      col=$((col + 1))
    done
    while [ "${line% }" != "${line}" ]; do
      line="${line% }"
    done
    printf '%s\n' "${line}"
    row=$((row + 1))
  done
}

format_bool() {
  case "$1" in
    __TRUE__)
      printf 'yes'
      ;;
    __FALSE__)
      printf 'no'
      ;;
    *)
      printf '%s' "$1"
      ;;
  esac
}

print_toolchain_summary() {
  local pkg mode
  local install_packages=""
  local system_packages=""
  local path_packages=""
  local disabled_packages=""

  for pkg in ${package_list}; do
    mode=$(get_effective_package_mode "${pkg}")
    case "${mode}" in
      __INSTALL__)
        append_report_line install_packages "  - ${pkg}"
        ;;
      __SYSTEM__)
        append_report_line system_packages "  - ${pkg}"
        ;;
      __DONTUSE__)
        append_report_line disabled_packages "  - ${pkg}"
        ;;
      *)
        append_report_line path_packages "  - ${pkg}: ${mode}"
        ;;
    esac
  done

  printf '\nToolchain configuration summary\n'
  printf '%s\n' '-------------------------------'
  printf 'System specifications:\n'
  printf '   -%-20s = %s\n' "j" "${NPROCS_OVERWRITE}"
  printf '  --%-20s = %s\n' "target-cpu" "${TARGET_CPU}"
  printf '  --%-20s = %s\n' "gpu-ver" "${GPUVER}"
  printf '  --%-20s = %s\n' "mpi-mode" "${MPI_MODE}"
  printf '  --%-20s = %s\n' "math-mode" "${MATH_MODE}"
  printf '\nEnabled features:\n'
  printf '  --%-20s = %s\n' "enable-tsan" "$(format_bool "${enable_tsan}")"
  printf '  --%-20s = %s\n' "enable-cuda" "$(format_bool "${enable_cuda}")"
  printf '  --%-20s = %s\n' "enable-gauxc-cutlass" "$(format_bool "${enable_gauxc_cutlass}")"
  printf '  --%-20s = %s\n' "enable-hip" "$(format_bool "${enable_hip}")"
  printf '  --%-20s = %s\n' "enable-opencl" "$(format_bool "${enable_opencl}")"
  printf '  --%-20s = %s\n' "enable-cray" "$(format_bool "${enable_cray}")"
  printf '\n'
  print_report_group "Packages to be installed:" "${install_packages}"
  printf '\n'
  print_report_group "Packages to be detected from system:" "${system_packages}"
  if [ -n "${path_packages}" ]; then
    printf '\n'
    print_report_group "Packages linked from user paths:" "${path_packages}"
  fi
  printf '\n'
  print_report_group "Packages not used:" "${disabled_packages}"
  printf '\n'
}

# ------------------------------------------------------------------------
# Build packages unless dry-run mode is enabled.
# ------------------------------------------------------------------------
if [ "${dry_run}" = "__TRUE__" ]; then
  print_toolchain_summary
  printf "With --dry-run option, this script concludes with above report.\n"
  printf "The setup, toolchain env and conf files are written to %s.\n" "${INSTALLDIR}"
else
  echo "Options have been parsed successfully."
  print_toolchain_summary
  echo "Compiling with ${NPROCS_OVERWRITE} processes for target ${TARGET_CPU}."
  echo "# Leak suppressions" > "${INSTALLDIR}"/lsan.supp
  "${SCRIPTDIR}"/stage0/install_stage0.sh
  "${SCRIPTDIR}"/stage1/install_stage1.sh
  "${SCRIPTDIR}"/stage2/install_stage2.sh
  "${SCRIPTDIR}"/stage3/install_stage3.sh
  "${SCRIPTDIR}"/stage4/install_stage4.sh
  "${SCRIPTDIR}"/stage5/install_stage5.sh
  "${SCRIPTDIR}"/stage6/install_stage6.sh
  "${SCRIPTDIR}"/stage7/install_stage7.sh
  "${SCRIPTDIR}"/stage8/install_stage8.sh
  "${SCRIPTDIR}"/stage9/install_stage9.sh
  echo
  cat << EOF
========================== Epilogue =========================
Done! To build CP2K with dependencies you installed via toolchain, simply run
this script:

  ./build_cp2k.sh -j $(get_nprocs)

It will source the file "install/setup", generate proper CMake flags based on
toolchain options, and then build and install CP2K. For available options
with the script, run "./build_cp2k.sh -h".
EOF
fi

#EOF
