#!/usr/bin/env bash
#
# get_siesta_fj.sh – download, patch, and compile SIESTA with Fujitsu compilers
#
# Requires the following environment variables (set in ~/.bashrc or exported
# before calling this script):
#   MY_USER     – your Fugaku username
#   MY_PROJECT  – your project code 
#   MY_VOLUME   – your storage volume 
#
# Usage examples
#   ./get_siesta_fj.sh                    # default version (5.4.2)
#   ./get_siesta_fj.sh 5.4.1              # different version
#   ./get_siesta_fj.sh -c                 # delete extracted dir if it exists
#   ./get_siesta_fj.sh -r                 # refresh (re-download if tarball missing)
#   ./get_siesta_fj.sh -l /path/to/repo   # use a local source directory
#

#set -euo pipefail

###############################################################################
# Helper: display usage
###############################################################################
usage() {
  cat <<EOF
Usage: $0 [version] [-c|--clean] [-r|--refresh] [-l|--local <path>]

  version    SIESTA version to download (default: 5.4.2)
  -c, --clean
             Delete the extracted directory if it already exists.
  -r, --refresh
             If the directory exists *and* the tarball does not, download the
             tarball, delete the directory, and extract again.
  -l, --local <path>
             Use a local directory instead of downloading the tarball.

Only one of -c or -r can be supplied at the same time.
EOF
  exit 1
}

###############################################################################
# Parse command-line arguments
###############################################################################
VERSION="5.4.2"
CLEAN=false
REFRESH=false
LOCAL_PATH=""

while [[ $# -gt 0 ]]; do
  case "$1" in
    -c|--clean)
      CLEAN=true
      ;;
    -r|--refresh)
      REFRESH=true
      ;;
    -l|--local)
      LOCAL_PATH="$2"
      shift
      ;;
    -h|--help)
      usage
      ;;
    -*)
      echo "Unknown option: $1" >&2
      usage
      ;;
    *)
      VERSION="$1"
      ;;
  esac
  shift
done

# Reject simultaneous -c and -r
if $CLEAN && $REFRESH; then
  echo "Error: -c/--clean and -r/--refresh are mutually exclusive." >&2
  exit 1
fi

###############################################################################
# Check required environment variables
###############################################################################
for var in MY_USER MY_PROJECT MY_VOLUME; do
  if [[ -z "${!var:-}" ]]; then
    echo "Error: environment variable ${var} is not set." >&2
    echo "Export it or add it to your ~/.bashrc before running this script." >&2
    exit 1
  fi
done

###############################################################################
# Variables derived from version and environment
###############################################################################
PKG="siesta-${VERSION}"
TARBALL="${PKG}.tar.gz"
URL="https://gitlab.com/siesta-project/siesta/-/releases/${VERSION}/downloads/${TARBALL}"

SPACK_SETUP="/${MY_VOLUME}/mdt1/data/${MY_PROJECT}/${MY_USER}/tmp/spack/share/spack/setup-env.sh"
NETCDF_ENV="/${MY_VOLUME}/mdt1/data/${MY_PROJECT}/${MY_USER}/netcdf_fujitsu/netcdf_env.sh"

###############################################################################
# Helper: download tarball if absent
###############################################################################
download_tarball() {
  echo "Downloading ${TARBALL} ..."
  if command -v curl &>/dev/null; then
    curl -L -o "${TARBALL}" "${URL}"
  elif command -v wget &>/dev/null; then
    wget -O "${TARBALL}" "${URL}"
  else
    echo "Error: neither curl nor wget is available." >&2
    exit 1
  fi
}

###############################################################################
# 1-3. Obtain source code
###############################################################################
if [[ -n "${LOCAL_PATH}" ]]; then
  if [[ ! -d "${LOCAL_PATH}" ]]; then
    echo "Error: local directory '${LOCAL_PATH}' does not exist." >&2
    exit 1
  fi
  echo "Using local directory: ${LOCAL_PATH}"
  PKG="${LOCAL_PATH}"
else
  if $CLEAN && [[ -d "${PKG}" ]]; then
    echo "Removing existing directory ${PKG} (requested with --clean) ..."
    rm -rf "${PKG}"
  fi

  if $REFRESH && [[ -d "${PKG}" ]] && [[ ! -f "${TARBALL}" ]]; then
    echo "Refresh requested and tarball missing."
    download_tarball
    echo "Removing existing directory ${PKG} ..."
    rm -rf "${PKG}"
  fi

  if [[ ! -f "${TARBALL}" ]]; then
    download_tarball
  else
    echo "Tarball ${TARBALL} already exists. Skipping download."
  fi

  if [[ ! -d "${PKG}" ]]; then
    echo "Extracting ${TARBALL} ..."
    tar -xzf "${TARBALL}"
  else
    echo "Directory ${PKG} already exists. Re-extracting ..."
    rm -rf "${PKG}"
    tar -xzf "${TARBALL}"
  fi
fi

###############################################################################
# 4. Create the required build directories
###############################################################################
cd "${PKG}"
build_="_build_fj"
bin_="_siesta_bin_fj"
mkdir -p "${build_}" "${bin_}"
echo "Directories ${build_} and ${bin_} are ready in $(pwd)."

###############################################################################
# 5. Load Spack environment
###############################################################################
echo "Loading Spack environment ..."
source "${SPACK_SETUP}"
echo "Spack environment loaded."

###############################################################################
# 6. Load Spack packages
###############################################################################
echo "Loading Spack packages ..."
spack load /fomls7l  # fujitsu-ssl2@head
spack load /ldigwm3  # netlib-scalapack@2.2.2
spack load /qhm66vh  # python@3.13.5 %fj@4.12.0
spack load /hn27egk  # cmake@3.31.8
spack load /ptmidr7  # py-pip@25.1.1
spack load /ei25gqb  # flex@2.6.4
spack load /latufg2  # fftw@3.3.10
spack load /5hspcpy  # bison@3.8.2

#Those were local installation
spack load /2nmda3o  # libxc@6.2.2
spack load /h5qnbwj  # py-ruamel-yaml@0.17.32 ^python@3.13.5 /qhm66vh %fj@4.12.0
echo "Spack packages loaded."
ml list

###############################################################################
# 7. Load NetCDF environment
###############################################################################
echo "Loading NetCDF environment ..."
source "${NETCDF_ENV}"
echo "NetCDF environment loaded."

###############################################################################
# 8. Resolve additional paths
###############################################################################
export LD_LIBRARY_PATH=/lib64:${LD_LIBRARY_PATH}

export FLEX_PATH=$(spack location -i flex%fj@4.12.0)
export BISON_PATH=$(spack location -i bison%fj@4.12.0)
export PATH="${FLEX_PATH}/bin:${BISON_PATH}/bin:${PATH}"

export FUJITSU_SSL2_PATH="/opt/FJSVxtclanga/tcsds-ssl2-latest"
export SCALAPACK_PATH=$(spack location -i netlib-scalapack%fj@4.12.0)

echo "Paths resolved."

###############################################################################
# 9. Set MPI interface flag (5.4.2 does not support legacy interface)
###############################################################################
if [ "${VERSION}" == "5.4.2" ]; then
    interface="none"
else
    interface="legacy"
fi

###############################################################################
# 10. Apply source patches for Fujitsu compiler compatibility
#
# frt 4.12.1 enforces the Fortran standard more strictly than gfortran.
# The patches below fix constructs that gfortran accepts silently but
# frt rejects as errors:
#   - jwd1740i: module name collides with a locally imported entity
#   - jwd2391i: assignment between derived types without a defined operator
#   - jwd2043i: intent(out) variable may be unassigned on some paths
#   - jwd1130i: USE inside BLOCK...END BLOCK is not supported
###############################################################################
echo "Applying frt 4.12.1 compatibility patches ..."

# ============================================================
# PATCH 1: m_ts_voltage.F90
# jwd1740i: 'parallel' is both a module name and a local entity
# in ts_ncdf_voltage_assert. Rename IONode with an explicit alias
# and pass ts_IONode as an argument to the inner subroutine.
# ============================================================
sed -i 's/    use parallel, only : IONode$/    use parallel, only : ts_IONode => IONode/' \
    Src/m_ts_voltage.F90
sed -i 's/    use parallel,     only : IONode$/    use parallel,     only : ts_IONode => IONode/' \
    Src/m_ts_voltage.F90
sed -i 's/if ( IONode ) then/if ( ts_IONode ) then/g' Src/m_ts_voltage.F90
sed -i 's/if ( IONode .and./if ( ts_IONode .and./g' Src/m_ts_voltage.F90
sed -i 's/else if ( IONode .and./else if ( ts_IONode .and./g' Src/m_ts_voltage.F90
sed -i 's/if ( .not. IONode ) return/if ( .not. ts_IONode ) return/g' Src/m_ts_voltage.F90
sed -i 's/if ( IONode ) &/if ( ts_IONode ) \&/g' Src/m_ts_voltage.F90
sed -i 's/call ts_ncdf_voltage_assert(Hartree_fname,cell,nmesh)/call ts_ncdf_voltage_assert(Hartree_fname,cell,nmesh,ts_IONode)/' \
    Src/m_ts_voltage.F90
sed -i 's/subroutine ts_ncdf_voltage_assert(fname, cell, nmesh)/subroutine ts_ncdf_voltage_assert(fname, cell, nmesh, ts_IONode)/' \
    Src/m_ts_voltage.F90
awk 'BEGIN{c=0} /use parallel/{c++; if(c==3)next} 1' \
    Src/m_ts_voltage.F90 > /tmp/m_ts_voltage.F90 && \
    mv /tmp/m_ts_voltage.F90 Src/m_ts_voltage.F90
grep -q "logical, intent(in) :: ts_IONode" Src/m_ts_voltage.F90 || \
    sed -i 's/    integer, intent(in) :: nmesh(3)$/    integer, intent(in) :: nmesh(3)\n    logical, intent(in) :: ts_IONode/' \
    Src/m_ts_voltage.F90
echo "  PATCH 1 applied: Src/m_ts_voltage.F90"

# ============================================================
# PATCH 2: mass rename of imported entities in nested scopes
# jwd1740i: same collision pattern in 6 additional files.
# Python handles inline comments correctly during the rename.
# Note: spinorbit.f receives special treatment in PATCH 2b.
# ============================================================
python3 << 'PYEOF'
import re

def rename_entities_in_use(line):
    m = re.match(r'(\s*use\s+parallel\s*,\s*only\s*:\s*)(.*)', line, re.IGNORECASE)
    if not m:
        return line, {}
    prefix = m.group(1)
    rest = m.group(2)
    comment_match = re.search(r'\s*!.*$', rest)
    comment = comment_match.group(0) if comment_match else ''
    entities_str = rest[:comment_match.start()].rstrip() if comment_match else rest.rstrip()
    has_continuation = entities_str.endswith('&')
    if has_continuation:
        entities_str = entities_str[:-1].rstrip()
    entities = [e.strip() for e in entities_str.split(',') if e.strip()]
    renamed = {}
    new_entities = []
    for e in entities:
        if '=>' in e:
            new_entities.append(e)
        else:
            new_name = f'par_{e}'
            new_entities.append(f'{new_name} => {e}')
            renamed[e] = new_name
    new_line = prefix + ', '.join(new_entities)
    if has_continuation:
        new_line += ' &'
    new_line += comment + '\n'
    return new_line, renamed

problems = {
    "Src/flook_siesta.F90":      [964],
    "Src/matel_table.F90":       [666, 739],
    "Src/m_elsi_interface.F90":  [267, 413, 886, 1491],
    "Src/m_new_dm.F90":          [1369, 1686, 1812, 1905, 1998],
    "Src/m_w90_wrapper.F90":     [212, 1730],
    "Src/Orphans/new_dm.F":      [458],
}

for fname, problem_lines in problems.items():
    try:
        with open(fname, 'r') as fh:
            lines = fh.readlines()
    except FileNotFoundError:
        print(f"  WARNING: {fname} not found, skipping...")
        continue

    all_renamed = {}
    for pline in problem_lines:
        idx = pline - 1
        if idx >= len(lines):
            continue
        new_line, renamed = rename_entities_in_use(lines[idx])
        if renamed:
            lines[idx] = new_line
            all_renamed.update(renamed)

    if not all_renamed:
        continue

    for old, new in all_renamed.items():
        pattern = re.compile(r'\b' + re.escape(old) + r'\b')
        for i, line in enumerate(lines):
            if i+1 in problem_lines:
                continue
            stripped = line.lstrip()
            if stripped.startswith('!') or stripped.startswith('#'):
                continue
            new_line = pattern.sub(new, line)
            if new_line != line:
                lines[i] = new_line

    with open(fname, 'w') as fh:
        fh.writelines(lines)
    print(f"  PATCH 2 applied: {fname}")

PYEOF

# ============================================================
# PATCH 2b: spinorbit.f – special-case scope handling
# jwd1740i: inner subroutine uses 'Node' which already exists
# at module level. Manual rename only in the inner scope to
# avoid clobbering the module-level references.
# ============================================================
sed -i 's/      use parallel, only: par_Node, Nodes/      use parallel, only: Node, Nodes/' \
    Src/spinorbit.f
sed -i 's/      use parallel,        only: Node$/      use parallel,        only: par_Node => Node/' \
    Src/spinorbit.f
sed -i 's/if (par_Node .eq. 0) then/if (Node .eq. 0) then/g' \
    Src/spinorbit.f
sed -i 's/call LocalToGlobalOrb(io_l,par_Node,Nodes,io_u)/call LocalToGlobalOrb(io_l,Node,Nodes,io_u)/g' \
    Src/spinorbit.f
echo "  PATCH 2b applied: Src/spinorbit.f"

# ============================================================
# PATCH 2c: m_new_dm.F90 – case correction after mass rename
# The mass rename changed Node->par_Node but the source also
# uses 'node' (lowercase) which frt treats as a distinct entity.
# ============================================================
sed -i 's/if (node==0) print \*, " # of linearly independent vectors: ", m/if (par_Node==0) print *, " # of linearly independent vectors: ", m/' \
    Src/m_new_dm.F90
sed -i 's/if (node==0) print \*, " Rank of DIIS matrix: ", m/if (par_Node==0) print *, " Rank of DIIS matrix: ", m/' \
    Src/m_new_dm.F90
sed -i 's/if (node==0) print \*, " Estimated Rank of xi\*xj matrix: ", m/if (par_Node==0) print *, " Estimated Rank of xi*xj matrix: ", m/' \
    Src/m_new_dm.F90
sed -i 's/if (node==0) then/if (par_Node==0) then/g' \
    Src/m_new_dm.F90
echo "  PATCH 2c applied: Src/m_new_dm.F90"

# ============================================================
# PATCH 3: psop.f90
# jwd2391i: assignment between ps_annotation_t (alias of
# assoc_list_t) without a defined assignment operator.
# These are metadata annotations only; commenting them out
# does not affect physical calculations.
# ============================================================
sed -i 's/^    gannot = grid_annotation/    !gannot = grid_annotation  ! patched: frt 4.12.1/' \
    Pseudo/vnl-operator/psop.f90
sed -i 's/^    ps%local%annotation = annotation/    !ps%local%annotation = annotation  ! patched: frt 4.12.1/' \
    Pseudo/vnl-operator/psop.f90
sed -i 's/^    nlp%annotation = annotation/    !nlp%annotation = annotation  ! patched: frt 4.12.1/' \
    Pseudo/vnl-operator/psop.f90
echo "  PATCH 3 applied: Pseudo/vnl-operator/psop.f90"

# ============================================================
# PATCH 4: siesta_init.F
# jwd1740i: netcdf_ncdf has a member variable named 'parallel'
# which collides with the 'parallel' module imported on lines
# 55-57. Narrow the USE to import only what is actually needed.
# ============================================================
sed -i 's/      use netcdf_ncdf$/      use netcdf_ncdf, only: ncdf_IONode/' \
    Src/siesta_init.F
echo "  PATCH 4 applied: Src/siesta_init.F"

# ============================================================
# PATCH 5: mm_assign.f90
# jwd2043i: intent(out) variable atxres may not be assigned on
# all execution paths in subroutine paramats.
# Initialise to 0 at the start of the subroutine.
# ============================================================
python3 << 'PYEOF'
fname = "Util/QMMM-driver/Src/mm_assign.f90"
with open(fname, 'r') as fh:
    lines = fh.readlines()

target = "type(temp_residue), allocatable :: ff_residues(:)"
for i, line in enumerate(lines):
    if target in line:
        insert_idx = i + 2
        new_line = "    atxres = 0  ! patched: frt 4.12.1 intent(out) must be assigned\n"
        if new_line not in lines[insert_idx]:
            lines.insert(insert_idx, new_line)
        break

with open(fname, 'w') as fh:
    fh.writelines(lines)
print("  PATCH 5 applied: Util/QMMM-driver/Src/mm_assign.f90")
PYEOF

# ============================================================
# PATCH 6: amn.F90
# jwd1130i: USE inside BLOCK...END BLOCK is not supported by
# frt 4.12.1. Dissolve the block, move USE statements and
# declarations to subroutine scope, and rename the local
# variable orb_gindex -> blk_orb_gindex to avoid a collision
# with the orb_gindex function imported from atmfuncs.
# ============================================================
python3 << 'PYEOF'
fname = "Src/amn.F90"
with open(fname, 'r') as fh:
    content = fh.read()

old_block = """          block
            use radial, only: radial_rescale_radfunc, rad_func
            use m_matel_registry, only: peek_at_registered_radfunc
            use m_matel_registry, only: register_in_rf_pool
            
            integer :: orb_gindex, l, m
            type(rad_func), pointer :: rfunc, func_new => null()
            
            orb_gindex = tf%iorb_gindex
            call peek_at_registered_radfunc(orb_gindex,rfunc,l,m)
            if ( modulo(m,2) == 1 ) then
               allocate(func_new)
               call radial_rescale_radfunc(rfunc,func_new,scale=-1.0_dp)
               call register_in_rf_pool(func_new, l, m, "wann_twin_orb", [iproj], gindex)
            else
               call register_in_rf_pool(rfunc, l, m, "wann_orb", [iproj], gindex)
            endif
         end block"""

new_block = """          blk_orb_gindex = tf%iorb_gindex
            call peek_at_registered_radfunc(blk_orb_gindex,rfunc,l,m)
            if ( modulo(m,2) == 1 ) then
               allocate(func_new)
               call radial_rescale_radfunc(rfunc,func_new,scale=-1.0_dp)
               call register_in_rf_pool(func_new, l, m, "wann_twin_orb", [iproj], gindex)
            else
               call register_in_rf_pool(rfunc, l, m, "wann_orb", [iproj], gindex)
            endif"""

if old_block in content:
    content = content.replace(old_block, new_block)
    print("  Step 1 OK: BLOCK...END BLOCK dissolved")
else:
    print("  ERROR step 1: block not found")

old_use = "  use mpi_siesta"
new_use = """  use radial, only: radial_rescale_radfunc, rad_func  ! patched: frt 4.12.1
  use m_matel_registry, only: peek_at_registered_radfunc  ! patched: frt 4.12.1
  use m_matel_registry, only: register_in_rf_pool  ! patched: frt 4.12.1
  use mpi_siesta"""

if old_use in content:
    content = content.replace(old_use, new_use, 1)
    print("  Step 2 OK: USE statements added at subroutine scope")
else:
    print("  ERROR step 2: 'use mpi_siesta' not found")

old_decl = "  integer  :: gindex          ! Global index of the trial projector function"
new_decl = """  integer  :: blk_orb_gindex, l, m  ! patched: frt 4.12.1 block not supported
  type(rad_func), pointer :: rfunc => null(), func_new => null()  ! patched: frt 4.12.1
  integer  :: gindex          ! Global index of the trial projector function"""

if old_decl in content:
    content = content.replace(old_decl, new_decl, 1)
    print("  Step 3 OK: declarations added at subroutine scope")
else:
    print("  ERROR step 3: declaration not found")

with open(fname, 'w') as fh:
    fh.write(content)

print("  PATCH 6 applied: Src/amn.F90")
PYEOF

###############################################################################
# 11. Verify all patches
###############################################################################
echo ""
echo "Verifying patches ..."
errors=0

count=$(grep -c "use parallel" Src/m_ts_voltage.F90)
if [ "$count" -eq 2 ]; then
    echo "  ✓ Src/m_ts_voltage.F90"
else
    echo "  ✗ Src/m_ts_voltage.F90 – 'use parallel' count: $count (expected: 2)"
    errors=$((errors+1))
fi

for f in Src/flook_siesta.F90 Src/matel_table.F90 Src/m_elsi_interface.F90 \
         Src/m_new_dm.F90 Src/m_w90_wrapper.F90 Src/Orphans/new_dm.F; do
    count=$(grep -c "par_" "$f" 2>/dev/null)
    if [ "$count" -gt 0 ]; then
        echo "  ✓ $f"
    else
        echo "  ✗ $f – no par_ references found"
        errors=$((errors+1))
    fi
done

if grep -q "use parallel, only: Node, Nodes" Src/spinorbit.f && \
   grep -q "par_Node => Node" Src/spinorbit.f; then
    echo "  ✓ Src/spinorbit.f"
else
    echo "  ✗ Src/spinorbit.f"
    errors=$((errors+1))
fi

if ! grep -q "node==0" Src/m_new_dm.F90 && \
   grep -q "par_Node==0" Src/m_new_dm.F90; then
    echo "  ✓ Src/m_new_dm.F90 (PATCH 2c)"
else
    echo "  ✗ Src/m_new_dm.F90 (PATCH 2c)"
    errors=$((errors+1))
fi

count=$(grep -c "patched: frt 4.12.1" Pseudo/vnl-operator/psop.f90)
if [ "$count" -eq 4 ]; then
    echo "  ✓ Pseudo/vnl-operator/psop.f90"
else
    echo "  ✗ Pseudo/vnl-operator/psop.f90 – patched count: $count (expected: 4)"
    errors=$((errors+1))
fi

if grep -q "use netcdf_ncdf, only: ncdf_IONode" Src/siesta_init.F; then
    echo "  ✓ Src/siesta_init.F"
else
    echo "  ✗ Src/siesta_init.F"
    errors=$((errors+1))
fi

count=$(grep -c "patched: frt 4.12.1" Util/QMMM-driver/Src/mm_assign.f90)
if [ "$count" -eq 1 ]; then
    echo "  ✓ Util/QMMM-driver/Src/mm_assign.f90"
else
    echo "  ✗ Util/QMMM-driver/Src/mm_assign.f90 – patched count: $count (expected: 1)"
    errors=$((errors+1))
fi

if grep -q "use radial, only: radial_rescale_radfunc, rad_func  ! patched" Src/amn.F90 && \
   ! grep -q "end block" Src/amn.F90 && \
   grep -q "blk_orb_gindex, l, m  ! patched" Src/amn.F90; then
    echo "  ✓ Src/amn.F90"
else
    echo "  ✗ Src/amn.F90"
    errors=$((errors+1))
fi

if [ $errors -eq 0 ]; then
    echo ""
    echo "All patches applied successfully."
else
    echo ""
    echo "ERROR: $errors patch(es) failed. Aborting."
    exit 1
fi

###############################################################################
# 12. Configure SIESTA with CMake
###############################################################################
echo ""
echo "Starting CMake configuration ..."

FC=frt CC=fcc CXX=FCC cmake -S . -B "${build_}" \
        -DCMAKE_Fortran_COMPILER=frt \
        -DCMAKE_C_COMPILER=fcc \
        -DCMAKE_CXX_COMPILER=FCC \
        -DCMAKE_INSTALL_PREFIX="${bin_}" \
        -DCMAKE_Fortran_FLAGS="-Kopenmp -O2 -Kident_mpi -w -X9 -I/opt/FJSVxtclanga/.common/MECA032/include/mpi/fujitsu ${NETCDF_INC}" \
        -DCMAKE_C_FLAGS="-Kopenmp -O2 -Kident_mpi -Nclang -I/opt/FJSVxtclanga/.common/MECA032/include/mpi/fujitsu ${NETCDF_INC}" \
        -DCMAKE_CXX_FLAGS="-Kopenmp -O2 -Kident_mpi -I/opt/FJSVxtclanga/.common/MECA032/include/mpi/fujitsu -DMPICH_SKIP_MPICXX -DOMPI_SKIP_MPICXX -DMPI_NO_CPPBIND" \
        -DMPI_Fortran_COMPILER=mpifrt \
        -DMPI_C_COMPILER=mpifcc \
        -DMPI_CXX_COMPILER=mpiFCC \
        -DMPI_Fortran_LIBRARIES="/opt/FJSVxtclanga/.common/MECA032/lib64/libmpi_usempif08.so;/opt/FJSVxtclanga/.common/MECA032/lib64/libmpi_mpifh.so;/opt/FJSVxtclanga/.common/MECA032/lib64/libmpi.so" \
        -DMPI_C_LIBRARIES="/opt/FJSVxtclanga/.common/MECA032/lib64/libmpi.so" \
        -DMPI_CXX_LIBRARIES="/opt/FJSVxtclanga/.common/MECA032/lib64/libmpi_cxx.so;/opt/FJSVxtclanga/.common/MECA032/lib64/libmpi.so" \
        -DCMAKE_EXE_LINKER_FLAGS="-L/opt/FJSVxtclanga/.common/MECA032/lib64 -Wl,-rpath,/opt/FJSVxtclanga/.common/MECA032/lib64 -lfjstring_internal ${NETCDF_LIB}" \
        -DMPIEXEC_EXECUTABLE="/opt/FJSVxtclanga/tcsds-1.2.42/bin/mpiexec" \
        -DMPIEXEC_MAX_NUMPROCS=4 \
        -DBLAS_LIBRARY="${FUJITSU_SSL2_PATH}/lib64/libfjlapack.so" \
        -DLAPACK_LIBRARY="${FUJITSU_SSL2_PATH}/lib64/libfjlapack.so" \
        -DSCALAPACK_LIBRARY="${SCALAPACK_PATH}/lib/libscalapack.so" \
        -DFLEX_EXECUTABLE="${FLEX_PATH}/bin/flex" \
        -DBISON_EXECUTABLE="${BISON_PATH}/bin/bison" \
        -DSIESTA_WITH_LIBXC=OFF \
        -DSIESTA_WITH_NETCDF=ON \
        -DSIESTA_WITH_OPENMP=ON \
        -DSIESTA_WITH_MPI_INTERFACES="${interface}" \
        -DCMAKE_BUILD_TYPE=Release \
        -DSIESTA_WITH_ELPA=OFF \
        -DSIESTA_WITH_ELSI=OFF \
        -DSIESTA_WITH_DFTD3=OFF \
        -DSIESTA_WITH_FLOOK=OFF

###############################################################################
# 13. Build and install
###############################################################################
if [ $? -eq 0 ]; then
    echo ""
    echo "CMake configuration successful. Building ..."
    cmake --build "${build_}" -j 8 2>&1 | tee build_log.txt
    build_status=${PIPESTATUS[0]}

    if [ ${build_status} -eq 0 ]; then
        echo ""
        echo "Build successful. Installing ..."
        cmake --install "${build_}"
    else
        echo ""
        echo "ERROR: Build failed (exit code: ${build_status})"
        echo ""
        echo "========================================"
        echo "Source files with Fujitsu errors:"
        echo "========================================"
        grep "jwd1740i" build_log.txt | \
            grep -oP '"[^"]+\.f90|[^"]+\.F90|[^"]+\.F|[^"]+\.f"' | \
            sort -u
        echo ""
        echo "All compiler errors:"
        echo "========================================"
        grep -E "jwd[0-9]+i-s|Error [0-9]+" build_log.txt | \
            grep -v "^gmake" | \
            sort -u
        echo "========================================"
        echo "Full log: cat build_log.txt"
        exit 1
    fi
else
    echo ""
    echo "ERROR: CMake configuration failed."
    exit 1
fi