#!/bin/bash -e

# shellcheck disable=all

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

# Load module environment if exists
LOADEDMODULES_AT_BUILD="${LOADEDMODULES-}"

TOOLCHAIN_ROOTDIR="${PWD}"
# Exit this script if it is not called from the ./tools/toolchain directory
if [ "${TOOLCHAIN_ROOTDIR}" != "${SCRIPT_DIR}" ]; then
  cat << EOF
ERROR: Incorrect execution location.
The absolute path of the build_cp2k.sh script is at:
  ${SCRIPT_DIR}
Actual working directory where it is currently called:
  ${TOOLCHAIN_ROOTDIR}
Please enter the absolute path above before executing the build_cp2k.sh script
so that subsequent scripts can be found and files can be placed correctly.
EOF
  exit 1
fi
TOOLCHAIN_SCRIPTS_DIR="${TOOLCHAIN_ROOTDIR}/scripts"
source "${TOOLCHAIN_ROOTDIR}/toolchain_settings"
source "${TOOLCHAIN_SCRIPTS_DIR}/tool_kit.sh"

# ====================== Parameter parsing ======================
CP2K_ROOT=$(cd "${TOOLCHAIN_ROOTDIR}/../.." && pwd)
CMAKE_INSTALL_PREFIX=${CP2K_ROOT}/install
DEBUG_BUILD="__FALSE__"
BUILD_JOBS="$(get_nprocs)"
BUILD_SHARED_LIBS="ON"
DRY_RUN="__FALSE__"
REBUILD_ONLY="__FALSE__"
PREFIX_SET="__FALSE__"
DEBUG_SET="__FALSE__"
BUILD_STATIC_SET="__FALSE__"

show_help() {
  cat << EOF
Usage: $(basename "$0") [OPTIONS]

Generate CMake options from the toolchain configuration and build CP2K.

Options:
  -h, --help            Show this help message and exit
  -j N, -jN             Number of parallel build jobs
  --prefix              Set CMAKE_INSTALL_PREFIX (default is ${CP2K_ROOT}/install)
  --dry-run             Show generated CMake options only and then exit
  --debug               Build debug version of CP2K (-DCMAKE_BUILD_TYPE=Debug)
  --build-static        Set -DBUILD_SHARED_LIBS=OFF (default is ON)
  --rebuild-only        Skip CMake configuration and only rebuild/install CP2K
                        from the existing build directory. This requires a
                        previous successful run without --rebuild-only and does
                        not regenerate cp2k_env.
EOF
}

while [ $# -ge 1 ]; do
  case "$1" in
    --dry-run)
      DRY_RUN="__TRUE__"
      ;;
    --rebuild-only)
      REBUILD_ONLY="__TRUE__"
      ;;
    -j)
      BUILD_JOBS="$2"
      shift
      ;;
    -j[0-9]*)
      BUILD_JOBS="${1#-j}"
      ;;
    --prefix)
      PREFIX_SET="__TRUE__"
      if [[ "${2}" != /* ]]; then
        report_error "The path for --prefix must be an absolute path."
      fi
      CMAKE_INSTALL_PREFIX="${2}"
      shift
      ;;
    --debug)
      DEBUG_SET="__TRUE__"
      DEBUG_BUILD="__TRUE__"
      ;;
    --build-static)
      BUILD_STATIC_SET="__TRUE__"
      BUILD_SHARED_LIBS="OFF"
      ;;
    -h | --help)
      show_help
      exit 0
      ;;
    *)
      echo "ERROR: Unknown option: $1"
      show_help
      exit 1
      ;;
  esac
  shift
done

if [ "${REBUILD_ONLY}" = "__TRUE__" ]; then
  if [ "${DRY_RUN}" = "__TRUE__" ] ||
    [ "${PREFIX_SET}" = "__TRUE__" ] ||
    [ "${DEBUG_SET}" = "__TRUE__" ] ||
    [ "${BUILD_STATIC_SET}" = "__TRUE__" ]; then
    cat << EOF
ERROR: "--rebuild-only" cannot be used together with options that affect CMake configuration.
Please run this script without "--rebuild-only" if you want to change CMake settings.
EOF
    exit 1
  fi
fi

# ====================== Pre-checks ======================
# Require complete source tree
# A minimum working environment for this script should be as follows (assuming out-of-tree build not required):
# cp2k                                  <- variable ${CP2K_ROOT}; CMake option -S
# ├── CMakeLists.txt                    <- file to be parsed for generating cmake options
# ├── cmake                             <- directory containing CMake files
# ├── data                              <- CP2K data directory; CMake option -DCP2K_DATA_DIR\
# ├── build                             <- to-be-created; CMake option -B
# ├── install                           <- to-be-created; CMake option -DCMAKE_INSTALL_PREFIX
# ├── src                               <- CP2K source code directory
# └── tools
#     └── toolchain                     <- working directory; variable ${TOOLCHAIN_ROOTDIR}
#         ├── build_cp2k.sh             <- this script
#         ├── install_cp2k_toolchain.sh <- script being executed before calling this script
#         ├── scripts                   <- directory with toolchain scripts; ${TOOLCHAIN_SCRIPTS_DIR}
#         │   └── tool_kit.sh
#         └── install                   <- directory with installed dependencies; ${TOOLCHAIN_INSTALL_DIR}
#             ├── setup                 <- * file to be used for building CP2K
#             ├── toolchain.conf        <- * file to be parsed for generating cmake options
#             └── toolchain.env         <- * file to be parsed for generating cmake options (MPI_F08)
#
if [ -d "${CP2K_ROOT}/src" ]; then
  echo "Root directory of CP2K with source code is found as ${CP2K_ROOT}"
  echo "(path is exported to variable \${CP2K_ROOT})."
else
  report_error ${LINENO} "\${CP2K_ROOT} does not have subdirectory src."
fi
if [ -f "${CP2K_ROOT}/CMakeLists.txt" ] && [ -r "${CP2K_ROOT}/CMakeLists.txt" ]; then
  echo "\${CP2K_ROOT}/CMakeLists.txt exists; will be parsed for CMake options."
else
  report_error ${LINENO} "\${CP2K_ROOT}/CMakeLists.txt cannot be found or read."
fi
if [ -d "${CP2K_ROOT}/data" ]; then
  echo "Data directory ${CP2K_ROOT}/data is found."
else
  report_error ${LINENO} "Data directory \${CP2K_ROOT}/data cannot be found."
fi

# Require finished toolchain
if [ ! -f "${TOOLCHAIN_INSTALL_DIR}/setup" ]; then
  echo "Error: Toolchain is not installed. Please run ./install_cp2k_toolchain.sh first."
  exit 1
fi

# Disallow combination of installing toolchain outside the source tree and CP2K under the source tree
if [ "${REBUILD_ONLY}" != "__TRUE__" ] &&
  [ "${TOOLCHAIN_INSTALL_DIR}" != "${TOOLCHAIN_ROOTDIR}"/install ] &&
  [[ ${CMAKE_INSTALL_PREFIX} == ${CP2K_ROOT}/* ]]; then
  echo
  cat << EOF
ERROR: You toolchain installation is outside the source tree but the install
prefix of CP2K is under the source tree, which is disallowed by this script.
The script will now abort; please manually set "--prefix" to a proper path.
EOF
  exit 1
fi

# ====================== Configure & Build ======================
# Load toolchain environment (required for with_xxx variables)
source "${TOOLCHAIN_INSTALL_DIR}/setup"
source "${TOOLCHAIN_INSTALL_DIR}/toolchain.conf"

# Generate cmake options for compiling cp2k
CMAKE_OPTIONS="-DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS}"
if [[ ${CMAKE_INSTALL_PREFIX} == ${CP2K_ROOT}/* ]]; then
  CMAKE_OPTIONS+=" -DCP2K_DATA_DIR=${CP2K_ROOT}/data"
fi
if [ ${DEBUG_BUILD} == "__TRUE__" ]; then
  CMAKE_OPTIONS+=" -DCMAKE_BUILD_TYPE=Debug"
fi
if [ -n "$(grep -- "--install-all" "${TOOLCHAIN_ROOTDIR}/toolchain_settings")" ]; then
  CMAKE_OPTIONS+=" -DCP2K_USE_EVERYTHING=ON -DCP2K_USE_DLAF=OFF -DCP2K_USE_PEXSI=OFF -DCP2K_USE_OPENPMD=OFF"
  # If MPI is disabled, set "CP2K_USE_MPI" to "OFF"
  if [ "${mpi_mode}" = "no" ]; then
    CMAKE_OPTIONS+=" -DCP2K_USE_MPI=OFF"
  fi
  # Some options that should be specially considered:
  # Intel MKL includes FFTW
  if [ "${with_fftw}" = "__DONTUSE__" ] && [ "${math_mode}" != "mkl" ]; then
    CMAKE_OPTIONS+=" -DCP2K_USE_FFTW3=OFF"
  fi
  # MiMic-MCL (MiMiC Communication Library)
  if [ "${with_mcl}" = "__DONTUSE__" ]; then
    CMAKE_OPTIONS+=" -DCP2K_USE_MIMIC=OFF"
  fi
  for toolchain_option in $(grep -i "dontuse" "${TOOLCHAIN_INSTALL_DIR}/toolchain.conf" |
    grep -Evi "gcc|amd|intel|cmake|fftw|mkl|dbcsr" | cut -d'_' -f2 | cut -d'=' -f1); do
    ADDED_CMAKE_OPTION=$(sed -n '/option(/,/)/p' "${CP2K_ROOT}/CMakeLists.txt" |
      grep -i "${toolchain_option}" | awk '{print $1}' | cut -d'(' -f2 | head -n 1)
    # Use "if-then" below can avoid generating empty "-D=OFF" options
    if [ -n "${ADDED_CMAKE_OPTION}" ]; then
      CMAKE_OPTIONS+=" -D${ADDED_CMAKE_OPTION}=OFF"
    fi
  done
else
  # If MPI is used, set "CP2K_USE_MPI" to "ON"
  if [ "${mpi_mode}" != "no" ]; then
    CMAKE_OPTIONS+=" -DCP2K_USE_MPI=ON -DCP2K_USE_MPI_F08=ON"
  fi
  # Some options that should be specially considered:
  # Intel MKL includes FFTW
  if [ "${with_fftw}" != "__DONTUSE__" ] || [ "${math_mode}" = "mkl" ]; then
    CMAKE_OPTIONS+=" -DCP2K_USE_FFTW3=ON"
  fi
  # MiMic-MCL (MiMiC Communication Library)
  if [ "${with_mcl}" != "__DONTUSE__" ]; then
    CMAKE_OPTIONS+=" -DCP2K_USE_MIMIC=ON"
  fi
  # Detect if any other dependencies is used and add the proper cmake option
  # Since "pugixml" and "gsl" are not mentioned in CMakeLists.txt, they will not be considered.
  for toolchain_option in $(grep "with" "${TOOLCHAIN_INSTALL_DIR}"/toolchain.conf |
    grep -Evi "dontuse|gcc|amd|intel|cmake|fftw|mkl|dbcsr" | cut -d'_' -f2 | cut -d'=' -f1); do
    var_name="with_${toolchain_option}"
    if [ "${!var_name}" != "__DONTUSE__" ]; then
      ADDED_CMAKE_OPTION=$(sed -n '/option(/,/)/p' "${CP2K_ROOT}/CMakeLists.txt" |
        grep -i "${toolchain_option}" | awk '{print $1}' | cut -d'(' -f2 | head -n 1)
      # Use "if-then" below can avoid generating empty "-D=ON" options
      if [ -n "${ADDED_CMAKE_OPTION}" ]; then
        CMAKE_OPTIONS+=" -D${ADDED_CMAKE_OPTION}=ON"
      fi
    fi
  done
fi
# Additional feature of SIRIUS
if [ "${with_sirius}" = "__INSTALL__" ]; then
  CMAKE_OPTIONS+=" -DCP2K_USE_SIRIUS_VCSQNM=ON"
  if [ "${with_tblite}" != "__DONTUSE__" ]; then
    CMAKE_OPTIONS+=" -DCP2K_USE_SIRIUS_DFTD3=ON -DCP2K_USE_SIRIUS_DFTD4=ON"
  elif [ "${with_dftd4}" != "__DONTUSE__" ]; then
    CMAKE_OPTIONS+=" -DCP2K_USE_SIRIUS_DFTD4=ON"
  fi
fi
# If GPU acceleration is used, add the option about GPU acceleration
if [ "${ENABLE_CUDA}" = "__TRUE__" ]; then
  CMAKE_OPTIONS+=" -DCP2K_USE_ACCEL=CUDA -DCP2K_WITH_GPU=${GPU_VER}"
  if [ "${with_cusolvermp}" != "__DONTUSE__" ]; then
    CMAKE_OPTIONS+=" -DCP2K_USE_CUSOLVER_MP=ON"
  fi
elif [ "${ENABLE_HIP}" = "__TRUE__" ]; then
  CMAKE_OPTIONS+=" -DCP2K_USE_ACCEL=HIP -DCP2K_WITH_GPU=${GPU_VER}"
elif [ "${ENABLE_OPENCL}" = "__TRUE__" ]; then
  CMAKE_OPTIONS+=" -DCP2K_USE_ACCEL=OPENCL"
fi

# Set build directory
BUILD_DIR="${CP2K_ROOT}/build"

log_cmake() {
  printf '%s\n' "$@" | tee -a cmake.log
}
log_build() {
  printf '%s\n' "$@" | tee -a build.log
}

if [ "${REBUILD_ONLY}" != "__TRUE__" ]; then
  [ -f "cmake.log" ] && rm -f cmake.log
  # Show CMake options
  log_cmake "Generated CMake flags:"
  for flag in ${CMAKE_OPTIONS}; do
    log_cmake "   ${flag}"
  done
fi

if [ "${DRY_RUN}" != "__TRUE__" ]; then
  rm -f build.log

  if [ "${REBUILD_ONLY}" = "__TRUE__" ]; then
    # Check if cmake_install.cmake exists
    CMAKE_INSTALL_FILE="${BUILD_DIR}/cmake_install.cmake"
    if [ ! -r "${CMAKE_INSTALL_FILE}" ]; then
      cat << EOF
ERROR: "--rebuild-only" was requested, but no CMake install script was found:
  ${CMAKE_INSTALL_FILE}
This usually means that CMake configuration/generation has not completed successfully.
Please run this script once successfully without "--rebuild-only" first.
EOF
      exit 1
    fi
    # Detect CMAKE_INSTALL_PREFIX from cmake_install.cmake
    CMAKE_INSTALL_PREFIX="$(sed -n \
      's/^[[:space:]]*set(CMAKE_INSTALL_PREFIX[[:space:]]*"\(.*\)")[[:space:]]*$/\1/p' \
      "${CMAKE_INSTALL_FILE}" | head -n 1)"
    if [ -z "${CMAKE_INSTALL_PREFIX}" ]; then
      cat << EOF
ERROR: Could not determine CMAKE_INSTALL_PREFIX from:
  ${CMAKE_INSTALL_FILE}
Please run this script once successfully without "--rebuild-only" first.
EOF
      exit 1
    fi
    # Check if cp2k_env exists under CMAKE_INSTALL_PREFIX
    if [ ! -f "${CMAKE_INSTALL_PREFIX}/cp2k_env" ]; then
      cat << EOF
ERROR: "--rebuild-only" was requested, but the CP2K environment file was not found:
  ${CMAKE_INSTALL_PREFIX}/cp2k_env
This mode does not regenerate cp2k_env.
Please run this script once successfully without "--rebuild-only" first.
EOF
      exit 1
    fi

    log_build "Skipping CMake configuration because \"--rebuild-only\" was requested."
    log_build "Reusing existing build directory:"
    log_build "  ${BUILD_DIR}"
    log_build "Using existing CP2K environment file:"
    log_build "  ${CMAKE_INSTALL_PREFIX}/cp2k_env"
  else
    if [ -d "${BUILD_DIR}" ]; then
      echo "Removing existing build directory before configuration: ${BUILD_DIR}"
      rm -rf "${BUILD_DIR}"
    fi
    mkdir -p "${BUILD_DIR}"

    log_cmake "================== CMake configuration ==================="
    log_cmake "Source dir : ${CP2K_ROOT}"
    log_cmake "Build  dir : ${BUILD_DIR}"
    log_cmake "Install dir: ${CMAKE_INSTALL_PREFIX}"
    log_cmake "Shared libs: ${BUILD_SHARED_LIBS}"

    set -o pipefail
    cmake -S "${CP2K_ROOT}" -B "${BUILD_DIR}" ${CMAKE_OPTIONS} 2>&1 | tee -a cmake.log
  fi

  log_build "==================== Building CP2K ======================="
  log_build "Parallel jobs: ${BUILD_JOBS}"
  set -o pipefail
  cmake --build "${BUILD_DIR}" --target install -j "${BUILD_JOBS}" 2>&1 | tee -a build.log
  echo "=========================================================="

  # Export variable for CMake options to cp2k_env file
  if [ "${REBUILD_ONLY}" != "__TRUE__" ]; then
    echo "#!/bin/bash" > "${CMAKE_INSTALL_PREFIX}/cp2k_env"
    if [ -n "${LOADEDMODULES_AT_BUILD}" ]; then
      printf "Detected module environment; writing to cp2k_env.\n\n"
      printf 'module load %s\n' "${LOADEDMODULES_AT_BUILD//:/ }" \
        >> "${CMAKE_INSTALL_PREFIX}/cp2k_env"
    fi
    cat << EOF >> "${CMAKE_INSTALL_PREFIX}/cp2k_env"
source ${TOOLCHAIN_INSTALL_DIR}/setup
prepend_path PATH "${CMAKE_INSTALL_PREFIX}/bin"
prepend_path LD_LIBRARY_PATH "${CMAKE_INSTALL_PREFIX}/lib"
prepend_path PKG_CONFIG_PATH "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig"
prepend_path CMAKE_PREFIX_PATH "${CMAKE_INSTALL_PREFIX}"
EOF
    cat << EOF
Done! Installed binaries are now available in: ${CMAKE_INSTALL_PREFIX}/bin
EOF
  else
    cat << EOF
Note: "--rebuild-only" was used, so the installation path and the existing CP2K
environment file were not affected.
EOF
  fi

  cat << EOF

Please always source this script to load CP2K environment before running CP2K:
  source ${CMAKE_INSTALL_PREFIX}/cp2k_env

It's suggested to run regtests after installation:
  ${CP2K_ROOT}/tests/do_regtest.py ${CMAKE_INSTALL_PREFIX}/bin psmp
Run \`${CP2K_ROOT}/tests/do_regtest.py --help\` for help message.

If you want to clean the build cache (except cached CMake files) after
installation, run:
  cmake --build "${BUILD_DIR}" --target clean
EOF
else
  cat << EOF
Since you run this script with \"--dry-run\", it now exits.
To build CP2K, drop off this flag and re-run this script.
EOF
fi

#EOF
