# Build CP2K 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

set(CP2K_LATEST_RELEASE_VERSION "2022.2")

include(ExternalProject)

# paths
set(CHEMSH_CP2K_LIB_DIR       ${CHEMSH_BUILD_DIR}/lib/cp2k)
set(CHEMSH_CP2K_TOOLCHAIN_DIR ${CHEMSH_CP2K_LIB_DIR}/toolchains)
file(MAKE_DIRECTORY ${CHEMSH_CP2K_LIB_DIR})
file(MAKE_DIRECTORY ${CHEMSH_CP2K_TOOLCHAIN_DIR})
set(CMAKE_Fortran_MODULE_DIRECTORY ${CHEMSH_BUILD_DIR}/modules/cp2k)
include_directories(${CHEMSH_BUILD_DIR}/modules
                    ${CHEMSH_BUILD_DIR}/modules/parallel
                    ${CHEMSH_BUILD_DIR}/modules/dl_py2f)


# without CP2K
if("${CP2K}" STREQUAL "")
    # compile libcp2k.so with dummy source files
    add_library(cp2k SHARED cp2k.f90 cp2k_dummies.f90 ${NOTLINKED})
    target_link_libraries(cp2k dl_py2f parallel)
    return()
endif()

get_filename_component(CP2K_ABSOLUTE "${CP2K}" ABSOLUTE)

# determine if serial or parallel build required
if(WITH_MPI)
    message("CP2K library will be compiled parallel with MPI")
    set(CP2K_BUILD_VERSION "psmp")
    set(CP2K_PARALLEL_MACROS "-D__parallel")
    set(CP2K_ScaLAPACK_FLAGS ${CHEMSH_ScaLAPACK_FLAGS})
    set(CP2K_BUILD_ARCH "chemsh-${CHEMSH_ARCH}-mpi")
else()
    message("CP2K library will be compiled serial. Specify --mpi to compile a parallel version")
    set(CP2K_BUILD_VERSION "ssmp")
    set(CP2K_BUILD_ARCH "chemsh-${CHEMSH_ARCH}")
endif()

# from user-specified location
if(EXISTS ${CP2K_ABSOLUTE})

    # if is directory
    if(IS_DIRECTORY ${CP2K})
        set(CP2K_DIR       ${CP2K})
        set(CP2K_SRC_DIR   ${CP2K_DIR}/src)
        set(CP2K_LIB_DIR   ${CP2K_DIR}/lib/${CP2K_BUILD_ARCH}/${CP2K_BUILD_VERSION})
        # currently not in use
        set(CP2K_TOOLS_DIR ${CP2K_DIR}/tools/toolchain/install)
    # if file
    else()
        set(CP2K_DIR     ${CMAKE_CURRENT_SOURCE_DIR})
        set(CP2K_SRC_DIR ${CP2K_DIR}/src)
    endif()
    set(CP2K_DATA_DIR ${CP2K_DIR}/data)

    # get the CP2K version number from the source file
    if("${CP2K_VERSION}" STREQUAL "")
        file(READ ${CP2K_SRC_DIR}/cp2k_info.F CP2K_INFO LIMIT 3000)
        # TWK 11/10/2022 This finds the version number line for CP2K versions < 9.1
        string(REGEX MATCH "version_nr = \"[+-]?([0-9]*[.])?[0-9]+" CP2K_VERSION ${CP2K_INFO})
        if("${CP2K_VERSION}" STREQUAL "")
            # TWK 11/10/2022 This finds the version number line for CP2K from 2022 onwards
            string(REGEX MATCH "cp2k_version = \"CP2K version [+-]?([0-9]*[.])?[0-9]+" CP2K_VERSION ${CP2K_INFO})
        endif()
        # TWK 11/10/2022 modified to ensure final number on the line is chosen
        string(REGEX MATCH "[+-]?([0-9]*[.])?[0-9]+$" CP2K_VERSION ${CP2K_VERSION})
        set(CP2K_NAME cp2k-${CP2K_VERSION})
    endif()
    if("${CP2K_VERSION}" VERSION_LESS 8.0)
        message(FATAL_ERROR " ERROR: We no longer support CP2K version earlier than 8.0. Please upgrade!")
    endif()

    set(CP2K_NAME cp2k-${CP2K_VERSION})
# from the GitHub repository
else()
    message("${BoldWhite}CP2K${EndColour} repository URL: ${CP2K}")
    set(CP2K_VERSION   ${CP2K_LATEST_RELEASE_VERSION})
    set(CP2K_NAME      cp2k-${CP2K_VERSION})
    set(CP2K_GIT       ON)
    set(CP2K_GIT_REPO  ${CP2K})
    set(CP2K_GIT_TAG   "v${CP2K_VERSION}")
    set(CP2K_TARBALL   "")
    set(CP2K_DIR       ${CMAKE_CURRENT_LIST_DIR}/${CHEMSH_ARCH}/src/${CP2K_NAME})
    set(CP2K_SRC_DIR   ${CP2K_DIR}/src)
    set(CP2K_DATA_DIR  ${CP2K_DIR}/data)
    set(CP2K_LIB_DIR   ${CP2K_DIR}/lib/${CP2K_BUILD_ARCH}/${CP2K_BUILD_VERSION})
    # currently not in use
    set(CP2K_TOOLS_DIR ${CP2K_DIR}/tools/toolchain/install)
endif()

# to make static lib linkable from a shared lib
# always build CP2K 32-bit
set(CP2K_FFLAGS "${CMAKE_Fortran_FLAGS} -fPIC")
set(CP2K_CFLAGS "${CMAKE_C_FLAGS} -std=c99 -fPIC")

# these variales are to be used by cp2k_arch.in
set(LD ${CMAKE_Fortran_COMPILER})
if(${CHEMSH_ARCH} STREQUAL "intel")

    # YL 05/03/2021: Intel 17 results in wrong
    if(${CMAKE_Fortran_COMPILER_VERSION} VERSION_LESS 18.0)
        message(FATAL_ERROR "\nCP2K v{CP2K_VERSION} requires Intel compilers of at least v18.0, the current v${CMAKE_Fortran_COMPILER_VERSION} results in wrong energy!\n")
    endif()

    # always use 32-bit integers
    string(REPLACE "-i8" "-i4" CP2K_FFLAGS ${CP2K_FFLAGS})

    # compile macros
    set(CP2K_MACROS "-D__MKL -D__INTEL -D__FFTW3 -D__DATA_DIR=\\\"${CP2K_DATA_DIR}\\\"")

    # parallel version
    if(WITH_MPI)
        set(CP2K_MACROS "${CP2K_MACROS} ${CP2K_PARALLEL_MACROS} -D__SCALAPACK")
    else()
        set(CP2K_MACROS "${CP2K_MACROS} ${CP2K_PARALLEL_MACROS}")
    endif()

    # debug
    if(DEBUG)
        set(CP2K_FFLAGS "${CP2K_FFLAGS} -fpp -free -fopenmp")
        set(CP2K_CFLAGS "${CP2K_CFLAGS} -funroll-loops -fopenmp -traceback -xHost")
    else()
        set(CP2K_FFLAGS "${CP2K_FFLAGS} -fpp -free -fopenmp -diag-disable=6843,8291,8293")
        set(CP2K_CFLAGS "${CP2K_CFLAGS} -funroll-loops -fopenmp -traceback -xHost -wd167")
    endif()

    # FFTW
    set(CP2K_FFLAGS "${CP2K_FFLAGS} ${MKL_COMPILE_FLAGS} -I${FFTW_INCLUDE_DIR} ${CP2K_MACROS}")
    set(CP2K_CFLAGS "${CP2K_CFLAGS} ${MKL_COMPILE_FLAGS} -I${FFTW_INCLUDE_DIR}")
    # YL TODO 05/03/2021: define MKLROOT in setup and platform.py when the MKL flags Issue is addressed
    get_filename_component(MKLROOT ${MKL}/../.. ABSOLUTE)
    set(FFTW_INTERFACE_DIR ${MKLROOT}/interfaces/fftw3xf)
    set(FFTW3_CFLAGS "-fPIC -Wall -Werror")
    # do NOT use -j for multi-jobs building because when one thread deletes the dir containing the object files others are still compiling (bug in MKL makefile!)
    set(FFTW3_BUILD_COMMAND make -j 1 libintel64 compiler=${CHEMSH_ARCH} INSTALL_DIR=${CHEMSH_CP2K_LIB_DIR} _cflags_intel_diag=${FFTW3_CFLAGS})
    set(CHEMSH_CP2K_LINK_FLAGS "-qopenmp ${CHEMSH_MKL_FLAGS}")

    # link flags
    set(CP2K_LDFLAGS "-qopenmp ${CHEMSH_MKL_FLAGS}")
    if(${CMAKE_Fortran_COMPILER_VERSION} VERSION_GREATER_EQUAL 20.0)
        set(CP2K_C_LDFLAGS "-nofor-main")
    else()
        set(CP2K_C_LDFLAGS "-nofor_main")
    endif()

elseif(${CHEMSH_ARCH} STREQUAL "gnu" OR ${CHEMSH_ARCH} STREQUAL "cpe-gnu")

    # always use 32-bit integers
    string(REPLACE "-fdefault-integer-8" "" CP2K_FFLAGS ${CP2K_FFLAGS})

    # compile macros
#    set(CP2K_MACROS "-D__DATA_DIR=\\\"${CP2K_DATA_DIR}\\\" -D__COMPILE_ARCH=\\\"${CP2K_BUILD_ARCH}\\\"")
    set(CP2K_MACROS "-D__DATA_DIR=\\\"${CP2K_DATA_DIR}\\\"")

    # parallel version
    if(WITH_MPI)
        set(CP2K_MACROS "${CP2K_MACROS} ${CP2K_PARALLEL_MACROS}")
    else()
        # we don't use these -U for Intel which issues tons of warnings
        set(CP2K_PARALLEL_MACROS "-U__parallel -U__SCALAPACK")
        set(CP2K_MACROS "${CP2K_MACROS} ${CP2K_PARALLEL_MACROS}")
    endif()

    # ScaLAPACK
    if(WITH_ScaLAPACK)
        set(CP2K_MACROS "${CP2K_MACROS} -D__SCALAPACK")
    endif()

    # FFTW
    if(WITH_FFTW)
        set(CP2K_FFTW_LINK_FLAGS "-L${FFTW_DIR} -lfftw3 -lfftw3_threads -lfftw3_omp")
        set(CP2K_MACROS "${CP2K_MACROS} -D__FFTW3")
    endif()

#    set(CP2K_FFLAGS "${CP2K_FFLAGS} -cpp -ffree-form -ffree-line-length-none -ftree-vectorize -fopenmp -mtune=native ${CP2K_MACROS} -D__DATA_DIR=\\\"${CP2K_DATA_DIR}\\\" -D__COMPILE_ARCH=\\\"${CP2K_BUILD_ARCH}\\\" -D__COMPILE_HOST=\\\"$ENV{USER}\\\" -D__COMPILE_DATE=\\\"${CHEMSH_COMPILE_TIME}\\\"")
    if(DEBUG)
        #Rajany: added fcheck=no-array-temps to avoid warnings about temp arrays flooding outfile.
        set(CP2K_FFLAGS "${CP2K_FFLAGS} -cpp -ffree-form -ffree-line-length-none -fopenmp -Wno-array-temporaries -fcheck=no-array-temps")
        set(CP2K_CFLAGS "${CP2K_CFLAGS} -ftree-vectorize -fopenmp -mtune=native")
    else()
        set(CP2K_FFLAGS "${CP2K_FFLAGS} -cpp -ffree-form -ffree-line-length-none -fopenmp -Wno-array-temporaries -fcheck=no-array-temps")
        set(CP2K_CFLAGS "${CP2K_CFLAGS} -ftree-vectorize -fopenmp -mtune=native")
    endif()

    # FFTW
    set(CP2K_FFLAGS "${CP2K_FFLAGS} -I${FFTW_INCLUDE_DIR} ${CP2K_MACROS}")
    set(CP2K_CFLAGS "${CP2K_CFLAGS} -I${FFTW_INCLUDE_DIR}")

    # link flags
    set(CP2K_LDFLAGS   "${CP2K_FFTW_LINK_FLAGS}")
    # ScaLAPACK
    if(WITH_ScaLAPACK)
        set(CP2K_LDFLAGS "${CP2K_LDFLAGS} ${CHEMSH_ScaLAPACK_FLAGS}")
    endif()
    set(CP2K_C_LDFLAGS "")

#    set(CP2K_WFLAGS "-Werror=aliasing -Werror=ampersand -Werror=c-binding-type -Werror=intrinsic-shadow -Werror=intrinsics-std -Werror=tabs -Werror=realloc-lhs-all -Werror=target-lifetime -Werror=underflow -Werror=unused-but-set-variable -Werror=unused-variable -Werror=unused-dummy-argument -Werror=conversion -Werror=zerotrip -Werror=uninitialized -Wno-maybe-uninitialized -Wuse-without-only")
    # -fopenmp is also needed for linking by gcc/gfortran
    set(CHEMSH_CP2K_LINK_FLAGS "${CP2K_FFTW_LINK_FLAGS} -fopenmp")

endif()

   
#list(APPEND CP2K_TOOLCHAIN_LIBS libfftw3xf_intel.a)
list(APPEND CHEMSH_CP2K_EXTRA_LIBS ${CP2K_LIB_DIR}/exts/dbcsr/libdbcsr.a)

# YL 23/02/2021: we support 8.1 now which allows building a single archive
list(APPEND CP2K_STATIC_LIBS libcp2k.a)

foreach(CP2K_STATIC_LIB ${CP2K_STATIC_LIBS})
    # a list of source static libs to copy: should NOT include quotation marks
    set(CP2K_STATIC_LIBS_FULLPATH ${CP2K_STATIC_LIBS_FULLPATH} ${CP2K_LIB_DIR}/${CP2K_STATIC_LIB})
    # note: by this point the libs have not yet been copied. this is only an assumed full list of them
    set(CHEMSH_CP2K_STATIC_LIBS "${CHEMSH_CP2K_STATIC_LIBS} ${CHEMSH_CP2K_LIB_DIR}/${CP2K_STATIC_LIB}")
endforeach(CP2K_STATIC_LIB)

needRebuild(CP2K_NEEDS_RECOMPILE CP2K_REINSTALL CP2K_STATIC_LIBS CP2K_LIB_DIR CHEMSH_CP2K_LIB_DIR)

# include
include_directories(${CP2K_DIR}/obj/${CP2K_BUILD_ARCH}/${CP2K_BUILD_VERSION}
                    ${CP2K_DIR}/obj/${CP2K_BUILD_ARCH}/${CP2K_BUILD_VERSION}/exts/dbcsr
                   )
if(NOT EXISTS ${CP2K_DIR}/obj/${CP2K_BUILD_ARCH}/${CP2K_BUILD_VERSION})
    message("No pre-compiled objects found. CP2K needs to be recompiled.")
    set(CP2K_NEEDS_RECOMPILE ON)
endif()

# get CP2K arch file and build command ready
set(CP2K_ARCH_FILE ${CP2K_BUILD_ARCH}.${CP2K_BUILD_VERSION})
configure_file(cp2k_arch.in ${CP2K_ARCH_FILE} @ONLY)

# NB: the flags are passed via the arch file rather than make command
set(CP2K_BUILD_COMMAND make -j ${CHEMSH_MAKE_NJOBS} ARCH=${CP2K_BUILD_ARCH} VERSION=${CP2K_BUILD_VERSION} libcp2k)
#message(FATAL_ERROR "CP2K_BUILD_COMMAND = make -j ${CHEMSH_MAKE_NJOBS} ARCH=${CP2K_BUILD_ARCH} VERSION=${CP2K_BUILD_VERSION} F77=${CMAKE_Fortran_COMPILER} FCFLAGS=${CP2K_FFLAGS} CFLAGS=${CP2K_CFLAGS} LDFLAGS=${CP2K_LDFLAGS}")

set(CP2K_INSTALL_COMMAND ${CMAKE_COMMAND} -E copy ${CP2K_STATIC_LIBS_FULLPATH} ${CHEMSH_CP2K_LIB_DIR})

# forced to recompile and CP2K objects already exist
if(CP2K_RECOMPILE AND NOT CP2K_NEEDS_RECOMPILE)
    # CMake stupidly only accepts 0 or 1 in ExternalProject_Add()
    set(CP2K_CLEAN_COMMAND1 make clean)
    set(CP2K_CLEAN_COMMAND2 make distclean)
    file(REMOVE_RECURSE ${CHEMSH_ARCH}/stamp)
# forced to recompile but CP2K objects do not exist
elseif(CP2K_RECOMPILE AND CP2K_NEEDS_RECOMPILE)
# not forced to recompile but CP2K objects do not exist
elseif(NOT CP2K_RECOMPILE AND CP2K_NEEDS_RECOMPILE)
# not forced to recompile and CP2K objects already exist
else()
    set(FFTW3_BUILD_COMMAND    "")
    set(CP2K_CONFIGURE_COMMAND "")
    set(CP2K_BUILD_COMMAND     "")
    set(CP2K_INSTALL_COMMAND   "")
endif()

# compile CP2K as an external project using its original Makefile
ExternalProject_Add(
    ${CP2K_NAME}
    GIT_REPOSITORY      ${CP2K_GIT_REPO}
    GIT_TAG             ${CP2K_GIT_TAG}
    GIT_SHALLOW         1
    GIT_PROGRESS        ${DEBUG}
    GIT_CONFIG          advice.detachedHead=false
    UPDATE_DISCONNECTED 0
    UPDATE_COMMAND      pwd
    PREFIX              ${CHEMSH_ARCH}
    STAMP_DIR           ${CHEMSH_ARCH}/stamp
    TMP_DIR             ${CHEMSH_ARCH}/tmp
    BUILD_ALWAYS        ${CP2K_RECOMPILE}
    SOURCE_DIR          ${CP2K_DIR}
    CONFIGURE_COMMAND   ""
    BUILD_COMMAND       ${CMAKE_COMMAND} -E cmake_echo_color --switch=$(COLOR) --magenta --bold "Architecture for which '${CP2K_NAME}' is built: '${CHEMSH_ARCH}'"
    INSTALL_COMMAND     ""
)
    
ExternalProject_Add_Step(
    ${CP2K_NAME} CP2K_CONFIGURE
    COMMENT           "Cleaning up previous binaries for '${CP2K_NAME}'..."
    ALWAYS            ${CP2K_RECOMPILE}
    DEPENDEES         configure
    COMMAND           ${CP2K_CLEAN_COMMAND1} COMMAND ${CP2K_CLEAN_COMMAND2}
    WORKING_DIRECTORY ${CP2K_DIR}
)
    
ExternalProject_Add_Step(
    ${CP2K_NAME} CP2K_FFTW3_BUILD
    COMMENT           "Building FFTW3 library from ${FFTW_INTERFACE_DIR}..."
    ALWAYS            ${CP2K_RECOMPILE}
    DEPENDEES         CP2K_CONFIGURE
    COMMAND           ${FFTW3_BUILD_COMMAND}
    WORKING_DIRECTORY ${FFTW_INTERFACE_DIR}
)

ExternalProject_Add_Step(
    ${CP2K_NAME} CP2K_ARCH
    COMMENT           "Installing CP2K architechture file to ${CP2K_DIR}/arch..."
    ALWAYS            ${CP2K_RECOMPILE}
    DEPENDEES         CP2K_FFTW3_BUILD
    COMMAND           ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/${CP2K_ARCH_FILE} ${CP2K_DIR}/arch
    WORKING_DIRECTORY ${CP2K_DIR}
)

ExternalProject_Add_Step(
    ${CP2K_NAME} CP2K_BUILD
    COMMENT           "Building static library for '${CP2K_NAME}'..."
    ALWAYS            ${CP2K_RECOMPILE}
    # always preceded by CP2K_CONFIGURE
    DEPENDEES         CP2K_ARCH
    COMMAND           ${CMAKE_COMMAND} -E cmake_echo_color --switch=$(COLOR) --white --bold "This may take a while. Please be patient..."
    COMMAND           ${CP2K_BUILD_COMMAND}
    WORKING_DIRECTORY ${CP2K_DIR}
)
ExternalProject_Add_Step(
    ${CP2K_NAME} CP2K_INSTALL
    COMMENT           "Installing static library to ${CHEMSH_CP2K_LIB_DIR}..."
    ALWAYS            ${COPY_CP2K_LIBS}
    # always preceded by CP2K_BUILD
    DEPENDEES         CP2K_BUILD
    COMMAND           ${CP2K_INSTALL_COMMAND}
    WORKING_DIRECTORY ${CP2K_DIR}
)

# cp2k_base_uses.f90
add_custom_target(cp2k_base_uses)
add_custom_command(TARGET cp2k_base_uses PRE_BUILD
                   COMMAND ${CMAKE_COMMAND} -E copy ${CP2K_SRC_DIR}/base/base_uses.f90 ${CMAKE_CURRENT_LIST_DIR}/cp2k_base_uses.f90)

# compile libcp2k.so (containing only Py-ChemShell interface files)
add_library(cp2k SHARED cp2k.f90 cp2k_routines.f90 ${LINKED})
# dependencies: cp2k -> cp2k_base_uses.f90 -> cp2k-x.x.x -> cp2k_arch_file
add_dependencies(cp2k ${CP2K_NAME} cp2k_base_uses)
add_dependencies(cp2k_base_uses ${CP2K_NAME})
#add_dependencies(${CP2K_NAME} cp2k_arch_target)

# link the static library agains libcp2k.so
target_link_libraries(cp2k dl_py2f parallel)

if(${CHEMSH_ARCH} STREQUAL "intel")
    set_target_properties(cp2k PROPERTIES COMPILE_FLAGS "-fpp -fopenmp")
elseif(${CHEMSH_ARCH} STREQUAL "gnu")
    target_link_libraries(cp2k blas lapack)
    set_target_properties(cp2k PROPERTIES COMPILE_FLAGS "-cpp -ffree-line-length-none -fopenmp")
elseif(${CHEMSH_ARCH} STREQUAL "cpe-gnu")
    set_target_properties(cp2k PROPERTIES COMPILE_FLAGS "-cpp -ffree-line-length-none -fopenmp")
endif()

# CP2K uses the built-in ScaLAPACK provided by the toolchain utility
set_target_properties(cp2k PROPERTIES LINK_FLAGS "-Wl,--whole-archive                  \
                                                  ${CHEMSH_CP2K_STATIC_LIBS}           \
                                                  ${CHEMSH_CP2K_EXTRA_LIBS}            \
                                                  -Wl,--no-whole-archive               \
                                                  ${CHEMSH_CP2K_LINK_FLAGS}            \
                                                  ${CP2K_ScaLAPACK_FLAGS}              \
                                                  -Wl,--version-script=${CMAKE_CURRENT_LIST_DIR}/export.map")
                                                  #-Wl,--allow-multiple-definition      \
                                                  #-Wl,--unresolved-symbols=ignore-all  \
