#------------------------------------------------------------------------
# -*- mode: cmake -*-
#------------------------------------------------------------------------
#
# LDASTools Frame Library  - Frame library
#
# Copyright (C) 2018 California Institute of Technology
#
# LDASTools Frame Library  is free software; you may redistribute
# it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2 (GPLv2)
#  of the License or at your discretion, any later version.
#
# LDASTools Frame Library is distributed in the hope that it will
# be useful, but without any warranty or even the implied warranty of
# merchantability or fitness for a particular purpose. See the GNU
# General Public License (GPLv2) for more details.
#
# Neither the names of the California Institute of Technology (Caltech),
# The Massachusetts Institute of Technology (M.I.T), The Laser
# Interferometer Gravitational-Wave Observatory (LIGO), nor the names
# of its contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# You should have received a copy of the licensing terms for this
# software included in the file LICENSE located in the top-level
# directory of this package. If you did not, you can view a copy at
# http://dcc.ligo.org/M1500244/LICENSE
#
#------------------------------------------------------------------------
cmake_minimum_required(VERSION 3.12)

set( LDAS_TOOLS_FRAMECPP_PACKAGE_NAME ldas-tools-framecpp )
set( LDAS_TOOLS_FRAMECPP_VERSION      3.0.5 )

set( LDAS_TOOLS_CMAKE_VERSION         1.3.1 )
set( LDAS_TOOLS_AL_VERSION            2.7.0 )

set( FRAME_SPEC_VERSION 9 )

set(
  PROJECT_DESCRIPTION
  "This provides the libraries for the framecpp library."
  )
set(
  PROJECT_DESCRIPTION_LONG
  "This provides a collection of classes and fuctions"
  "needed to manipulate gravitational wave frame files"
  "as used by the LDAS Tools Suite."
  )

set( BOOST_MINIMUM_VERSION 1.67 )

#------------------------------------------------------------------------
#------------------------------------------------------------------------

if (NOT "${CMAKE_CXX_STANDARD}")
  set(CMAKE_CXX_STANDARD 17)
endif( )
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO)
if ( NOT CMAKE_OSX_ARCHITECTURES )
  set( CMAKE_OSX_ARCHITECTURES x86_64 )
endif( )

#------------------------------------------------------------------------
#
#------------------------------------------------------------------------

project( ${LDAS_TOOLS_FRAMECPP_PACKAGE_NAME} )

#------------------------------------------------------------------------
# Define some local functions to be used by subdirectories
#------------------------------------------------------------------------

function( cl_target_test_help executable )
  set(test_name ${executable}_help)
  if ( BUILD_TESTING )
    cx_target_test(
      ${test_name}
      COMMAND ${executable} --help
      DEPENDS ${executable}
    )
    set_tests_properties(
      ${test_name}
      PROPERTIES
      ENVIRONMENT "ASAN_OPTIONS=$ENV{ASAN_OPTIONS}"
    )
  endif( )
endfunction( )

##
## @brief Adds extra template-instantiation debug flags to CMAKE_CXX_FLAGS_<CONFIG>.
##
## @details
##   Appends the following flags to the specified CMAKE_CXX_FLAGS_<BUILD_TYPE> variables, but only if supported by the compiler:
##     -ftemplate-backtrace-limit=0
##     -fdiagnostics-show-note-include-stack (or fallback to -fdiagnostics-show-template-tree)
##
##   If BUILD_TYPE is not provided, defaults to Debug.
##
## @param BUILD_TYPE
##   Optional list of CMake build types (e.g., Debug, Release, RelWithDebInfo).
##
## @example
##   # Apply only to Debug (default)
##   cl_cxx_extra_template_debugging()
##
##   # Apply to Debug and RelWithDebInfo
##   cl_cxx_extra_template_debugging(BUILD_TYPE Debug RelWithDebInfo)
##
function(cl_cxx_extra_template_debugging)
  # Declare BUILD_TYPE as a multi-value named argument
  set(_option_args )
  set(_one_value_args )
  set(_multi_value_args BUILD_TYPE)
  cmake_parse_arguments(
    CLX   # prefix for parsed variables
    "${_option_args}"
    "${_one_value_args}"
    "${_multi_value_args}"
    ${ARGV}
  )

  # Determine which build types to operate on
  if (CLX_BUILD_TYPE)
    set(_types ${CLX_BUILD_TYPE})
  else()
    set(_types Debug)
  endif()

  # Check for supported compiler flags
  include(CheckCXXCompilerFlag)
  check_cxx_compiler_flag("-ftemplate-backtrace-limit=0"        CLX_HAS_TBT)
  check_cxx_compiler_flag("-fdiagnostics-show-note-include-stack" CLX_HAS_NS)
  check_cxx_compiler_flag("-fdiagnostics-show-template-tree"     CLX_HAS_TMPL)

  foreach(t IN LISTS _types)
    string(TOUPPER "${t}" t_upper)
    set(flags_var "CMAKE_CXX_FLAGS_${t_upper}")

    if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
      # Build the list of diagnostic flags
      set(_diag_flags)
      if (CLX_HAS_TBT)
        list(APPEND _diag_flags "-ftemplate-backtrace-limit=0")
      endif()
      if (CLX_HAS_NS)
        list(APPEND _diag_flags "-fdiagnostics-show-note-include-stack")
      elseif (CLX_HAS_TMPL)
        list(APPEND _diag_flags "-fdiagnostics-show-template-tree")
      endif()

      if (_diag_flags)
        # Join and append into the cache variable
        string(JOIN " " _diag_flags_str ${_diag_flags})
        set(
          ${flags_var}
          "${${flags_var}} ${_diag_flags_str}"
          CACHE STRING "Compiler flags for ${t}" FORCE
        )
      else()
        message(WARNING "cl_cxx_extra_template_debugging: no supported diagnostic flags found for ${CMAKE_CXX_COMPILER_ID}")
      endif()
    else()
      message(
        WARNING
        "cl_cxx_extra_template_debugging: "
        "ignoring for ${CMAKE_CXX_COMPILER_ID}; flags only supported on GCC/Clang"
      )
    endif()
  endforeach()
endfunction()

#------------------------------------------------------------------------
# Locate the standard LDAS Tools cmake macro directory
#------------------------------------------------------------------------

find_package(
  LDASToolsCMake
  CONFIG
  REQUIRED
  HINTS ${CMAKE_INSTALL_PREFIX}/share/ldas-tools/cmake/Modules
  )

#========================================================================
# Start pulling in the LDAS Tools macros for easy setup
#========================================================================

find_package(AxLDASTools)

cm_init(
  ${LDAS_TOOLS_FRAMECPP_PACKAGE_NAME}
  "${LDAS_TOOLS_FRAMECPP_VERSION}"
  ${LDAS_TOOLS_BUG_REPORT_URL}
  ${LDAS_TOOLS_FRAMECPP_PACKAGE_NAME}
  ${LDAS_TOOLS_HOMEPAGE_URL}
  DESCRIPTION "${PROJECT_DESCRIPTION}"
  )

#dnl =====================================================================
#dnl  Prepare a header file to store the glorious configuration info
#dnl =====================================================================
set( PROJECT_CONFIG_FILENAME
     "${CMAKE_CURRENT_BINARY_DIR}/framecpp_config.h" )

ch_top(
  "/*"
  " * This software impliments the LIGO / Virgo frame specification"
  " *"
  " * Copyright (C) 2018 California Institute of Technology"
  " *"
  " *  This program is free software; you may redistribute it and/or modify"
  " *  it under the terms of the GNU General Public License as published by"
  " *  the Free Software Foundation; either version 2 (GPLv2) of the"
  " *  License or at your discretion, any later version."
  " *"
  " *  This program is distributed in the hope that it will be useful, but"
  " *  without any warranty or even the implied warranty of merchantability"
  " *  or fitness for a particular purpose. See the GNU General Public"
  " *  License (GPLv2) for more details."
  " *"
  " *  Neither the names of the California Institute of Technology (Caltech),"
  " *  The Massachusetts Institute of Technology (M.I.T), The Laser"
  " *  Interferometer Gravitational-Wave Observatory (LIGO), nor the names"
  " *  of its contributors may be used to endorse or promote products derived"
  " *  from this software without specific prior written permission."
  " *"
  " *  You should have received a copy of the licensing terms for this"
  " *  software included in the file LICENSE located in the top-level"
  " *  directory of this package. If you did not, you can view a copy at"
  " *  http://dcc.ligo.org/M1500244/LICENSE"
  " *"
  " */"
  " "
  "#ifndef FRAMECPP_CONFIG_H"
  "#define FRAMECPP_CONFIG_H"
  " "
  )
ch_bottom(
  " "
  "#if HAVE__PRAGMA"
  "#define DO_PRAGMA(x) _Pragma(#x)"
  "#else /* HAVE__PRAGMA */"
  "#define DO_PRAGMA(x)"
  "#endif /* HAVE__PRAGMA */"

  "#if HAVE_PRAGMA_WIMPLICIT_FALLTHROUGH"
  "#define BEGIN_WIMPLICIT_FALLTHROUGH \\"
  "DO_PRAGMA(GCC diagnostic push) \\"
  "DO_PRAGMA(GCC diagnostic ignored \"-Wimplicit-fallthrough\")"
  "#define END_WIMPLICIT_FALLTHROUGH \\"
  "DO_PRAGMA(GCC diagnostic pop)"
  "#else /* HAVE_PRAGMA_WIMPLICIT_FALLTHROUGH */"
  "#define BEGIN_WIMPLICIT_FALLTHROUGH"
  "#define END_WIMPLICIT_FALLTHROUGH"
  "#endif /* HAVE_PRAGMA_WIMPLICIT_FALLTHROUGH */"

  "#endif /* FRAMECPP_CONFIG_H */"
   )
#
if ( APPLE )
  cm_msg_notice( "Setting RPATH for APPLE" )
  set( CMAKE_INSTALL_RPATH "@loader_path/../${CMAKE_INSTALL_LIBDIR}" )
elseif ( UNIX )
  cm_msg_notice( "Setting RPATH for LINUX" )
  set( CMAKE_INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}:$ORIGIN" )
  #set( CMAKE_SKIP_BUILD_RPATH TRUE )
  #set( CMAKE_BUILD_WITH_INSTALL_RPATH FALSE )
  #set( CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_FULL_LIBDIR}" )
  #set( CMKAE_INSTALL_RPATH_USE_LINK_PATH TRUE )
  #set( CMAKE_BUILD_WITH_INSTALL_RPATH FALSE )
  #list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir)
  #if("${isSystemDir}" STREQUAL "-1")
  #  set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}")
  #endif( )
else()
  cm_msg_notice( "Setting RPATH for DEFAULT" )
endif( )

#dnl =====================================================================
#dnl  Check for compilers and language features
#dnl =====================================================================
#dnl =====================================================================
#dnl  Checking if the OS is 64 bit
#dnl =====================================================================
#dnl  Check for key programs
#dnl =====================================================================
#dnl ---------------------------------------------------------------------
#dnl  Checks for packaging
#dnl ---------------------------------------------------------------------
#dnl =====================================================================
#dnl  Check for header files
#dnl =====================================================================

cx_check_header_zstd( )

cx_scheme_sanitizer(
  ALLOW_WARNINGS
  ENABLE_ASAN
  ENABLE_USE_AFTER_SCOPE
  DISABLE_RECOVERY_ASAN
  SUPPRESSION_FILE_ASAN ${CMAKE_SOURCE_DIR}/asan_suppressions.txt
  ENABLE_LSAN
  DISABLE_RECOVERY_LSAN
  SUPPRESSION_FILE_LSAN ${CMAKE_SOURCE_DIR}/lsan_suppressions.txt
  ENABLE_MSAN
  DISABLE_RECOVERY_MSAN
  SUPPRESSION_FILE_MSAN ${CMAKE_SOURCE_DIR}/msan_suppressions.txt
  ENABLE_TSAN
  DISABLE_RECOVERY_TSAN
  SUPPRESSION_FILE_TSAN ${CMAKE_SOURCE_DIR}/tsan_suppressions.txt
  ENABLE_UBSAN
  DISABLE_RECOVERY_UBSAN
  SUPPRESSION_FILE_UBSAN ${CMAKE_SOURCE_DIR}/ubsan_suppressions.txt
)

# Note: ASAN/LSAN suppressions are configured via TESTS_ENVIRONMENT in test/CMakeLists.txt
# The cx_scheme_sanitizer() call above handles compile-time configuration

cx_check_compile_hardening( )
cx_cxx_warnings_as_errors( Debug )
cl_CXX_extra_template_debugging( BUILD_TYPE Debug )

# =======================================================================
#  Check for key programs
# =======================================================================

if ( BUILD_TESTING )
  find_package(GTest REQUIRED)
endif( )

# Use LDAS Tools Boost detection (handles conda header-only libraries)
cx_boost_base(${BOOST_MINIMUM_VERSION})
cx_boost_chrono()
cx_boost_log()
cx_boost_program_options()
cx_boost_serialization()
cx_boost_system()
cx_boost_filesystem()

# Verify all components found
set(_required_components CHRONO LOG PROGRAM_OPTIONS SERIALIZATION SYSTEM FILESYSTEM)
foreach(_comp ${_required_components})
  if(NOT BOOST_${_comp}_FOUND)
    message(FATAL_ERROR "Required Boost component ${_comp} not found")
  endif()
endforeach()

# Build BOOST_CPPFLAGS for pkg-config
list(APPEND BOOST_CPPFLAGS "-I${BOOST_INCLUDE_DIR}")

# Build BOOST_LDFLAGS for pkg-config from component LDFLAGS
set(BOOST_LDFLAGS "")
foreach(_comp ${_required_components})
  if(BOOST_${_comp}_LDFLAGS)
    list(APPEND BOOST_LDFLAGS ${BOOST_${_comp}_LDFLAGS})
  endif()
endforeach()

# Convert lists to space-separated strings for pkg-config
string(REPLACE ";" " " BOOST_LDFLAGS "${BOOST_LDFLAGS}")
string(REPLACE ";" " " BOOST_CPPFLAGS "${BOOST_CPPFLAGS}")


#dnl =====================================================================
#dnl  Check for structures/sizes
#dnl =====================================================================

cm_c_bigendian( )
cx_msg_debug_variable( WORDS_BIGENDIAN )
if ( WORDS_BIGENDIAN )
  cm_subst( LDAS_WORDS_BIGENDIAN 1 )
else( )
  set( LDAS_WORDS_BIGENDIAN 0 )
  cm_subst( LDAS_WORDS_BIGENDIAN 0 )
endif( )
cx_msg_debug_variable( LDAS_WORDS_BIGENDIAN )

# =====================================================================
#  Check for individual libraries
# =====================================================================

cx_check_lib_z( )
cx_check_lib_zstd( )
cx_check_lib_framel( )
cx_check_lib_md5sum( )

#dnl =====================================================================
#dnl  Check for individual functions
#dnl =====================================================================

#dnl =====================================================================
#dnl  Check for supported classes
#dnl =====================================================================

#dnl =====================================================================
#dnl Look for additional ldas-tools pieces
#dnl =====================================================================

#dnl =====================================================================
#dnl
#dnl =====================================================================


string( TIMESTAMP FRAMECPP_BUILD_DATE )
cm_subst( FRAMECPP_BUILD_DATE ${FRAMECPP_BUILD_DATE} )

# =====================================================================
# Look for additional ldas-tools pieces
# =====================================================================

if( NOT ENABLE_DOCUMENTATION_ONLY )
  cm_pkg_check_modules(
    PREFIX LDASTOOLS
    REQUIRED
    MODULES
      ldastoolsal>=${LDAS_TOOLS_AL_VERSION}
    )
endif( )

#========================================================================
# Local macros
#========================================================================

function( framecpp_target_gtest target )
  if ( NOT BUILD_TESTING OR ( CMAKE_CROSSCOMPILING AND NOT CMAKE_CROSSCOMPILING_EMULATOR ) )
    return( )
  endif( )

  set(options
    )
  set(oneValueArgs
    )
  set(multiValueArgs
    COMMAND_ARGS
    DEPENDENCIES
    LDADD
    SOURCES
    INCLUDES
    )

  cmake_parse_arguments( ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )

  if ( NOT ARG_LDADD )
    set( ARG_LDADD ${LDADD} )
  endif( )
  if ( NOT ARG_INCLUDES )
    set( ARG_INCLUDES ${AM_CPPFLAGS} )
  endif( )

    set( THREADS_PREFER_PTHREAD_FLAG ON)
  find_package( Threads REQUIRED )

  cx_target_executable(
    ${target}
    NOINST
    SOURCES ${ARG_SOURCES}
    LDADD ${ARG_LDADD}
    INCLUDE_DIRECTORIES BEFORE PRIVATE
    ${ARG_INCLUDES}
    )
  if(ARG_DEPENDENCIES)
    add_dependencies( ${target} ${ARG_DEPENDENCIES} )
  endif( )
  gtest_add_tests(
    TARGET ${target}
    EXTRA_ARGS
    --gtest_death_test_style=fast
    --gtest_catch_exceptions=1
    ${ARG_COMMAND_ARGS}
    TEST_LIST gtest_test_names
  )
  cx_scheme_sanitizer_set_environment(
    TEST_TARGETS ${gtest_test_names}
  )
endfunction( )

#========================================================================
# Traverse the tree
#========================================================================

add_subdirectory(src)
add_subdirectory(test)
add_subdirectory(doc)

#========================================================================
# Commit configuration information to the file
#========================================================================

cm_config_headers( ${PROJECT_CONFIG_FILENAME} )

#========================================================================
# Prepare the distribution
#========================================================================

cx_cpack(
  MACPORTS_CATEGORY science
  MAINTAINER "Edward Maros <ed.maros@ligo.org>"
  )

#========================================================================
# Report configuration information
#  CMAKE_MAKE_PROGRAM:FILEPATH
#========================================================================
cm_msg_notice( "=========================================================")
cm_msg_notice( " Project ${PROJECT_NAME} has now ben successfully configured:" )
cm_msg_notice( "      VERSION: ${PROJECT_VERSION}" )
cm_msg_notice( " " )
cm_msg_notice( " installation directories:" )
cm_msg_notice( "      CMAKE_INSTALL_BINDIR           ${CMAKE_INSTALL_BINDIR}" )
cm_msg_notice( "      CMAKE_INSTALL_DATADIR          ${CMAKE_INSTALL_DATADIR}" )
cm_msg_notice( " package information:" )
cm_msg_notice( "      LDASTOOLS_FOUND:               ${LDASTOOLS_FOUND}" )
cm_msg_notice( "      LDASTOOLS_LIBRARIES:           ${LDASTOOLS_LIBRARIES}" )
cm_msg_notice( "      LDASTOOLS_LIBRARIES_FULL_PATH: ${LDASTOOLS_LIBRARIES_FULL_PATH}" )
cm_msg_notice( "      GTEST_INCLUDE_DIRS:            ${GTEST_INCLUDE_DIRS}" )
cm_msg_notice( "      GTEST_LIBRARIES:               ${GTEST_LIBRARIES}" )
cm_msg_notice( " " )
cm_msg_notice( "  Sanitizers:" )
cm_msg_notice( "      Active:                         ${CX_SCHEME_SANITIZERS_ACTIVE}")
if(EXISTS ${CMAKE_SOURCE_DIR}/asan_suppressions.txt)
  cm_msg_notice( "      ASAN suppression file:          ${CMAKE_SOURCE_DIR}/asan_suppressions.txt")
endif()
if(EXISTS ${CMAKE_SOURCE_DIR}/lsan_suppressions.txt)
  cm_msg_notice( "      LSAN suppression file:          ${CMAKE_SOURCE_DIR}/lsan_suppressions.txt")
  if(CX_SCHEME_SANITIZERS_ACTIVE MATCHES "address" OR CX_SCHEME_SANITIZERS_ACTIVE MATCHES "leak")
    cm_msg_notice( "      LSAN enabled in test env:       YES (LeakSanitizer active)")
  else()
    cm_msg_notice( "      LSAN enabled in test env:       NO (LeakSanitizer not active)")
  endif()
else()
  cm_msg_notice( "      LSAN enabled in test env:       NO (suppression file not found)")
endif()
cm_msg_notice( " " )
cm_msg_notice( "  Now run 'cmake --build .' to build ${PROJECT_NAME}" )
cm_msg_notice( "  and run 'cpack' to package ${PROJECT_NAME}" )
cm_msg_notice( "=========================================================" )
