#  Copyright (C) 2020 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/>.

# Build DL_MONTE as a static library using its native Makefile
# you.lu@stfc.ac.uk
# July 2020

set(DL_MONTE_LATEST_RELEASE_VERSION "2.07")

include(ExternalProject)

# paths
set(CHEMSH_DL_MONTE_LIB_DIR       ${CHEMSH_BUILD_DIR}/lib/dl_monte)
set(CHEMSH_DL_MONTE_TOOLCHAIN_DIR ${CHEMSH_DL_MONTE_LIB_DIR}/toolchains)
file(MAKE_DIRECTORY ${CHEMSH_DL_MONTE_LIB_DIR})
file(MAKE_DIRECTORY ${CHEMSH_DL_MONTE_TOOLCHAIN_DIR})
set(CMAKE_Fortran_MODULE_DIRECTORY ${CHEMSH_BUILD_DIR}/modules/dl_monte)
#${CHEMSH_BUILD_DIR}/modules
include_directories(${CHEMSH_BUILD_DIR}/modules/parallel
                    ${CHEMSH_BUILD_DIR}/modules/dl_py2f)


# without DL_MONTE
if("${DL_MONTE}" STREQUAL "")
    # compile libdl_monte.so with dummy source files
    add_library(dl_monte SHARED dl_monte.f90 dl_monte_dummies.f90 ${NOTLINKED})
    target_link_libraries(dl_monte dl_py2f parallel)
    return()
endif()

get_filename_component(DL_MONTE_ABSOLUTE "${DL_MONTE}" ABSOLUTE)
get_filename_component(DL_MONTE_HTK_ABSOLUTE "${DL_MONTE_HTK}" ABSOLUTE)

set(DL_MONTE_BUILD_ARCH "chemsh-${CHEMSH_ARCH}")


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

    # if is directory
    if(IS_DIRECTORY ${DL_MONTE})
        set(DL_MONTE_DIR       ${DL_MONTE})
        set(DL_MONTE_SRC_DIR   ${DL_MONTE_DIR}/src)
        set(DL_MONTE_LIB_DIR   ${DL_MONTE_DIR}/lib/${DL_MONTE_BUILD_ARCH}/${DL_MONTE_BUILD_VERSION})
        # currently not in use
        set(DL_MONTE_TOOLS_DIR ${DL_MONTE_DIR}/tools/toolchain/install)
    # if file
    else()
        set(DL_MONTE_DIR     ${CMAKE_CURRENT_SOURCE_DIR})
        set(DL_MONTE_SRC_DIR ${DL_MONTE_DIR}/src)
    endif()

    # get the DL_MONTE version number from the source file
    if("${DL_MONTE_VERSION}" STREQUAL "")
        file(READ ${DL_MONTE_SRC_DIR}/dl_monte_info.F DL_MONTE_VERSION LIMIT 3000)
        string(REGEX MATCH "version_nr = \"[+-]?([0-9]*[.])?[0-9]+" DL_MONTE_VERSION ${DL_MONTE_VERSION})
        string(REGEX MATCH "[+-]?([0-9]*[.])?[0-9]+" DL_MONTE_VERSION ${DL_MONTE_VERSION})
        set(DL_MONTE_NAME dl_monte-${DL_MONTE_VERSION})
    endif()

    set(DL_MONTE_NAME dl_monte-${DL_MONTE_VERSION})
    set(DL_MONTE_UNZIP_COMMAND "")
# from the GitHub repository
else()
    message("${BoldWhite}DL_MONTE${EndColour} repository URL: ${DL_MONTE}")
    set(DL_MONTE_VERSION   ${DL_MONTE_LATEST_RELEASE_VERSION})
    set(DL_MONTE_NAME      dl_monte-${DL_MONTE_VERSION})
    set(DL_MONTE_GIT       ON)
    set(DL_MONTE_GIT_REPO  ${DL_MONTE})
    set(DL_MONTE_DIR       ${CMAKE_CURRENT_LIST_DIR}/${CHEMSH_ARCH})
    set(DL_MONTE_LIB_DIR   ${DL_MONTE_DIR}/lib/${DL_MONTE_BUILD_ARCH}/${DL_MONTE_BUILD_VERSION})
    # currently not in use
    set(DL_MONTE_TOOLS_DIR ${DL_MONTE_DIR}/tools/toolchain/install)
    set(DL_MONTE_TARBALL   dl_monte_release_v${DL_MONTE_VERSION}.tar.gz)
    set(DL_MONTE_SRC_DIR   ${DL_MONTE_DIR}/src/dl_monte_release_v${DL_MONTE_VERSION})
    set(DL_MONTE_UNZIP_COMMAND ${CMAKE_COMMAND} -E tar xzf ${DL_MONTE_DIR}/src/${DL_MONTE_TARBALL})
endif()

# HTK paths
if(EXISTS ${DL_MONTE_HTK_ABSOLUTE})
    set(DL_MONTE_HTK_DIR ${DL_MONTE_HTK_ABSOLUTE})
else()
    message("${BoldWhite}DL_MONTE_HTK${EndColour} repository URL: ${DL_MONTE_HTK}")
    set(DL_MONTE_HTK_NAME      dl_monte_htk)
    set(DL_MONTE_HTK_GIT       ON)
    set(DL_MONTE_HTK_GIT_REPO  ${DL_MONTE_HTK})
    set(DL_MONTE_HTK_DIR       ${CMAKE_CURRENT_LIST_DIR}/htk)
    set(DL_MONTE_HTK_SRC_DIR   ${DL_MONTE_HTK_DIR}/dlmontepython)
endif()
configure_file(htk_path.in htk_path @ONLY)


set(DL_MONTE_CONSTANTS_OBJECTS kinds_f90.o constants_module.o)

set(DL_MONTE_SERIAL_OBJECTS    mpi_module.o comms_mpi_module-SRL.o)

set(DL_MONTE_PARALLEL_OBJECTS  comms_mpi_module-PRL.o)

set(DL_MONTE_MODULE_OBJECTS    comms_omp_module.o error.o warning.o parse_module.o parallel_loop_module.o arrays_module.o bondlist_type.o anglist_type.o atom_type.o nbrlist_type.o dihlist_type.o invlist_type.o coul_type.o lattice_type.o thblist_type.o molecule_type.o control_type.o config_type.o statistics_type.o random_module.o fed_interface_type.o psmc_control_type.o)

set(DL_MONTE_OTHER_OBJECTS     dcd_format_module.o dihlist_module.o invlist_module.o inversion_module.o bondlist_module.o anglist_module.o nbrlist_module.o atom_module.o molecule_module.o species_module.o bond_module.o gb_potential_module.o metpot_module.o lattice_module.o coul_module.o config_module.o thblist_module.o thbpotential_module.o tersoff_module.o vdw_module.o slit_module.o external_potential_module.o cell_module.o angle_module.o dihedral_module.o field.o psmc_module.o orientation_module.o fed_calculus_module.o lc_order_module.o fed_order_module.o fed_interface_module.o control_module.o statistics_module.o rep_exchange_module.o spin_module.o mc_moves.o gcmc_moves.o gibbs_moves.o vol_moves.o psmc_moves.o yamldata_module.o montecarlo_module.o traj_convert.o)


# debug flag
if(DEBUG)
    set(DL_MONTE_DEBUG_MACRO "-U DEBUG")
endif()

set(CHEMSH_DL_MONTE_STATIC_LIBS ${DL_MONTE_SRC_DIR}/libdl_monte.a)

# determine if serial or parallel build required
if(WITH_MPI)
    message("DL_MONTE library will be compiled in parallel with MPI")
    set(DL_MONTE_PARALLEL_FLAG "-D__parallel -D__SCALAPACK -D__BLACS")
    set(DL_MONTE_ScaLAPACK_FLAGS ${CHEMSH_ScaLAPACK_FLAGS})
    set(DL_MONTE_CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy ${DL_MONTE_SRC_DIR}/Makefile_PRL ${DL_MONTE_SRC_DIR}/Makefile)
    set(DL_MONTE_HACK_COMMAND      ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/dl_monte_constants_module.f90 ${DL_MONTE_SRC_DIR}/constants_module.f90)
    if(${CHEMSH_ARCH} STREQUAL "intel")
    elseif(${CHEMSH_ARCH} STREQUAL "gnu")
        if(DEBUG)
            set(DL_MONTE_FCFLAGS "-c -cpp -O0 -fPIC -g3 -ggdb ${DL_MONTE_DEBUG_MACRO}")
        else()
            set(DL_MONTE_FCFLAGS "-c -cpp -O2 -fPIC")
        endif()
        set(DL_MONTE_LDFLAGS "-o DLMONTE-PRL.X")
        set(DL_MONTE_BUILD_COMMAND make parallel FC=${CMAKE_Fortran_COMPILER} LD=${CMAKE_Fortran_COMPILER} LDFLAGS=${DL_MONTE_LDFLAGS} FCFLAGS=${DL_MONTE_FCFLAGS} EXE= > make.log)
    endif()
    set(DL_MONTE_ARCHIVE_COMMAND ar rcv ${CHEMSH_DL_MONTE_STATIC_LIBS} ${DL_MONTE_CONSTANTS_OBJECTS} ${DL_MONTE_PARALLEL_OBJECTS} ${DL_MONTE_MODULE_OBJECTS} ${DL_MONTE_OTHER_OBJECTS} > ar.log)
else()
    message("DL_MONTE library will be compiled in serial. Specify MPI to compile in parallel.")
    set(DL_MONTE_CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy ${DL_MONTE_SRC_DIR}/Makefile_SRL ${DL_MONTE_SRC_DIR}/Makefile)
    set(DL_MONTE_HACK_COMMAND      ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/dl_monte_constants_module.f90 ${DL_MONTE_SRC_DIR}/constants_module.f90)
    if(${CHEMSH_ARCH} STREQUAL "intel")
    elseif(${CHEMSH_ARCH} STREQUAL "gnu")
        if(DEBUG)
            set(DL_MONTE_FCFLAGS "-c -cpp -O0 -fPIC -g3 -ggdb ${DL_MONTE_DEBUG_MACRO}")
        else()
            set(DL_MONTE_FCFLAGS "-c -cpp -O2 -fPIC")
        endif()
        set(DL_MONTE_LDFLAGS "-o DLMONTE-SRL.X")
        set(DL_MONTE_BUILD_COMMAND make serial FC=${CMAKE_Fortran_COMPILER} LD=${CMAKE_Fortran_COMPILER} LDFLAGS=${DL_MONTE_LDFLAGS} FCFLAGS=${DL_MONTE_FCFLAGS} EXE= > make.log)
    endif()
    set(DL_MONTE_ARCHIVE_COMMAND ar rcv ${CHEMSH_DL_MONTE_STATIC_LIBS} ${DL_MONTE_CONSTANTS_OBJECTS} ${DL_MONTE_SERIAL_OBJECTS} ${DL_MONTE_MODULE_OBJECTS} ${DL_MONTE_OTHER_OBJECTS} > ar.log)
endif()

if(${CHEMSH_ARCH} STREQUAL "intel")
    get_filename_component(MKL_ROOT ${MKL}/../.. ABSOLUTE)
#    set(FFTW3_INTERFACE_DIR ${MKL_ROOT}/interfaces/fftw3xf)
#    set(FFTW3_CFLAGS "-Wall -Werror -fPIC")
    # 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_DL_MONTE_LIB_DIR} _cflags_intel_diag=${FFTW3_CFLAGS})
    set(DL_MONTE_MKL_FLAGS "${CHEMSH_MKL_FLAGS} -lmkl_avx -lmkl_def")
    set(DL_MONTE_FFT_FLAGS "-D__INTEL -D__FFTSG -D__FFTW3 -D__FFTMKL")
    set(FFTW_INCLUDE_DIR "${MKL_ROOT}/include/fftw")

elseif(${CHEMSH_ARCH} STREQUAL "gnu")
    set(DL_MONTE_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")
    set(DL_MONTE_LIBS "-lpthread -lm -ldl -lstdc++ -lblas -llapack -lfftw3")
    # FIXME: we don't support OpenMP but even if it is opted out from the DL_MONTE toolchain some OpenMP functions are still required for linking
    set(CHEMSH_DL_MONTE_LINK_FLAGS "-fopenmp -lfftw3")
    set(DL_MONTE_FFT_FLAGS "-D__FFTSG -D__FFTW3")
    get_filename_component(FFTW_INCLUDE_DIR "${FFTW_INCLUDE_DIR}" ABSOLUTE)

endif()



if(${DL_MONTE_RECOMPILE})
    set(DL_MONTE_RECOMPILE 1)
    file(REMOVE_RECURSE ${CHEMSH_ARCH}/stamp)
    set(DL_MONTE_CLEAN_COMMAND make clean)
############## DEBUG ###################
# YL: uncomment to speed up compiling and enable modifications to the original DL_MONTE code
#    set(DL_MONTE_CLEAN_COMMAND "")
#    set(DL_MONTE_GIT_REPO "")
#    set(DL_MONTE_UNZIP_COMMAND "")
############# END DEBUG #################
else()
    if(EXISTS ${DL_MONTE_SRC_DIR}/libdl_monte.a)
        set(DL_MONTE_RECOMPILE 0)
        set(DL_MONTE_CLEAN_COMMAND   ${CMAKE_COMMAND} -E cmake_echo_color --switch=$(COLOR) --blue --bold "Found all required static libraries. Skipped cleaning step")
        set(DL_MONTE_BUILD_COMMAND   ${CMAKE_COMMAND} -E cmake_echo_color --switch=$(COLOR) --blue --bold "Skipped recompile step")
        set(DL_MONTE_ARCHIVE_COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --switch=$(COLOR) --blue --bold "Skipped archive step")
        set(DL_MONTE_GIT_REPO        "")
        message("${Red}DL_MONTE_RECOMPILE${EndColour} set ${BoldWhite}OFF${EndColour}: DL_MONTE source code will not be recompiled")
    else()
        set(DL_MONTE_RECOMPILE 1)
        file(REMOVE_RECURSE ${CHEMSH_ARCH}/stamp)
        message("${Green}DL_MONTE_RECOMPILE${EndColour} set ${BoldWhite}OFF${EndColour} but necessary static libraries missing under ${Yellow}${CHEMSH_DL_MONTE_LIB_DIRECTORY}${EndColour}: DL_MONTE source code will still be recompiled")
    endif()
endif()



# compile DL_MONTE 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 DL_MONTE; this is in command-line mode so make sure use full absolute path!

#    ${DL_MONTE_GIT_OPTIONS}
#     UPDATE_COMMAND      git pull --allow-unrelated-histories ${ATEN_GIT_REPO}
#    WORKING_DIRECTORY   ${DL_MONTE_DIR}
ExternalProject_Add(
    ${DL_MONTE_NAME}
    GIT_REPOSITORY      ${DL_MONTE_GIT_REPO}
    GIT_SHALLOW         1
    GIT_PROGRESS        ${DEBUG}
    GIT_CONFIG          advice.detachedHead=false
#    GIT_TAG             issue21_chemshell
    UPDATE_DISCONNECTED 1
    UPDATE_COMMAND      pwd
    PREFIX              ${CHEMSH_ARCH}
    STAMP_DIR           ${CHEMSH_ARCH}/stamp
    TMP_DIR             ${CHEMSH_ARCH}/tmp
    BUILD_ALWAYS        ${DL_MONTE_RECOMPILE}
    SOURCE_DIR          ${DL_MONTE_DIR}/src
    BINARY_DIR          ${DL_MONTE_DIR}/build
    CONFIGURE_COMMAND   ""
    BUILD_COMMAND       ${CMAKE_COMMAND} -E cmake_echo_color --switch=$(COLOR) --magenta --bold "Architecture for which '${DL_MONTE_NAME}' is built: '${CHEMSH_ARCH}'"
    INSTALL_COMMAND     ""
)

ExternalProject_Add_Step(
    ${DL_MONTE_NAME}  DL_MONTE_UNZIP
    COMMENT           "Unpacking tarball '${DL_MONTE_TARBALL}'..."
    ALWAYS            ${DL_MONTE_RECOMPILE}
    DEPENDEES         configure
    COMMAND           ${DL_MONTE_UNZIP_COMMAND}
    WORKING_DIRECTORY ${DL_MONTE_DIR}/src
)

ExternalProject_Add_Step(
    ${DL_MONTE_NAME}  DL_MONTE_CLEAN
    COMMENT           "Cleaning up previous binaries for '${DL_MONTE_NAME}'..."
    ALWAYS            ${DL_MONTE_RECOMPILE}
    DEPENDEES         DL_MONTE_UNZIP
    COMMAND           ${DL_MONTE_CLEAN_COMMAND}
    WORKING_DIRECTORY ${DL_MONTE_SRC_DIR}
)
    
ExternalProject_Add_Step(
    ${DL_MONTE_NAME}  DL_MONTE_CONFIGURE
    COMMENT           "Configuring ${DL_MONTE_NAME} for..."
    ALWAYS            ${DL_MONTE_RECOMPILE}
    DEPENDEES         DL_MONTE_CLEAN
    COMMAND           ${DL_MONTE_CONFIGURE_COMMAND}
    WORKING_DIRECTORY ${DL_MONTE_SRC_DIR}
)

ExternalProject_Add_Step(
    ${DL_MONTE_NAME}  DL_MONTE_HACK
    COMMENT           "Hacking ${DL_MONTE_NAME}..."
    ALWAYS            ${DL_MONTE_RECOMPILE}
    DEPENDEES         DL_MONTE_CONFIGURE
    COMMAND           ${DL_MONTE_HACK_COMMAND}
    WORKING_DIRECTORY ${DL_MONTE_SRC_DIR}
)

# dummy step and space holder for debugging
ExternalProject_Add_Step(
    ${DL_MONTE_NAME} DL_MONTE_TEMP
    COMMENT           ""
    ALWAYS            ${DL_MONTE_RECOMPILE}
    # always preceded by DL_MONTE_CONFIGURE
    DEPENDEES         DL_MONTE_HACK
    COMMAND           ""
    WORKING_DIRECTORY ${DL_MONTE_SRC_DIR}
)

ExternalProject_Add_Step(
    ${DL_MONTE_NAME} DL_MONTE_BUILD
    COMMENT           "Building static library for '${DL_MONTE_NAME}'..."
    ALWAYS            ${DL_MONTE_RECOMPILE}
    # always preceded by DL_MONTE_CONFIGURE
    DEPENDEES         DL_MONTE_TEMP
    COMMAND           ${DL_MONTE_BUILD_COMMAND}
    WORKING_DIRECTORY ${DL_MONTE_SRC_DIR}
)

ExternalProject_Add_Step(
    ${DL_MONTE_NAME} DL_MONTE_ARCHIVE
    COMMENT           "Archiving DL_MONTE object files as ${CHEMSH_DL_MONTE_STATIC_LIBS}..."
    ALWAYS            ${COPY_DL_MONTE_LIBS}
    # always preceded by DL_MONTE_BUILD
    DEPENDEES         DL_MONTE_BUILD
    COMMAND           ${DL_MONTE_ARCHIVE_COMMAND}
    WORKING_DIRECTORY ${DL_MONTE_SRC_DIR}
)

# compile libdl_monte.so (containing only Py-ChemShell interface files)
#include_directories(${DL_MONTE_SRC_DIR})
add_library(dl_monte SHARED dl_monte.f90 dl_monte_routines.f90 ${LINKED})

target_include_directories(dl_monte BEFORE PRIVATE ${DL_MONTE_SRC_DIR})

# dependencies: dl_monte -> dl_monte_base_uses.f90 -> dl_monte-x.x.x -> dl_monte_arch_file
add_dependencies(dl_monte ${DL_MONTE_NAME})

# link the static library agains libdl_monte.so
target_link_libraries(dl_monte dl_py2f parallel)

if(${CHEMSH_ARCH} STREQUAL "intel")
    set_target_properties(dl_monte PROPERTIES COMPILE_FLAGS "-fpp")
elseif(${CHEMSH_ARCH} STREQUAL "gnu")
    target_link_libraries(dl_monte blas lapack)
    set_target_properties(dl_monte PROPERTIES COMPILE_FLAGS "-cpp -O2 -fPIC")
endif()

# DL_MONTE uses the built-in ScaLAPACK provided by the toolchain utility
set_target_properties(dl_monte PROPERTIES LINK_FLAGS "-rdynamic -Wl,--export-dynamic      \
                                                      -Wl,--whole-archive                 \
                                                      ${CHEMSH_DL_MONTE_STATIC_LIBS}      \
                                                      -Wl,--no-whole-archive              \
                                                      ${CHEMSH_DL_MONTE_LINK_FLAGS}       \
                                                      -Wl,--version-script=${CMAKE_CURRENT_LIST_DIR}/export.map")
                                                      #-Wl,--allow-multiple-definition      \
#                                                      ${DL_MONTE_MKL_FLAGS}               \
#                                                      ${DL_MONTE_ScaLAPACK_FLAGS}         \
#                                                      -Wl,--unresolved-symbols=ignore-all \


# HTK
ExternalProject_Add(
    ${DL_MONTE_HTK_NAME}
    GIT_REPOSITORY      ${DL_MONTE_HTK_GIT_REPO}
    GIT_SHALLOW         1
    GIT_PROGRESS        ${DEBUG}
    GIT_CONFIG          advice.detachedHead=false
    UPDATE_DISCONNECTED 0
    UPDATE_COMMAND      ""
    PREFIX              ${DL_MONTE_HTK_DIR}
    STAMP_DIR           ${DL_MONTE_HTK_DIR}/stamp
    TMP_DIR             ${DL_MONTE_HTK_DIR}/tmp
    BUILD_ALWAYS        ${DL_MONTE_RECOMPILE}
    SOURCE_DIR          ${DL_MONTE_HTK_SRC_DIR}
    BINARY_DIR          ${DL_MONTE_HTK_DIR}/build
    CONFIGURE_COMMAND   ""
    BUILD_COMMAND       ""
    INSTALL_COMMAND     ""
)

