#  Copyright (C) 2018 The authors of Py-ChemShell
#
#  This file is part of Py-ChemShell.
#
#  Py-ChemShell is free software: you can redistribute it and/or modify
#  it under the terms of the GNU Lesser General Public License as
#  published by the Free Software Foundation, either version 3 of the
#  License, or (at your option) any later version.
#
#  Py-ChemShell is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU Lesser General Public License for more details.
#
#  You should have received a copy of the GNU Lesser General Public
#  License along with Py-ChemShell.  If not, see
#  <http://www.gnu.org/licenses/>.

# you.lu@stfc.ac.uk
# Oct 2018
# YL 13/07/2024: added support for CHARM++ 8.0 in NAMD 3.0

# do nothing if Charm++ is not requested
if("${NAMD_SRC_DIR}" STREQUAL "")
    return()
endif()

# YL 02/10/2020: FetchContent isn't available until 3.11 but we should aim at 3.10 which
#                comes with Ubuntu 18.04
# this is a copy of 3.16 (3-clause BSD) as a built-in utility
if(${CMAKE_VERSION} VERSION_LESS "3.11")
    include(${CHEMSH_DIR}/chemsh/utils/cmake/fetch_content.cmake)
else()
    include(FetchContent)
endif()

include(ExternalProject)

# get Charm++ dir
function(get_charm_dir NAMD_SRC_DIR)
    file(GLOB CHARM++_LIST LIST_DIRECTORIES true CONFIGURE_DEPENDS "${NAMD_SRC_DIR}/charm-*")
    foreach(ITEM ${CHARM++_LIST})
        if(EXISTS ${ITEM})
            set(CHARM++ "${ITEM}" CACHE FILEPATH "" FORCE)
        endif()
    endforeach()
endfunction()
get_charm_dir("${NAMD_SRC_DIR}")

# get Charm++ name
#get_filename_component(CHARM++_NAME "${CHARM++}" NAME_WE)
string(REGEX MATCH "charm-([0-9]+[.])+[0-9]+" CHARM++_VERSION "${CHARM++}")
string(REGEX MATCH "([0-9]+[.])+[0-9]+" CHARM++_VERSION "${CHARM++_VERSION}")
set(CHARM++_NAME "Charm++-${CHARM++_VERSION}")

set(CHARM++_DIR        ${CMAKE_CURRENT_LIST_DIR} CACHE INTERNAL "CHARM++ PATH")
set(CHARM++_SRC_DIR    ${CMAKE_CURRENT_LIST_DIR}/src)
set(CHARM++_BINARY_DIR ${CMAKE_CURRENT_LIST_DIR}/${CHEMSH_ARCH}/charm++)
file(MAKE_DIRECTORY ${CHARM++_BINARY_DIR})

# MPI version
if(WITH_MPI)
    set(ENV{MPICXX} ${CMAKE_CXX_COMPILER})

    # <version> in Charm++ build command
    set(NAMD_ARCH "mpi-linux-x86_64")

    if(${CHEMSH_ARCH} STREQUAL "gnu")
        set(CHARM++_Fortran_COMPILER "gfortran")
        set(CHARM++_C_COMPILER "mpicxx")
        # YL 11/03/2026: Charm++ 8.0+ uses C-Fortran order in directory name (previously I didn't change this for GNU)
        if(${CHARM++_VERSION} VERSION_GREATER_EQUAL 8.0)
            set(CHARM++_BUILD_DIR "mpi-linux-x86_64-${CHARM++_C_COMPILER}-${CHARM++_Fortran_COMPILER}")
        else()
            set(CHARM++_BUILD_DIR "mpi-linux-x86_64-${CHARM++_Fortran_COMPILER}-${CHARM++_C_COMPILER}")
        endif()
        set(CHARM++_TARGET_NAME "charm++_mpi_gnu")
    elseif(${CHEMSH_ARCH} STREQUAL "cpe-gnu")
        set(CHARM++_Fortran_COMPILER "gfortran")
        set(CHARM++_C_COMPILER "gcc")
        if(${CHARM++_VERSION} VERSION_GREATER_EQUAL 8.0)
            set(CHARM++_BUILD_DIR "mpi-linux-x86_64-${CHARM++_C_COMPILER}-${CHARM++_Fortran_COMPILER}")
        else()
            set(CHARM++_BUILD_DIR "mpi-linux-x86_64-${CHARM++_Fortran_COMPILER}-${CHARM++_C_COMPILER}")
        endif()
        set(CHARM++_TARGET_NAME "charm++_mpi_gnu")
        # on Cray systems we use wrappers what Charm++ doesn't directly support (not at least as of NAMD 2.14)
        # therefore we must specify the underlying gcc/gfortran and let pass the MPI compile check
        set(CHARM++_EXTRA_OPTS --incdir=${MPI_INCLUDE_PATH} --libdir=${MPI_LIB_PATH})
    elseif(${CHEMSH_ARCH} STREQUAL "intel")
        set(CHARM++_C_COMPILER "mpicxx")
        set(CHARM++_Fortran_COMPILER "ifort")
        # YL 13/07/2024: added support for CHARM++ 8.0 in NAMD 3.0
        if(${CHARM++_VERSION} VERSION_GREATER_EQUAL 8.0)
            if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "IntelLLVM")
                set(CHARM++_C_COMPILER "icx")
            endif()
            if("${CMAKE_Fortran_COMPILER_ID}" STREQUAL "IntelLLVM")
                set(CHARM++_Fortran_COMPILER "ifx")
            endif()
            set(CHARM++_BUILD_DIR "mpi-linux-x86_64-${CHARM++_C_COMPILER}-${CHARM++_Fortran_COMPILER}")
        # YL 13/07/2024: older CHARM++ doesn't support icx/ifx
        else()
            set(CHARM++_BUILD_DIR "mpi-linux-x86_64-${CHARM++_Fortran_COMPILER}-${CHARM++_C_COMPILER}")
        endif()
        set(CHARM++_TARGET_NAME "charm++_mpi_intel")
    endif()

# serial version
else()
    set(ENV{MPICXX} "")
    set(NAMD_ARCH "multicore-linux-x86_64")
    if(${CHEMSH_ARCH} STREQUAL "gnu")
        set(CHARM++_C_COMPILER "gcc")
        set(CHARM++_Fortran_COMPILER "gfortran")
        # YL 18/03/2026: Charm++ 8.0+ uses C-Fortran order in directory name
        if(${CHARM++_VERSION} VERSION_GREATER_EQUAL 8.0)
            set(CHARM++_BUILD_DIR "multicore-linux-x86_64-${CHARM++_C_COMPILER}-${CHARM++_Fortran_COMPILER}")
        else()
            set(CHARM++_BUILD_DIR "multicore-linux-x86_64-${CHARM++_Fortran_COMPILER}-${CHARM++_C_COMPILER}")
        endif()
        set(CHARM++_TARGET_NAME "charm++_multicore_gnu")
    elseif(${CHEMSH_ARCH} STREQUAL "cpe-gnu")
        set(CHARM++_C_COMPILER "gcc")
        set(CHARM++_Fortran_COMPILER "gfortran")
        if(${CHARM++_VERSION} VERSION_GREATER_EQUAL 8.0)
            set(CHARM++_BUILD_DIR "multicore-linux-x86_64-${CHARM++_C_COMPILER}-${CHARM++_Fortran_COMPILER}")
        else()
            set(CHARM++_BUILD_DIR "multicore-linux-x86_64-${CHARM++_Fortran_COMPILER}-${CHARM++_C_COMPILER}")
        endif()
        set(CHARM++_TARGET_NAME "charm++_multicore_gnu")
    elseif(${CHEMSH_ARCH} STREQUAL "intel")
        set(CHARM++_C_COMPILER "icc")
        set(CHARM++_Fortran_COMPILER "ifort")
        # YL 13/07/2024: added support for CHARM++ 8.0 in NAMD 3.0 (a bit redundant now but i'm afraid they could need to be treated separately)
        if(${CHARM++_VERSION} VERSION_GREATER_EQUAL 8.0)
            if("${CMAKE_C_COMPILER_ID}" STREQUAL "IntelLLVM")
                set(CHARM++_C_COMPILER "icx")
            endif()
            if("${CMAKE_Fortran_COMPILER_ID}" STREQUAL "IntelLLVM")
                set(CHARM++_Fortran_COMPILER "ifx")
            endif()
            set(CHARM++_BUILD_DIR "multicore-linux-x86_64-${CHARM++_C_COMPILER}-${CHARM++_Fortran_COMPILER}")
        else()
            if("${CMAKE_C_COMPILER_ID}" STREQUAL "IntelLLVM")
                set(CHARM++_C_COMPILER "icx")
            endif()
            if("${CMAKE_Fortran_COMPILER_ID}" STREQUAL "IntelLLVM")
                set(CHARM++_Fortran_COMPILER "ifx")
            endif()
            set(CHARM++_BUILD_DIR "multicore-linux-x86_64-${CHARM++_Fortran_COMPILER}-${CHARM++_C_COMPILER}")
        endif()
        set(CHARM++_TARGET_NAME "charm++_multicore_intel")
    endif()
endif()

# YL 07/03/2021: set the path anyway because configure_file() is performed at the configure stage
#                and we must determine CHARM++_PATH before that
set(CHARM++_PATH ${CMAKE_CURRENT_LIST_DIR}/${CHARM++_BUILD_DIR}/bin/charmrun)

# if a previous build already exists
if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/${CHARM++_BUILD_DIR}/bin/charmc AND EXISTS "${CHARM++_PATH}" AND NOT CHARM++_RECOMPILE)

    message("${CHARM++_NAME} was already successfully built under")
    message("${CMAKE_CURRENT_LIST_DIR}/${CHARM++_BUILD_DIR}")
    set(REBUILD_CHARM++ OFF)
    set(CHARM++_BUILD_CMD "")
    # add a virtual target so that NAMD can be processed because this is still configure stage
    if(NOT TARGET ${CHARM++_TARGET_NAME})
        add_custom_target(${CHARM++_TARGET_NAME})
    endif()

# if Charm++ still needs to be built
else()
    set(REBUILD_CHARM++ ON)
    message("${CHARM++_NAME} has not been successfully built")
    message("Removing previous build directory ${CHARM++_BUILD_DIR}...")
    file(REMOVE_RECURSE ${CMAKE_CURRENT_LIST_DIR}/${CHARM++_BUILD_DIR})
    FetchContent_Populate(
        charm++
        URL          ${CHARM++}
        SOURCE_DIR   ${CHARM++_SRC_DIR}
        BINARY_DIR   ${CHARM++_BINARY_DIR}/build
        SUBBUILD_DIR ${CHARM++_BINARY_DIR}/subbuild
    )
    # YL 05/01/2021: some versions of awk (e.g., mawk 1.3.4 with bash 5.0.17) inserts a new line by default and causes configure to fail
    if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/src/src/scripts/configure)
        if(NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/src/src/scripts/configure.BAK)
            file(READ ${CMAKE_CURRENT_LIST_DIR}/src/src/scripts/configure CHARM++_CONFIGURE)
            file(RENAME ${CMAKE_CURRENT_LIST_DIR}/src/src/scripts/configure ${CMAKE_CURRENT_LIST_DIR}/src/src/scripts/configure.BAK)
        else()
            file(READ ${CMAKE_CURRENT_LIST_DIR}/src/src/scripts/configure.BAK CHARM++_CONFIGURE)
        endif()
        # must use quotes about ${CHARM++_CONFIGURE}!
        string(REPLACE "1 | awk {" "1 | awk -v ORS= {" CHARM++_CONFIGURE.PATCHED "${CHARM++_CONFIGURE}")
        file(WRITE ${CMAKE_CURRENT_LIST_DIR}/configure "${CHARM++_CONFIGURE.PATCHED}")
        # change permissions from "644" to "755"
        file(COPY ${CMAKE_CURRENT_LIST_DIR}/configure DESTINATION ${CMAKE_CURRENT_LIST_DIR}/src/src/scripts FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
        message("ChemShell has patched Charm++'s configure file")
    endif()
    set(CHARM++_BUILD_CMD ${CHARM++_ENV} bash ${CHARM++_SRC_DIR}/build charm++ ${NAMD_ARCH} ${CHARM++_C_COMPILER} ${CHARM++_Fortran_COMPILER} --with-production --force)
    # NB: MATCHES will catch both gnu and cpe-gnu!
    if(${CHEMSH_ARCH} MATCHES "gnu")
        if("${CHARM++_EXTRA_OPTS}" STREQUAL "")
            set(CHARM++_BUILD_CMD ${CHARM++_ENV} bash ${CHARM++_SRC_DIR}/build charm++ ${NAMD_ARCH} ${CHARM++_C_COMPILER} ${CHARM++_Fortran_COMPILER} --with-production -w ${CHARM++_EXTRA_OPTS} --force)
        else()
            set(CHARM++_BUILD_CMD ${CHARM++_ENV} bash ${CHARM++_SRC_DIR}/build charm++ ${NAMD_ARCH} ${CHARM++_C_COMPILER} ${CHARM++_Fortran_COMPILER} --with-production --force)
        endif()
    endif()

    ExternalProject_Add(
        ${CHARM++_TARGET_NAME}
        PREFIX              ${CMAKE_CURRENT_LIST_DIR}/${CHEMSH_ARCH}
        STAMP_DIR           ${CMAKE_CURRENT_LIST_DIR}/${CHEMSH_ARCH}/stamp
        TMP_DIR             ${CMAKE_CURRENT_LIST_DIR}/${CHEMSH_ARCH}/tmp
        BINARY_DIR          ${CMAKE_CURRENT_LIST_DIR}
        DOWNLOAD_COMMAND    ""
        UPDATE_COMMAND      ""
        CONFIGURE_COMMAND   ""
        BUILD_COMMAND       ${CHARM++_BUILD_CMD}
        BUILD_ALWAYS        ${REBUILD_CHARM++}
        INSTALL_COMMAND     ""
        WORKING_DIRECTORY   ${CHARM++_SRC_DIR}/${CHEMSH_ARCH}/charm++/subbuild
    )

endif()

configure_file(${CMAKE_CURRENT_LIST_DIR}/__init__.py.in ${CMAKE_CURRENT_LIST_DIR}/__init__.py @ONLY)


set_directory_properties(PROPERTIES
    ADDITIONAL_MAKE_CLEAN_FILES "${CMAKE_CURRENT_LIST_DIR}/src;${CMAKE_CURRENT_LIST_DIR}/${CHARM++_BUILD_DIR};${CMAKE_CURRENT_LIST_DIR}/VERSION;${CMAKE_CURRENT_LIST_DIR}/bin;${CMAKE_CURRENT_LIST_DIR}/include;${CMAKE_CURRENT_LIST_DIR}/lib;"
)
