#!/usr/bin/env python3

# author: Ole Schuett

import argparse
import io
from pathlib import Path
from typing import Any


# ======================================================================================
def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--check", action="store_true")
    args = parser.parse_args()

    for version in "sdbg", "ssmp", "pdbg", "psmp":
        with OutputFile(f"Dockerfile.test_{version}", args.check) as f:
            mpi_mode = "mpich" if version.startswith("p") else "no"
            f.write(install_deps_toolchain(mpi_mode=mpi_mode))
            f.write(regtest("toolchain", version))

    with OutputFile(f"Dockerfile.test_psmp_4ranks", args.check) as f:
        testopts = f"--ompthreads=1 --mpiranks=4"
        f.write(install_deps_toolchain())
        f.write(regtest("toolchain", "psmp", testopts=testopts))

    with OutputFile(f"Dockerfile.test_generic_psmp", args.check) as f:
        f.write(
            install_deps_toolchain(
                target_cpu="generic", with_gauxc="no", with_libtorch="no"
            )
        )
        f.write(regtest("toolchain_generic", "psmp"))

    with OutputFile(f"Dockerfile.test_openmpi-psmp", args.check) as f:
        f.write(install_deps_toolchain(mpi_mode="openmpi"))
        f.write(regtest("toolchain", "psmp"))

    with OutputFile(f"Dockerfile.test_fedora-psmp", args.check) as f:
        f.write(install_deps_toolchain(base_image="fedora:44"))
        f.write(regtest("toolchain", "psmp"))

    with OutputFile(f"Dockerfile.test_rawhide-pdbg", args.check) as f:
        f.write(install_deps_toolchain(base_image="fedora:rawhide"))
        f.write(regtest("toolchain", "pdbg"))

    for version in "ssmp", "psmp":
        mpi_mode = "intelmpi" if version.startswith("p") else "no"
        f.write(install_deps_toolchain(mpi_mode=mpi_mode))
        with OutputFile(f"Dockerfile.test_intel-ifort-{version}", args.check) as f:
            base_image = "intel/hpckit:2024.2.1-0-devel-ubuntu22.04"
            f.write(install_deps_toolchain_intel(base_image, mpi_mode, with_ifx="no"))
            f.write(regtest("toolchain_intel", version))
        with OutputFile(f"Dockerfile.test_intel-ifx-{version}", args.check) as f:
            base_image = "intel/oneapi-hpckit:2025.2.2-0-devel-ubuntu24.04"
            f.write(install_deps_toolchain_intel(base_image, mpi_mode, with_ifx="yes"))
            f.write(regtest("toolchain_intel", version))

    with OutputFile(f"Dockerfile.test_minimal", args.check) as f:
        f.write(install_deps_ubuntu())
        f.write(regtest("minimal", "sdbg"))

    # Spack/CMake based testers

    testopts = f"--keepalive"

    with OutputFile(f"Dockerfile.test_spack_pdbg", args.check) as f:
        f.write(
            install_cp2k_spack(
                version="pdbg",
                mpi_mode="mpich",
                feature_flags="",
                testopts="",
                image_tag=f.image_tag,
            )
        )

    for gcc_version in 10, 11, 12, 13, 14, 15, 16:
        with OutputFile(
            f"Dockerfile.test_spack_psmp-gcc{gcc_version}", args.check
        ) as f:
            base_image = "ubuntu:26.04" if gcc_version > 12 else "ubuntu:24.04"
            feature_flags = "" if gcc_version > 10 else "-df libtorch"
            f.write(
                install_cp2k_spack(
                    version="psmp",
                    mpi_mode="mpich",
                    gcc_version=gcc_version,
                    base_image=base_image,
                    feature_flags=feature_flags,
                    testopts=testopts,
                    image_tag=f.image_tag,
                )
            )

    with OutputFile(f"Dockerfile.test_spack_pdbg-rawhide", args.check) as f:
        f.write(
            install_cp2k_spack(
                version="pdbg",
                mpi_mode="mpich",
                base_image="fedora:rawhide",
                feature_flags="",
                testopts=testopts,
                image_tag=f.image_tag,
            )
        )

    with OutputFile(f"Dockerfile.test_spack_psmp-fedora", args.check) as f:
        f.write(
            install_cp2k_spack(
                version="psmp",
                mpi_mode="mpich",
                base_image="fedora:latest",
                feature_flags="",
                testopts=testopts,
                image_tag=f.image_tag,
            )
        )

    with OutputFile(f"Dockerfile.test_spack_psmp-opensuse", args.check) as f:
        f.write(
            install_cp2k_spack(
                version="psmp",
                mpi_mode="mpich",
                base_image="opensuse/leap:16.0",
                gcc_version=13,
                feature_flags="",
                testopts=testopts,
                image_tag=f.image_tag,
            )
        )

    with OutputFile(f"Dockerfile.test_spack_psmp-rockylinux", args.check) as f:
        f.write(
            install_cp2k_spack(
                version="psmp",
                mpi_mode="mpich",
                base_image="docker.io/rockylinux/rockylinux:10",
                gcc_version=14,
                feature_flags="",
                testopts=testopts,
                image_tag=f.image_tag,
            )
        )

    with OutputFile(f"Dockerfile.test_spack_psmp-4x2", args.check) as f:
        f.write(
            install_cp2k_spack(
                version="psmp",
                mpi_mode="mpich",
                feature_flags="",
                testopts=f"--keepalive --mpiranks=4 --ompthreads=2",
                image_tag=f.image_tag,
            )
        )

    with OutputFile(f"Dockerfile.test_spack_openmpi-pdbg", args.check) as f:
        f.write(
            install_cp2k_spack(
                version="pdbg",
                mpi_mode="openmpi",
                feature_flags="",
                testopts="",
                image_tag=f.image_tag,
            )
        )

    with OutputFile(f"Dockerfile.test_spack_openmpi-psmp", args.check) as f:
        f.write(
            install_cp2k_spack(
                version="psmp",
                mpi_mode="openmpi",
                feature_flags="",
                testopts=testopts,
                image_tag=f.image_tag,
            )
        )

    with OutputFile(f"Dockerfile.test_spack_sdbg", args.check) as f:
        f.write(install_cp2k_spack(version="sdbg", mpi_mode="no", testopts=""))

    with OutputFile(f"Dockerfile.test_spack_ssmp", args.check) as f:
        f.write(
            install_cp2k_spack(
                version="ssmp", mpi_mode="no", testopts=testopts, image_tag=f.image_tag
            )
        )

    with OutputFile(f"Dockerfile.test_spack_ssmp-static", args.check) as f:
        f.write(
            install_cp2k_spack(
                version="ssmp-static",
                mpi_mode="no",
                testopts=testopts,
                image_tag=f.image_tag,
            )
        )

    with OutputFile(f"Dockerfile.test_spack_ssmp-P100", args.check) as f:
        f.write(
            install_cp2k_spack(
                version="ssmp",
                mpi_mode="no",
                base_image="docker.io/nvidia/cuda:12.9.1-devel-ubuntu24.04",
                gcc_version=13,
                gpu_model="P100",
                testopts=testopts,
                image_tag=f.image_tag,
            )
        )

    with OutputFile(f"Dockerfile.test_spack_psmp-P100", args.check) as f:
        f.write(
            install_cp2k_spack(
                version="psmp",
                mpi_mode="mpich",
                base_image="docker.io/nvidia/cuda:12.9.1-devel-ubuntu24.04",
                gcc_version=13,
                gpu_model="P100",
                feature_flags="",
                testopts=testopts,
                image_tag=f.image_tag,
            )
        )

    with OutputFile(f"Dockerfile.test_spack_pdbg-P100", args.check) as f:
        f.write(
            install_cp2k_spack(
                version="pdbg",
                mpi_mode="mpich",
                base_image="docker.io/nvidia/cuda:12.9.1-devel-ubuntu24.04",
                gcc_version=13,
                gpu_model="P100",
                feature_flags="",
                testopts=testopts,
                image_tag=f.image_tag,
            )
        )

    # End Spack/CMake based tester

    with OutputFile(f"Dockerfile.test_asan-psmp", args.check) as f:
        f.write(install_deps_toolchain())
        f.write(regtest("toolchain_asan", "psmp"))

    with OutputFile(f"Dockerfile.test_coverage", args.check) as f:
        f.write(install_deps_toolchain())
        f.write(coverage())

    for gcc_version in 9, 10, 11, 12, 13, 14, 15, 16:
        with OutputFile(f"Dockerfile.test_gcc{gcc_version}", args.check) as f:
            f.write(install_deps_ubuntu(gcc_version=gcc_version))
            f.write(regtest("ubuntu", "ssmp"))

    with OutputFile("Dockerfile.test_arm64-psmp", args.check) as f:
        base_img = "arm64v8/ubuntu:26.04"
        libs = dict(with_libtorch="no", with_deepmd="no", with_gauxc="no")
        f.write(install_deps_toolchain(base_img, **libs))
        f.write(regtest("toolchain_arm64", "psmp"))

    with OutputFile(f"Dockerfile.test_performance", args.check) as f:
        f.write(install_deps_toolchain())
        f.write(performance("toolchain"))

    for gpu_ver in "P100", "V100", "A100":
        with OutputFile(f"Dockerfile.test_cuda_{gpu_ver}", args.check) as f:
            f.write(install_deps_toolchain_cuda(gpu_ver=gpu_ver))
            f.write(regtest(f"toolchain_cuda_{gpu_ver}", "psmp"))
        with OutputFile(f"Dockerfile.test_performance_cuda_{gpu_ver}", args.check) as f:
            f.write(install_deps_toolchain_cuda(gpu_ver=gpu_ver))
            f.write(performance(f"toolchain_cuda_{gpu_ver}"))

    for gpu_ver in "Mi50", "Mi100":
        with OutputFile(f"Dockerfile.build_hip_rocm_{gpu_ver}", args.check) as f:
            f.write(install_deps_toolchain_hip_rocm(gpu_ver=gpu_ver))
            f.write(test_build(f"toolchain_hip_{gpu_ver}", "psmp"))

    with OutputFile(f"Dockerfile.test_conventions", args.check) as f:
        f.write(install_deps_toolchain())
        f.write(conventions())

    with OutputFile(f"Dockerfile.test_manual", args.check) as f:
        f.write(install_deps_toolchain())
        f.write(manual())

    with OutputFile(f"Dockerfile.test_precommit", args.check) as f:
        f.write(precommit())

    for name in "ase", "aiida", "i-pi", "phonopy", "gromacs":
        with OutputFile(f"Dockerfile.test_{name}", args.check) as f:
            f.write(install_deps_toolchain(mpi_mode="no"))
            f.write(test_3rd_party(name))

    for name in "misc", "doxygen":
        with OutputFile(f"Dockerfile.test_{name}", args.check) as f:
            f.write(test_without_build(name))


# ======================================================================================
def regtest(profile: str, version: str, testopts: str = "") -> str:
    return install_cp2k(profile=profile, version=version) + rf"""
# Run regression tests.
ARG TESTOPTS="{testopts}"
COPY ./tests ./tests
COPY ./tools/docker/scripts/test_regtest.sh ./
RUN /bin/bash -o pipefail -c " \
    TESTOPTS='${{TESTOPTS}}' \
    ./test_regtest.sh {profile} {version} |& tee report.log && \
    rm -rf regtesting"
""" + print_cached_report()


# ======================================================================================
def test_build(profile: str, version: str) -> str:
    return install_cp2k(profile=profile, version=version) + rf"""
# Run build test.
COPY ./tools/docker/scripts/test_build.sh .
RUN ./test_build.sh "{profile}" "{version}" 2>&1 | tee report.log
""" + print_cached_report()


# ======================================================================================
def performance(profile: str) -> str:
    return install_cp2k(profile=profile, version="psmp") + rf"""
# Run performance test for {profile}.
COPY ./benchmarks ./benchmarks
COPY ./tools/regtesting ./tools/regtesting
COPY ./tools/docker/scripts/test_performance.sh  \
     ./tools/docker/scripts/plot_performance.py  \
     ./
RUN ./test_performance.sh "{profile}" 2>&1 | tee report.log
""" + print_cached_report()


# ======================================================================================
def coverage() -> str:
    return (
        install_cp2k(profile="toolchain_coverage", version="psmp", revision=True)
        + rf"""
# Run coverage test.
COPY ./tests ./tests
COPY ./tools/docker/scripts/test_coverage.sh .
RUN ./test_coverage.sh 2>&1 | tee report.log
"""
        + print_cached_report()
    )


# ======================================================================================
def conventions() -> str:
    return (
        f"""
COPY ./tools/conventions/redirect_gfortran_output.py /usr/bin/
"""
        + install_cp2k(profile="toolchain_conventions", version="psmp")
        + f"""
# Run test for conventions.
COPY ./tools/conventions ./tools/conventions
RUN /bin/bash -ec "./tools/conventions/test_conventions.sh |& tee report.log"
"""
        + print_cached_report()
    )


# ======================================================================================
def manual() -> str:
    return install_cp2k(profile="toolchain", version="pdbg", revision=True) + rf"""
# Generate manual.
COPY ./docs ./docs
COPY ./tools/input_editing ./tools/input_editing
COPY ./tools/docker/scripts/test_manual.sh .
ARG ADD_EDIT_LINKS=yes
RUN ./test_manual.sh "${{ADD_EDIT_LINKS}}" 2>&1 | tee report.log
""" + print_cached_report()


# ======================================================================================
def precommit() -> str:
    return rf"""
FROM ubuntu:24.04

# Install dependencies.
WORKDIR /opt/cp2k-precommit
COPY ./tools/precommit/ /opt/cp2k-precommit/
RUN ./install_requirements.sh
ENV PATH="/opt/venv/bin:/opt/cp2k-precommit:$PATH"

# Install sources.
WORKDIR /opt/cp2k
COPY ./ ./

# Run precommit test.
RUN ./tools/docker/scripts/test_precommit.sh 2>&1 | tee report.log
""" + print_cached_report()


# ======================================================================================
def test_3rd_party(name: str) -> str:
    return install_cp2k(profile="toolchain", version="ssmp") + rf"""
# Run test for {name}.
COPY ./tests ./tests
COPY ./tools/docker/scripts/test_{name}.sh ./
RUN ./test_{name}.sh 2>&1 | tee report.log
""" + print_cached_report()


# ======================================================================================
def test_without_build(name: str) -> str:
    return rf"""
FROM ubuntu:24.04

# Install dependencies.
WORKDIR /opt/cp2k
COPY ./tools/docker/scripts/install_{name}.sh .
COPY ./tools/pao-ml/requirements.txt pao-ml-requirements.txt
RUN ./install_{name}.sh
ENV PATH="/opt/venv/bin:$PATH"

# Install sources.
ARG GIT_COMMIT_SHA
COPY ./src ./src
COPY ./data ./data
COPY ./docs ./docs
COPY ./tools ./tools
COPY ./tests ./tests
COPY ./cmake ./cmake
COPY ./CMakeLists.txt .
RUN bash -c "if [ -n "${{GIT_COMMIT_SHA}}" ] ; then echo "git:\${{GIT_COMMIT_SHA::7}}" > REVISION; fi"

# Run test for {name}.
COPY ./tools/docker/scripts/test_{name}.sh .
RUN ./test_{name}.sh 2>&1 | tee report.log
""" + print_cached_report()


# ======================================================================================
def print_cached_report() -> str:
    return r"""
# Output the report if the image is old and was therefore pulled from the build cache.
CMD cat $(find ./report.log -mmin +10) | sed '/^Summary:/ s/$/ (cached)/'
ENTRYPOINT []

#EOF
"""


# ======================================================================================
def install_cp2k(profile: str, version: str, revision: bool = False) -> str:
    output = ""
    if revision:
        output += "\n"
        output += "ARG GIT_COMMIT_SHA\n"
        output += "ENV GIT_COMMIT_SHA=${GIT_COMMIT_SHA}\n"

    output += rf"""
# Install CP2K sources.
WORKDIR /opt/cp2k
COPY ./src ./src
COPY ./data ./data
COPY ./tools/build_utils ./tools/build_utils
COPY ./cmake ./cmake
COPY ./CMakeLists.txt .

# Compile CP2K.
COPY ./tools/docker/scripts/build_cp2k.sh ./tools/docker/scripts/cmake_cp2k.sh ./
RUN ./build_cp2k.sh {profile} {version}
"""
    return output


# ======================================================================================
def install_deps_toolchain(
    base_image: str = "ubuntu:26.04",
    mpi_mode: str = "mpich",
    with_dbcsr: str = "",  # enabled by default
    with_gcc: str = "system",
    **kwargs: str,
) -> str:
    output = f"\nFROM {base_image}\n\n"
    output += install_toolchain(
        base_image=base_image,
        install_all="",
        mpi_mode=mpi_mode,
        with_dbcsr=with_dbcsr,
        with_gcc=with_gcc,
        **kwargs,
    )
    return output


# ======================================================================================
def install_deps_ubuntu(gcc_version: int = 15) -> str:
    assert gcc_version > 8
    base_image = "ubuntu:26.04" if gcc_version > 14 else "ubuntu:24.04"
    output = f"\nFROM {base_image}\n"

    if gcc_version > 13:
        output += rf"""
# Add Ubuntu universe repository.
RUN apt-get update -qq && apt-get install -qq --no-install-recommends software-properties-common
RUN add-apt-repository universe
"""

    output += rf"""
# Install Ubuntu packages.
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true && \
    apt-get update -qq && apt-get install -qq --no-install-recommends \
    cmake \
    less \
    nano \
    make \
    ninja-build \
    wget \
    python3 \
    ca-certificates \
    gcc-{gcc_version} \
    g++-{gcc_version} \
    gfortran-{gcc_version} \
    libfftw3-dev \
    libopenblas-dev \
    libint2-dev \
    libxc-dev \
    libhdf5-dev \
    libxsmm-dev \
    libspglib-f08-dev \
   && rm -rf /var/lib/apt/lists/*

# Create links in /usr/local/bin to overrule links in /usr/bin.
RUN ln -sf /usr/bin/gcc-{gcc_version}      /usr/local/bin/gcc  && \
    ln -sf /usr/bin/g++-{gcc_version}      /usr/local/bin/g++  && \
    ln -sf /usr/bin/gfortran-{gcc_version} /usr/local/bin/gfortran

# Use toolchain to install DBCSR.
""" + install_toolchain(
        base_image=base_image,
        mpi_mode="no",
        with_dbcsr="install",
        with_gcc="system",
        with_cmake="system",
        with_ninja="system",
        with_openblas="system",
        with_libxc="no",
        with_libint="no",
        with_fftw="no",
        with_libxsmm="install",
        with_libxs="install",
        with_spglib="no",
        with_libvori="no",
        with_tblite="no",
    )

    return output


# ======================================================================================
def install_deps_toolchain_intel(base_image: str, mpi_mode: str, with_ifx: str) -> str:
    return rf"""
FROM {base_image}

""" + install_toolchain(
        base_image="ubuntu",
        install_all="",
        mpi_mode=mpi_mode,
        with_ifx=with_ifx,
        with_mkl="",
        with_libsmeagol="",
        with_libtorch="no",
        with_deepmd="no",
        with_gauxc="no",
    )


# ======================================================================================
def install_deps_toolchain_cuda(gpu_ver: str, **kwargs: str) -> str:
    deps = rf"""
FROM nvidia/cuda:12.9.1-devel-ubuntu24.04

# Setup CUDA environment.
ENV CUDA_PATH /usr/local/cuda
ENV LD_LIBRARY_PATH /usr/local/cuda/lib64

# Disable JIT cache as there seems to be an issue with file locking on overlayfs.
# See also https://github.com/cp2k/cp2k/pull/2337
ENV CUDA_CACHE_DISABLE 1

# Install Ubuntu packages.
RUN apt-get update -qq && apt-get install -qq --no-install-recommends \
    gfortran                                                          \
   && rm -rf /var/lib/apt/lists/*

""" + install_toolchain(
        base_image="ubuntu",
        with_mpich="install",
        mpi_mode="mpich",
        enable_cuda="yes",
        with_libgint="install",
        with_sirius="install",
        gpu_ver=gpu_ver,
        **kwargs,
    )
    return deps


# ======================================================================================
def install_deps_toolchain_hip_rocm(gpu_ver: str) -> str:
    return rf"""
FROM rocm/dev-ubuntu-24.04:7.2-complete

# Install some Ubuntu packages.
RUN apt-get update -qq && apt-get install -qq --no-install-recommends \
    hipblas                                                           \
    gfortran                                                          \
    mpich                                                             \
    libmpich-dev                                                      \
   && rm -rf /var/lib/apt/lists/*

# Remove LTO from Ubuntu's MPICH
RUN sed -i -e 's/-flto=auto//g' -e 's/-ffat-lto-objects//g' \
    /usr/lib/x86_64-linux-gnu/pkgconfig/mpich.pc \
    /usr/bin/*.mpich

# Setup HIP environment.
ENV ROCM_PATH /opt/rocm
ENV PATH ${{PATH}}:${{ROCM_PATH}}/bin
ENV LD_LIBRARY_PATH ${{LD_LIBRARY_PATH}}:${{ROCM_PATH}}/lib
ENV HIP_PLATFORM amd
RUN hipconfig

""" + install_toolchain(
        base_image="ubuntu",
        mpi_mode="mpich",
        enable_hip="yes",
        with_sirius="install",
        gpu_ver=gpu_ver,
    )


# ======================================================================================
def install_toolchain(base_image: str, **kwargs: str) -> str:
    install_args = []
    for k, v in kwargs.items():
        k = k.replace("_", "-")
        if v != "":
            install_args.append(f"    --{k}={v} \\")
        else:
            install_args.append(f"    --{k} \\")
    install_args_str = "\n".join(install_args)

    return rf"""
# Install requirements for the toolchain.
WORKDIR /opt/cp2k-toolchain
COPY ./tools/toolchain/install_requirements*.sh ./
RUN ./install_requirements.sh {base_image}

# Install the toolchain.
RUN mkdir scripts
COPY ./tools/toolchain/scripts/VERSION \
     ./tools/toolchain/scripts/tool_kit.sh \
     ./tools/toolchain/scripts/common_vars.sh \
     ./tools/toolchain/scripts/signal_trap.sh \
     ./tools/toolchain/scripts/get_openblas_arch.sh \
     ./scripts/
COPY ./tools/toolchain/install_cp2k_toolchain.sh .
RUN ./install_cp2k_toolchain.sh \
{install_args_str}
    --dry-run

# Dry-run leaves behind config files for the followup install scripts.
# This breaks up the lengthy installation into smaller build steps.
COPY ./tools/toolchain/scripts/stage0/ ./scripts/stage0/
RUN  ./scripts/stage0/install_stage0.sh && rm -rf ./build

COPY ./tools/toolchain/scripts/stage1/ ./scripts/stage1/
RUN  ./scripts/stage1/install_stage1.sh && rm -rf ./build

COPY ./tools/toolchain/scripts/stage2/ ./scripts/stage2/
RUN  ./scripts/stage2/install_stage2.sh && rm -rf ./build

COPY ./tools/toolchain/scripts/stage3/ ./scripts/stage3/
RUN  ./scripts/stage3/install_stage3.sh && rm -rf ./build

COPY ./tools/toolchain/scripts/stage4/ ./scripts/stage4/
RUN  ./scripts/stage4/install_stage4.sh && rm -rf ./build

COPY ./tools/toolchain/scripts/stage5/ ./scripts/stage5/
RUN  ./scripts/stage5/install_stage5.sh && rm -rf ./build

COPY ./tools/toolchain/scripts/stage6/ ./scripts/stage6/
RUN  ./scripts/stage6/install_stage6.sh && rm -rf ./build

COPY ./tools/toolchain/scripts/stage7/ ./scripts/stage7/
RUN  ./scripts/stage7/install_stage7.sh && rm -rf ./build

COPY ./tools/toolchain/scripts/stage8/ ./scripts/stage8/
RUN  ./scripts/stage8/install_stage8.sh && rm -rf ./build

COPY ./tools/toolchain/scripts/stage9/ ./scripts/stage9/
RUN  ./scripts/stage9/install_stage9.sh && rm -rf ./build
""".lstrip()


# ======================================================================================
def install_cp2k_spack(
    version: str,
    mpi_mode: str,
    base_image: str = "ubuntu:26.04",
    gcc_version: int | None = None,
    gpu_model: str = "none",
    feature_flags: str = "",
    testopts: str = "",
    image_tag: str = "",
) -> str:
    if gcc_version is None or "fedora" in base_image:
        gcc_compilers = "g++ gcc gfortran"
    elif "opensuse/leap" in base_image:
        gcc_compilers = f"gcc gcc{gcc_version} gcc-c++ gcc{gcc_version}-c++ gcc-fortran gcc{gcc_version}-fortran"
    elif "rockylinux" in base_image:
        gcc_compilers = f"gcc gcc-c++ gcc-fortran"
    else:
        gcc_compilers = f"g++ g++-{gcc_version} gcc gcc-{gcc_version} gfortran gfortran-{gcc_version}"
    # Use the system GCC when no version is specified.
    gcc_version_flag = "" if gcc_version is None else f"-gv {gcc_version}"
    # Use external packages if possible
    use_externals = "-ue"
    # Static CP2K builds use the GCC compiler built with spack
    if version.endswith("-static"):
        use_externals = ""
        # A spack build of the same GCC version as the installed one
        # of the host system and ignoring all externals at the same
        # time is not supported
        if gcc_version == 13:
            print(
                f"\nERROR: GCC 13 is the default version of Ubuntu 24.04 and a spack build of the same version is not possible"
            )
        gcc_compilers = f"g++ gcc gfortran"
    if mpi_mode == "openmpi":
        use_externals = ""
    # Assemble docker file
    output = (
        install_base_image(
            base_image=rf"{base_image}", gcc_compilers=gcc_compilers, stage="build"
        )
        + rf"""
ARG IMAGE_TAG
ENV IMAGE_TAG=${{IMAGE_TAG:-{image_tag}}}

ARG SPACK_CACHE
ENV SPACK_CACHE="${{SPACK_CACHE:-s3://spack-cache --s3-endpoint-url=http://host.containers.internal:9000}}"

# Build CP2K dependencies
WORKDIR /opt/cp2k
COPY ./tools/spack ./tools/spack
COPY ./tools/docker ./tools/docker
COPY ./make_cp2k.sh .
RUN ./make_cp2k.sh -bd_only -cv {version} -gpu {gpu_model} {gcc_version_flag} -mpi {mpi_mode} {use_externals} {feature_flags}

###### Stage 2: Build CP2K ######

FROM build_deps AS build_cp2k

COPY ./src ./src
COPY ./data ./data
COPY ./tools/build_utils ./tools/build_utils
COPY ./cmake ./cmake
COPY ./CMakeLists.txt .

RUN ./make_cp2k.sh -cv {version} {gcc_version_flag} -gpu {gpu_model} -mpi {mpi_mode} {feature_flags}
"""
    )
    output += (
        install_base_image(
            base_image=rf"{base_image}", gcc_compilers=gcc_compilers, stage="install"
        )
        + rf"""
WORKDIR /opt/cp2k

# Install CP2K dependencies built with spack
COPY --from=build_cp2k /opt/cp2k/spack/spack/opt/spack ./spack/spack/opt/spack

# Install CP2K
COPY --from=build_cp2k /opt/cp2k/install ./install

# Install CP2K regression tests
COPY ./tests ./tests
COPY --from=build_cp2k /opt/cp2k/src/grid/sample_tasks ./src/grid/sample_tasks

# Install CP2K/Quickstep CI benchmarks
COPY ./benchmarks/CI ./benchmarks/CI

# Do not rely only on LD_LIBRARY_PATH because it is fragile
COPY --from=build_cp2k /etc/ld.so.conf.d/cp2k.conf /etc/ld.so.conf.d/cp2k.conf
RUN ldconfig

# Run CP2K regression test
RUN /opt/cp2k/install/bin/launch /opt/cp2k/install/bin/run_tests {testopts} || echo "ERROR: Tests failed"

# Create entrypoint and finalise container build
WORKDIR /mnt
ENTRYPOINT ["/opt/cp2k/install/bin/launch"]
CMD ["cp2k", "--help", "--version"]
"""
    )
    return output


# ======================================================================================
def install_base_image(
    base_image: str,
    gcc_compilers: str,
    stage: str,
) -> str:
    if stage == "build":
        output = rf"""
ARG BASE_IMAGE="{base_image}"

###### Stage 1: Build CP2K dependencies ######

FROM "${{BASE_IMAGE}}" AS build_deps
"""
        if "fedora" in base_image:
            output += rf"""
RUN dnf -qy install \
    cmake \
    {gcc_compilers} \
    git \
    libffi-devel \
    liblsan \
    libtool \
    make \
    patch \
    perl-core \
    pkg-config \
    python3 \
    python3-devel \
    unzip \
    wget \
    zlib-devel \
    zlib-static \
    && dnf clean -q all
"""
        elif "opensuse/leap" in base_image:
            output += rf"""
RUN zypper --non-interactive --quiet ref && \
    zypper --non-interactive --quiet in --no-recommends \
    bzip2 \
    cmake \
    diffutils \
    findutils \
    {gcc_compilers} \
    git \
    gzip \
    libssh-devel \
    libopenssl-devel \
    libtool \
    lsb-release \
    make \
    patch \
    pkgconf \
    python313 \
    python313-devel \
    unzip \
    wget \
    xz \
    zstd \
    && zypper --non-interactive --quiet clean --all
"""
        elif "rockylinux" in base_image:
            output += rf"""
RUN dnf -y install dnf-plugins-core && \
    dnf --enablerepo=crb -qy install \
    bzip2 \
    cmake \
    {gcc_compilers} \
    git \
    libtool \
    openssl-devel \
    patch \
    python3 \
    python3-devel \
    python3-pip \
    unzip \
    wget \
    xz \
    zlib-devel \
    zlib-static \
    && dnf clean -q all
"""
        elif "ubuntu" in base_image:
            output += rf"""
RUN apt-get update -qq && apt-get install -qq --no-install-recommends \
    bzip2 \
    ca-certificates \
    cmake \
    {gcc_compilers} \
    git \
    gnupg \
    libssh-dev \
    libssl-dev \
    libtool \
    libtool-bin \
    lsb-release \
    make \
    patch \
    pkgconf \
    python3 \
    python3-dev \
    python3-pip \
    python3-venv \
    unzip \
    wget \
    xxd \
    xz-utils \
    zlib1g \
    zlib1g-dev \
    zstd \
    && rm -rf /var/lib/apt/lists/*
"""
        else:
            print(f"\nERROR: Unknown base image {base_image} specified\n")
        if "nvidia" in base_image:
            output += rf"""
# Setup CUDA environment
ENV LD_LIBRARY_PATH /usr/local/cuda/lib64

# Disable JIT cache as there seems to be an issue with file locking on overlayfs
# See also https://github.com/cp2k/cp2k/pull/2337
ENV CUDA_CACHE_DISABLE 1
"""
    elif stage == "install":
        output = rf"""
###### Stage 3: Install CP2K ######

FROM "${{BASE_IMAGE}}" AS install_cp2k
"""
        if "fedora" in base_image:
            output += rf"""
RUN dnf -qy install \
    {gcc_compilers} \
    python3 \
    liblsan \
    && dnf clean -q all
"""
        elif "opensuse/leap" in base_image:
            output += rf"""
RUN zypper --non-interactive --quiet ref && \
    zypper --non-interactive --quiet in --no-recommends \
    {gcc_compilers} \
    python313 \
    && zypper --non-interactive --quiet clean --all
"""
        elif "rockylinux" in base_image:
            output += rf"""
RUN dnf -y install dnf-plugins-core && \
    dnf --enablerepo=crb -qy install \
    {gcc_compilers} \
    python3 \
    python3-pip \
    && dnf clean -q all
"""
        elif "ubuntu" in base_image:
            output += rf"""
RUN apt-get update -qq && apt-get install -qq --no-install-recommends \
    {gcc_compilers} \
    python3 \
    && rm -rf /var/lib/apt/lists/*
"""
        else:
            print(f"\nERROR: Unknown base image {base_image} specified\n")
        if "nvidia" in base_image:
            output += rf"""
# Setup CUDA environment
ENV LD_LIBRARY_PATH /usr/local/cuda/lib64

# Disable JIT cache as there seems to be an issue with file locking on overlayfs
# See also https://github.com/cp2k/cp2k/pull/2337
ENV CUDA_CACHE_DISABLE 1
"""
    else:
        print(f"\nERROR: Unknown stage {stage} specified\n")
    return output


# ======================================================================================
class OutputFile:
    def __init__(self, filename: str, check: bool) -> None:
        self.filename = filename
        self.check = check
        self.content = io.StringIO()
        self.content.write(f"#\n")
        self.content.write(f"# This file was created by generate_dockerfiles.py.\n")
        if "_spack_" in filename:
            self.image_tag = filename.removeprefix("Dockerfile.test_")
            usage = f"./spack_cache_start.sh; podman build --shm-size=1g -t {self.image_tag.lower()} -f ./{filename} ../../"
        else:
            self.image_tag = ""
            usage = f"podman build --shm-size=1g -f ./{filename} ../../"
        self.content.write(f"# Usage: {usage}\n#\n")

    def __enter__(self) -> "OutputFile":
        return self

    def write(self, text: str) -> None:
        self.content.write(text)

    def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
        output_path = Path(__file__).parent / self.filename
        if self.check:
            assert output_path.read_text(encoding="utf8") == self.content.getvalue()
            print(f"File {output_path} is consistent with generator script.")
        else:
            output_path.write_text(self.content.getvalue(), encoding="utf8")
            print(f"Wrote {output_path}")


# ======================================================================================
main()

# EOF
