cmake_minimum_required(VERSION 3.14)

set(QT_CREATOR_SKIP_MAINTENANCE_TOOL_PROVIDER ON)

# ******* MOUNT CONTROL COMPONENTS *******

project(mcc LANGUAGES C CXX Fortran VERSION 0.1)

# set(CMAKE_BUILD_TYPE Release)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")

# ******* LIBRARY OPTIONS *******

option(
    USE_SPDLOG
    "Use of SPDLOG library (add implementation of logger class based on this library)"
    ON
)

option(
    USE_ERFA
    "Use of ERFA library (add implementation of CCTE based on this library)"
    ON
)

option(
    USE_EIGEN3
    "Use of Eigen3 library as dependency for implemetation of PCM construction"
    ON
)

option(USE_BSPLINE_PCM "Use of FITPACK bivariate splines for PCM" ON)

option(
    USE_ASIO
    "Use of ASIO-library (add generic implementation of network capabilities)"
    ON
)

option(BUILD_TESTS "Build tests" ON)

find_package(Threads REQUIRED)

include(FetchContent)
include(ExternalProject)

# ******** Eigen3 LIBRARY *******

if(USE_EIGEN3)
    find_package(Eigen3 CONFIG)
    if(TARGET Eigen3::Eigen)
        message(STATUS "Eigen3 library was found!")
    else()
        message(STATUS \tfetch Eigen3 ...)
        FetchContent_Declare(
            eigen
            GIT_REPOSITORY "https://gitlab.com/libeigen/eigen.git"
            GIT_SHALLOW TRUE
            GIT_PROGRESS TRUE
            OVERRIDE_FIND_PACKAGE
        )
        FetchContent_MakeAvailable(eigen)

        find_package(Eigen3 REQUIRED CONFIG)
    endif()
endif()

# ******* SPDLOG LIBRARY *******

if(USE_SPDLOG)
    set(USE_SPDLOG_SYSTEM ON)

    # find_package(spdlog CONFIG NO_MODULE)
    # if(NOT ${spdlog_FOUND})
    message(STATUS "\tfetch spdlog-lib ...")
    FetchContent_Declare(
        spdlog
        GIT_REPOSITORY "https://github.com/gabime/spdlog.git"
        GIT_TAG "v1.15.1"
        GIT_SHALLOW TRUE
        GIT_SUBMODULES ""
        GIT_PROGRESS TRUE
        # CMAKE_ARGS
        # -DSPDLOG_USE_STD_FORMAT=ON
        # -DSPDLOG_FMT_EXTERNAL=OFF
        OVERRIDE_FIND_PACKAGE
    )

    set(SPDLOG_USE_STD_FORMAT ON CACHE INTERNAL "Use of C++20 std::format")
    set(SPDLOG_FMT_EXTERNAL OFF CACHE INTERNAL "Turn off external fmt library")
    set(SPDLOG_INSTALL ON CACHE BOOL "Enable spdlog installation" FORCE)
    FetchContent_MakeAvailable(spdlog)

    # find_package(spdlog REQUIRED CONFIG)

    set(USE_SPDLOG_SYSTEM OFF)
    # else()
    #     set(SPDLOG_USE_STD_FORMAT ON CACHE INTERNAL "Use of C++20 std::format")
    #     set(SPDLOG_FMT_EXTERNAL
    #         OFF
    #         CACHE INTERNAL
    #         "Turn off external fmt library"
    #     )
    # endif()
endif()

# ******* ERFA LIBRARY *******

find_package(PkgConfig REQUIRED)

if(USE_ERFA)
    pkg_check_modules(ERFALIB IMPORTED_TARGET GLOBAL erfa)

    if(NOT ERFALIB_FOUND)
        message(STATUS "\tfetch erfa-lib ...")
        # ExternalProject_Add(erfalib
        #     PREFIX ${CMAKE_BINARY_DIR}/erfa_lib
        #     GIT_REPOSITORY "https://github.com/liberfa/erfa.git"
        #     GIT_TAG "v2.0.1"
        #     UPDATE_COMMAND ""
        #     PATCH_COMMAND ""
        #     LOG_CONFIGURE 1
        #     CONFIGURE_COMMAND meson setup --reconfigure -Ddefault_library=static -Dbuildtype=release
        #                               -Dprefix=${CMAKE_BINARY_DIR}/erfa_lib -Dlibdir= -Dincludedir= -Ddatadir= <SOURCE_DIR>
        #     BUILD_COMMAND ninja -C <BINARY_DIR>
        #     INSTALL_COMMAND meson install -C <BINARY_DIR>
        #     BUILD_BYPRODUCTS ${CMAKE_BINARY_DIR}/erfa_lib/liberfa.a
        # )

        # add_library(PkgConfig::ERFALIB STATIC IMPORTED GLOBAL)
        # set_target_properties(PkgConfig::ERFALIB PROPERTIES IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/erfa_lib/liberfa.a)
        # set_target_properties(PkgConfig::ERFALIB PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_BINARY_DIR}/erfa_lib)
        # add_dependencies(PkgConfig::ERFALIB erfalib)

        # set(CACHE{ERFALIB_INCLUDE_DIRS} TYPE PATH VALUE "${CMAKE_BINARY_DIR}/erfa_lib")
        # set(CACHE{ERFALIB_LIBRARY_DIRS} TYPE PATH VALUE "${CMAKE_BINARY_DIR}/erfa_lib")
        # set(CACHE{ERFALIB_LIBRARIES} TYPE STRING VALUE "erfa;m")

        find_program(MESON_PROG NAMES meson HINTS ENV PATHS)

        if(NOT MESON_PROG)
            message(FATAL "meson executable can not be found!!!")
        endif()

        find_program(NINJA_PROG NAMES ninja ninja-build)

        if(NOT NINJA_PROG)
            message(FATAL "ninja executable can not be found!!!")
        endif()

        FetchContent_Declare(
            erfalib_project
            GIT_REPOSITORY "https://github.com/liberfa/erfa.git"
            GIT_TAG "v2.0.1"
            GIT_SHALLOW TRUE
            GIT_PROGRESS TRUE
        )

        FetchContent_MakeAvailable(erfalib_project)
        # message(STATUS "ERFA: ${erfalib_project_SOURCE_DIR}")

        message(STATUS "\tbuild erfa-lib ...")
        execute_process(
            COMMAND
                meson setup --reconfigure -Ddefault_library=static
                -Dbuildtype=release -Dprefix=${CMAKE_BINARY_DIR}/erfa_lib
                -Dlibdir= -Dincludedir= -Ddatadir= ${CMAKE_BINARY_DIR}/erfa_lib
                ${erfalib_project_SOURCE_DIR}
        )

        execute_process(
            WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/erfa_lib
            COMMAND ninja -C ${CMAKE_BINARY_DIR}/erfa_lib
        )

        execute_process(
            WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/erfa_lib
            COMMAND meson install -C ${CMAKE_BINARY_DIR}/erfa_lib
        )

        set(ENV{PKG_CONFIG_PATH} "${CMAKE_BINARY_DIR}/erfa_lib/pkgconfig")
        pkg_check_modules(ERFALIB IMPORTED_TARGET GLOBAL erfa)
    endif()

    message(STATUS "ERFA LIBS: ${ERFALIB_LIBRARIES}")
    message(STATUS "ERFA LIB PATHS: ${ERFALIB_LIBRARY_DIRS}")
    message(STATUS "ERFA INC PATHS: ${ERFALIB_INCLUDE_DIRS}")
endif()

if(USE_ASIO)
    pkg_check_modules(ASIOLIB IMPORTED_TARGET GLOBAL asio)

    set(USE_ASIO_SYSTEM ON)

    if(NOT ASIOLIB_FOUND)
        message(STATUS "\tfetch asio-lib ...")

        FetchContent_Declare(
            asiolib_project
            PREFIX
            ${CMAKE_BINARY_DIR}/asio
            GIT_REPOSITORY "https://github.com/chriskohlhoff/asio.git"
            GIT_TAG "asio-1-36-0"
            GIT_SHALLOW 1
        )

        FetchContent_MakeAvailable(asiolib_project)

        execute_process(
            WORKING_DIRECTORY ${asiolib_project_SOURCE_DIR}/asio
            COMMAND ./autogen.sh
        )

        execute_process(
            WORKING_DIRECTORY ${asiolib_project_SOURCE_DIR}/asio
            COMMAND ./configure --prefix=${asiolib_project_SOURCE_DIR}/asio
        )

        set(ENV{PKG_CONFIG_PATH} "${asiolib_project_SOURCE_DIR}/asio")
        pkg_check_modules(ASIOLIB IMPORTED_TARGET GLOBAL asio)

        message(STATUS "ASIO INC PATHS: ${ASIOLIB_INCLUDE_DIRS}")

        set(USE_ASIO_SYSTEM OFF)
    endif()
endif()

# if(USE_BSPLINE_PCM)
#     # fitpack by P. Dierckx
#     add_subdirectory(fitpack)
# endif()

set(MCC_SRC
    include/mcc/mcc_concepts.h
    include/mcc/mcc_constants.h
    include/mcc/mcc_epoch.h
    include/mcc/mcc_angle.h
    include/mcc/mcc_coordinate.h
    include/mcc/mcc_error.h
    include/mcc/mcc_traits.h
    include/mcc/mcc_utils.h
    include/mcc/mcc_pzone.h
    include/mcc/mcc_pzone_container.h
    include/mcc/mcc_pcm.h
    include/mcc/mcc_telemetry.h
    include/mcc/mcc_movement_controls.h
    include/mcc/mcc_generic_movecontrols.h
    include/mcc/mcc_serialization_common.h
    include/mcc/mcc_deserializer.h
    include/mcc/mcc_serializer.h
    include/mcc/mcc_generic_mount.h
)

if(USE_SPDLOG)
    list(APPEND MCC_SRC include/mcc/mcc_spdlog.h)
endif()

if(USE_ERFA)
    list(
        APPEND MCC_SRC
        include/mcc/mcc_ccte_iers.h
        include/mcc/mcc_ccte_iers_default.h
        include/mcc/mcc_ccte_erfa.h
    )
endif()

if(USE_ASIO)
    list(
        APPEND MCC_SRC
        include/mcc/mcc_netserver_endpoint.h
        include/mcc/mcc_netserver_proto.h
        include/mcc/mcc_netserver.h
    )
endif()

if(USE_BSPLINE_PCM)
    # fitpack by P. Dierckx
    list(APPEND MCC_SRC include/mcc/mcc_bsplines.h)
    add_subdirectory(fitpack)
endif()

if(USE_EIGEN3)
    list(
        APPEND MCC_SRC
        include/mcc/mcc_pcm_construct.h
        include/mcc/mcc_pcm_fit.h
    )
endif()

add_library(${PROJECT_NAME} INTERFACE ${MCC_SRC})
target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_23)
target_include_directories(
    ${PROJECT_NAME}
    INTERFACE
        "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>"
        "$<INSTALL_INTERFACE:include/${PROJECT_NAME}>"
)

if(USE_ERFA)
    target_link_libraries(${PROJECT_NAME} INTERFACE PkgConfig::ERFALIB)
endif()

if(USE_BSPLINE_PCM)
    target_compile_definitions(${PROJECT_NAME} INTERFACE USE_BSPLINE_PCM)
    target_link_libraries(${PROJECT_NAME} INTERFACE fitpack)
    target_link_directories(
        ${PROJECT_NAME}
        INTERFACE "$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/fitpack>"
    )
endif()

if(USE_EIGEN3)
    target_link_libraries(${PROJECT_NAME} INTERFACE Eigen3::Eigen)
endif()

if(USE_ASIO)
    target_link_libraries(${PROJECT_NAME} INTERFACE PkgConfig::ASIOLIB)
endif()

if(USE_SPDLOG)
    target_compile_definitions(
        ${PROJECT_NAME}
        INTERFACE SPDLOG_USE_STD_FORMAT=1 SPDLOG_FMT_EXTERNAL=0
    )
    target_link_libraries(${PROJECT_NAME} INTERFACE spdlog::spdlog_header_only)
    # target_compile_definitions(
    #     ${PROJECT_NAME}
    #     INTERFACE SPDLOG_USE_STD_FORMAT=1
    # )
endif()

if(BUILD_TESTS)
    add_executable(mcc_telemetry_test tests/mcc_telemetry_test.cpp)
    target_link_libraries(mcc_telemetry_test PRIVATE ${PROJECT_NAME})

    add_executable(mcc_coord_test tests/mcc_coord_test.cpp)
    target_link_libraries(mcc_coord_test PRIVATE ${PROJECT_NAME})

    add_executable(mcc_pzone_test tests/mcc_pzone_test.cpp)
    target_link_libraries(mcc_pzone_test PRIVATE ${PROJECT_NAME})

    add_executable(mcc_netmsg_test tests/mcc_netmsg_test.cpp)
    target_link_libraries(mcc_netmsg_test PRIVATE ${PROJECT_NAME})

    add_executable(mcc_fitpack_test tests/mcc_fitpack_test.cpp)
    target_link_libraries(mcc_fitpack_test PRIVATE ${PROJECT_NAME})

    add_executable(mcc_pcm_test tests/mcc_pcm_test.cpp)
    target_link_libraries(mcc_pcm_test PRIVATE ${PROJECT_NAME})

    add_executable(mcc_pcm_z1000_test tests/mcc_pcm_z1000_test.cpp)
    target_link_libraries(mcc_pcm_z1000_test PRIVATE ${PROJECT_NAME})
else()
    # This is just a stub to allow access to the path and library settings for the ${PROJECT_NAME} target during development
    add_executable(just_stub EXCLUDE_FROM_ALL main.cpp)
    target_link_libraries(just_stub PUBLIC ${PROJECT_NAME})
endif()

include(GNUInstallDirs)

install(
    TARGETS ${PROJECT_NAME}
    EXPORT ${PROJECT_NAME}_Targets
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

include(CMakePackageConfigHelpers)
write_basic_package_version_file(
    "${PROJECT_NAME}ConfigVersion.cmake"
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY SameMajorVersion
)

set(MCC_CONFIG_INSTALLDIR
    ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
    # CACHE PATH
    # "install path for generated library config files"
)
set(MCC_HEADERS_INSTALLDIR
    ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}
    # CACHE PATH
    # "install path for headers"
)

configure_package_config_file(
    "${PROJECT_SOURCE_DIR}/${PROJECT_NAME}Config.cmake.in"
    "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
    INSTALL_DESTINATION ${MCC_CONFIG_INSTALLDIR}
    PATH_VARS MCC_HEADERS_INSTALLDIR
)

install(
    EXPORT ${PROJECT_NAME}_Targets
    FILE ${PROJECT_NAME}Targets.cmake
    NAMESPACE ${PROJECT_NAME_NAMESPACE}::
    DESTINATION ${MCC_CONFIG_INSTALLDIR}
)
install(
    FILES
        "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
        "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
    DESTINATION ${MCC_CONFIG_INSTALLDIR}
)
install(FILES ${MCC_SRC} DESTINATION include/${PROJECT_NAME})

# uninstall target
if(NOT TARGET uninstall)
    configure_file(
        "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in"
        "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
        IMMEDIATE
        @ONLY
    )

    add_custom_target(
        uninstall
        COMMAND
            ${CMAKE_COMMAND} -P
            ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake
    )
endif()
