# Build GULP as a static library using its native Makefile

#  Copyright (C) 2017 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
# June 2015

# paths
file(MAKE_DIRECTORY ${CHEMSH_BUILD_DIR}/lib/gulp)
set(GULP_LIB_DIRECTORY ${CHEMSH_BUILD_DIR}/lib/gulp)
set(CMAKE_Fortran_MODULE_DIRECTORY ${CHEMSH_BUILD_DIR}/modules/gulp)
include_directories(${CHEMSH_BUILD_DIR}/modules
                    ${CHEMSH_BUILD_DIR}/modules/parallel
                    ${CHEMSH_BUILD_DIR}/modules/dl_py2f)

if(NOT ${GULP} STREQUAL "")
    get_filename_component(GULP ${GULP} ABSOLUTE)
endif()

if(EXISTS ${GULP})
    # if is directory
    if(IS_DIRECTORY ${GULP})
        set(GULP_DIR     ${GULP})
        set(GULP_SRC_DIR ${GULP_DIR}/Src)
        set(GULP_UTILS_DIR ${GULP_DIR}/Utils)
        set(GULP_LIBS_PATH ${GULP_DIR}/Libraries)
    # if file
    else()
        get_filename_component(GULP_ABSOLUTE "${GULP}" ABSOLUTE)
        set(GULP_DIR        ${CMAKE_CURRENT_SOURCE_DIR}/src)
        set(GULP_BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/${CHEMSH_ARCH})
        set(GULP_SRC_DIR    ${GULP_DIR}/Src)
        set(GULP_UTILS_DIR  ${GULP_SRC_DIR}/Utils)
        set(GULP_LIBS_PATH  ${GULP_SRC_DIR}/Libraries)
        # YL 15/06/2023: FetchContent is not available until CMake 3.11
        #                this is a copy of FetchContent 3.16 in chemsh/utils/cmake
        if(${GULP_RECOMPILE})
            file(REMOVE_RECURSE ${GULP_DIR})
            file(REMOVE_RECURSE ${GULP_BINARY_DIR})
        endif()
        FetchContent_Declare(
            gulp_source
            URL ${GULP_ABSOLUTE}
            DOWNLOAD_DIR ${CMAKE_CURRENT_LIST_DIR}
        )
        FetchContent_GetProperties(gulp_source)
        if(NOT gulp_source_POPULATED)
            FetchContent_Populate(
                gulp_source
                URL          ${GULP_ABSOLUTE}
                SOURCE_DIR   ${GULP_DIR}
                BINARY_DIR   ${GULP_BINARY_DIR}/build
                SUBBUILD_DIR ${GULP_BINARY_DIR}/subbuild
            )
        endif()
    endif()

    configure_file(gulp_libs_path.py.in gulp_libs_path.py @ONLY)

    # get the GULP version number from the README file
    file(READ ${GULP_DIR}/README GULP_VERSION LIMIT 30)
    string(REGEX MATCH "[+-]?([0-9]*[.])?[0-9]+" GULP_VERSION ${GULP_VERSION})
    set(GULP_NAME gulp-${GULP_VERSION})

    # determine if serial or parallel build required
    message("================================================")
    if(WITH_MPI)
#        find_package(ScaLAPACK REQUIRED)
        message("Parallel GULP ${GULP_VERSION} will be compiled with MPI")
        set(GULP_DEFS "-DNOFOX -DMPI -DNOGFNFF ")
        set(GULP_MPI "parallel")
    else()
        message("Serial GULP ${GULP_VERSION} will be compiled. Use --mpi to compile a parallel version.")
        set(GULP_MPI "serial")
        set(GULP_DEFS "-DNOFOX -DNOGFNFF ")
    endif()
    message("================================================")

    # since GULP 5.0, build is undertaken in a subdir
    if(${GULP_VERSION} GREATER_EQUAL 5.0)
        file(MAKE_DIRECTORY ${GULP_SRC_DIR}/chemsh-${CHEMSH_ARCH}-${GULP_MPI})
        set(GULP_WORKING_DIR ${GULP_SRC_DIR}/chemsh-${CHEMSH_ARCH}-${GULP_MPI})
        set(GULP_STATIC_LIB_DIR ${GULP_WORKING_DIR})
        file(COPY ${GULP_SRC_DIR}/Makefile DESTINATION ${GULP_WORKING_DIR})
        set(GULP_ScaLAPACK_FLAGS ${CHEMSH_ScaLAPACK_FLAGS})
    else()
        set(GULP_WORKING_DIR ${GULP_SRC_DIR})
        set(GULP_STATIC_LIB_DIR ${GULP_DIR})
    endif()

    # to make static lib linkable from a shared lib
    # always build GULP 32-bit
    set(GULP_FFLAGS "${CMAKE_Fortran_FLAGS} -fPIC")
    set(GULP_CFLAGS "${CMAKE_C_FLAGS} -fPIC")
    # since GULP 5.0, ARCHIVE and RANLIB are no longer defined in Makefile but injected from script mkgulp
    set(GULP_ARCHIVE "ar -rcv")
    set(GULP_RANLIB  "ar -ts")

    # we use 32-bit integer for GULP
    if(${CHEMSH_ARCH} STREQUAL "intel")
        string(REPLACE "-i8"                 "-i4" GULP_FFLAGS ${GULP_FFLAGS})
        if(NOT DEBUG)
            set(GULP_FFLAGS "${GULP_FFLAGS} -diag-disable=all")
        endif()
    elseif(${CHEMSH_ARCH} STREQUAL "gnu" OR ${CHEMSH_ARCH} STREQUAL "cpe-gnu")
        string(REPLACE "-fdefault-integer-8" ""    GULP_FFLAGS ${GULP_FFLAGS})
        # YL 08/03/2021: -Wno-all doesn't work still with seeing
        #                ../dump34.F90:302:0:
        #                  302 |           wboty(nboty) = botyword(nbotyptr(nboty))
        #                      |
        #                Warning: ‘__builtin_memcpy’ reading 9 bytes from a region of size 0 [-Wstringop-overflow=]
        if(NOT DEBUG)
            set(GULP_FFLAGS "${GULP_FFLAGS} -Wno-all")
            set(GULP_CFLAGS "${GULP_CFLAGS} -Wno-all")
        else()
            set(GULP_FFLAGS "${GULP_FFLAGS} -g -O0")
            set(GULP_CFLAGS "${GULP_CFLAGS} -g -O0")
        endif()
    endif()
    
    # switch: recompile
    if(DEBUG)
        message("\n >>> ChemShell CMAKE: GULP build command:\nmake -j1 RUNF90=${CMAKE_Fortran_COMPILER} FFLAGS=\"${GULP_FFLAGS}\" RUNCC=${CMAKE_C_COMPILER} CFLAGS=\"${GULP_CFLAGS}\" DEFS=\"${GULP_DEFS}\" ARCHIVE=\"${GULP_ARCHIVE}\" RANLIB=\"${GULP_RANLIB}\" lib > make.log\n")
    endif()
    set(GULP_LD_FLAGS "${CHEMSH_MKL_FLAGS} ${CHEMSH_ScaLAPACK_FLAGS}")
    set(GULP_MAKE_COMMAND make -j1 RUNF90=${CMAKE_Fortran_COMPILER} FFLAGS=${GULP_FFLAGS} RUNCC=${CMAKE_C_COMPILER} CFLAGS=${GULP_CFLAGS} DEFS=${GULP_DEFS} ARCHIVE=${GULP_ARCHIVE} RANLIB=${GULP_RANLIB} LDFLAGS=${GULP_LD_FLAGS} lib > make.log)
    set(GULP_INSTALL_COMMAND ${CMAKE_COMMAND} -E copy ${GULP_STATIC_LIB_DIR}/libgulp.a ${GULP_LIB_DIRECTORY})
    if(${GULP_RECOMPILE})
        set(GULP_RECOMPILE 1)
        file(REMOVE_RECURSE ${CHEMSH_ARCH}/stamp)
    else()
        if(EXISTS ${GULP_LIB_DIRECTORY}/libgulp.a)
            set(GULP_RECOMPILE 0)
            set(GULP_MAKE_COMMAND "")
            set(GULP_INSTALL_COMMAND "")
            message("${BoldRed}GULP_RECOMPILE${EndColour} set to 0: GULP source code will not be recompiled")
        else()
            set(GULP_RECOMPILE 1)
            file(REMOVE_RECURSE ${CHEMSH_ARCH}/stamp)
            message("${BoldRed}GULP_RECOMPILE${EndColour} set to 0 but ${GULP_LIB_DIRECTORY}/libgulp.a not found: GULP source code will still be recompiled ${GULP_RECOMPILE}")
        endif()
    endif()

    # YL 02/07/2023: this allows to compile only modified source files in GULP without recompiling the whole package which is very slow
    if(RECOMPILE_WITHOUT_DOWNLOAD)
        set(GULP_MAKE_CLEAN_COMMAND "")
    else()
        set(GULP_MAKE_CLEAN_COMMAND make clean COMMAND make libclean)
    endif()
    # compile GULP as an external project using its original Makefile
    #   PATCH_COMMAND   : no need to patch so far; if patching note cross-platform issues!
    #   DOWNLOAD_COMMAND: should not be set
    #   DOWNLOAD_DIR    : should not be set
    #   BINARY_DIR      : should not be set
    #   BUILD_IN_SOURCE : not the case
    #   BUILD_ALWAYS    : not needed since we use ALWAYS in ExternalProject_Add_Step instead to enforce a rebuild
    #   SOURCE_DIR      : destination of extraction
    #   INSTALL_COMMAND : make copy to keep different binaries of GULP; this is in command-line mode so make sure use full absolute path!
    include(ExternalProject)
    # YL 03/07/2023: patch to fix a bug in GULP until it's officially repaired
    ExternalProject_Add(
        ${GULP_NAME}
        PREFIX            ${CHEMSH_ARCH}
        STAMP_DIR         ${CHEMSH_ARCH}/stamp
        TMP_DIR           ${CHEMSH_ARCH}/tmp
        BUILD_ALWAYS      ${GULP_RECOMPILE}
        SOURCE_DIR        ${GULP_DIR}
        URL               file://${GULP}
        CONFIGURE_COMMAND ""
        DOWNLOAD_COMMAND  ""
        PATCH_COMMAND     ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/patch.py ${GULP_SRC_DIR} changemaxat.F90 COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/patch.py ${GULP_SRC_DIR} changemaxr1at.F90 COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/patch.py ${GULP_SRC_DIR} initial.F90 COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/patch.py ${GULP_SRC_DIR} potwordm.F90
        BUILD_COMMAND     ${CMAKE_COMMAND} -E cmake_echo_color --switch=$(COLOR) --magenta --bold "Architecture for which '${GULP_NAME}' is built: '${CHEMSH_ARCH}'"
        INSTALL_COMMAND   ""#${CMAKE_COMMAND} -E copy ${GULP_SRC_DIR}/../libgulp.a ${GULP_LIB_DIRECTORY}
    )
    
    ExternalProject_Add_Step(
        ${GULP_NAME} GULP_CONFIGURE
        COMMENT           "Cleaning up previous binaries for '${GULP_NAME}'..."
        ALWAYS            ${GULP_RECOMPILE}
        DEPENDEES         configure
        COMMAND           ${GULP_MAKE_CLEAN_COMMAND}
        WORKING_DIRECTORY ${GULP_WORKING_DIR}
    )
    
    ExternalProject_Add_Step(
        ${GULP_NAME} GULP_BUILD
        COMMENT           "Building static library for '${GULP_NAME}'..."
        ALWAYS            ${GULP_RECOMPILE}
        # always preceded by GULP_CONFIGURE
        DEPENDEES         GULP_CONFIGURE
        COMMAND           ${GULP_MAKE_COMMAND}
        WORKING_DIRECTORY ${GULP_WORKING_DIR}
    )
    
    ExternalProject_Add_Step(
        ${GULP_NAME} GULP_INSTALL
        COMMENT           "Installing static library to ${GULP_LIB_DIRECTORY}"
        ALWAYS            ${GULP_RECOMPILE}
        # always preceded by GULP_BUILD
        DEPENDEES         GULP_BUILD
        COMMAND           ${GULP_INSTALL_COMMAND}
        WORKING_DIRECTORY ${GULP_WORKING_DIR}
    )
    
    # compile libgulp.so (containing only Py-ChemShell interface files)
    add_library(gulp SHARED gulp.f90 gulp_routines.f90 ${LINKED})

    # add_dependencies to make sure target_link_libraries is performed only after ExternalProject_Add_Step is finished
    add_dependencies(gulp ${GULP_NAME})

    # link the static library agains libgulp.so
    target_link_libraries(gulp dl_py2f parallel)

    if(${CHEMSH_ARCH} STREQUAL "intel")
        target_link_libraries(gulp "${CHEMSH_MKL_FLAGS}")
    elseif(${CHEMSH_ARCH} STREQUAL "gnu")
        find_package(BLAS)
        find_package(LAPACK)
        target_link_libraries(gulp blas lapack)
    elseif(${CHEMSH_ARCH} STREQUAL "cpe-gnu")
        set_target_properties(gulp PROPERTIES COMPILE_FLAGS "-cpp")
    endif()

    # YL 14/10/2022: we currently don't support GULP 6.1's ${GULP_UTILS_DIR}/pGFNFF/Src/libpGFNFF.a \
    set_target_properties(gulp PROPERTIES LINK_FLAGS "-Wl,--whole-archive             \
                                                      ${GULP_LIB_DIRECTORY}/libgulp.a \
                                                      -Wl,--no-whole-archive          \
                                                      -Wl,--version-script=${CMAKE_CURRENT_LIST_DIR}/export.map \
                                                      ${GULP_ScaLAPACK_FLAGS}         \
                                                      ${CHEMSH_MKL_FLAGS}")

# without GULP
else()

    set(GULP_NAME gulp-NA)
#    message("${BoldRed}GULP (${GULP}) does not exist. Skipping...${EndColour}")
    # compile libgulp.so
    add_library(gulp SHARED gulp.f90 gulp_routines.f90 gulp_dummies.f90 ${NOTLINKED})

    target_link_libraries(gulp dl_py2f parallel)

endif()

# FoX libs
#file(GLOB_RECURSE GULP_FOXLIBS ${GULP_SRC_DIR}/FoX-*/objs/lib/*.a)

# delete the existing library anyway, if gulp is recompiled
#file(REMOVE ${CMAKE_CURRENT_SOURCE_DIR}/../../lib/${CHEMSH_ARCH}/libgulp.a)


# TWK: GULP 4.3 puts libgulp.a in parent directory of src
# must give a full absolute path!

# make sure the library file is always up-to-date
# try add_custom_command()?
#if(EXISTS ${GULP_SRC_DIR}/../libgulp.a)
#  file(INSTALL ${GULP_SRC_DIR}/../libgulp.a DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/../../lib/${CHEMSH_ARCH})
#endif()

# only when FoX used:
#target_link_libraries(gulp ${GULP_FOXLIBS})
