
cmake_minimum_required(VERSION 3.13)
project(mtr085 C)
set(NAME "mtr")
set(VERSION "0.85")
string(REPLACE "." "" NAME_SUFFIX "${VERSION}")
set(PACKAGE_NAME "${NAME}${NAME_SUFFIX}")

if(EXISTS .git)
  find_package(Git)
  if(GIT_EXECUTABLE)
    execute_process(
      COMMAND ${GIT_EXECUTABLE} rev-list --count 97af563..HEAD
      OUTPUT_STRIP_TRAILING_WHITESPACE
      RESULT_VARIABLE ERROR_CODE
      OUTPUT_VARIABLE GITREV
    )
  endif()
endif()

set(CMAKE_C_STANDARD 11)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # compile_commands.json
include(GNUInstallDirs)
include(CheckIncludeFile)
include(CheckTypeSize)
include(CheckSymbolExists)
include(CheckFunctionExists)
include(CheckLibraryExists)

set(MAN_PAGE "${NAME}.8")
set(MAN_PATH "${CMAKE_BINARY_DIR}/man8")
set(MANUAL "${MAN_PATH}/${MAN_PAGE}")

# set target
add_executable("${NAME}" "${NAME}.c" aux.c display.c polling.c report.c)
target_include_directories("${NAME}" PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
target_compile_options("${NAME}" PRIVATE -Wall -Wextra -Wpedantic -D_GNU_SOURCE)

# options
set(OPTION_LIST)
set(SOCKET "auto" CACHE STRING
  "RAW or USER sockets (AUTO is USER on Linux, otherwise RAW)")
set_property(CACHE SOCKET PROPERTY STRINGS "raw" "user" "auto")
set(VALID_SOCKETS "raw" "user" "auto")
if(NOT SOCKET IN_LIST VALID_SOCKETS)
  message(FATAL_ERROR "Wrong SOCKET '${SOCKET}' (valid ones: ${VALID_SOCKETS}")
endif()
if    (SOCKET STREQUAL "raw")
  set(RAW ON)
  set(RAWSTR "${SOCKET}")
elseif(SOCKET STREQUAL "user")
  set(RAW OFF)
  set(RAWSTR "${SOCKET}")
elseif(SOCKET STREQUAL "auto")
  if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    set(RAW OFF)
    set(RAWSTR "${SOCKET} (user)")
  else()
    set(RAW ON)
    set(RAWSTR "${SOCKET} (raw)")
  endif()
endif()
#
option(TUI       "TUI: [n]curses"       ON)
option(COLOR     "Colored output"       ON)
option(MOUSE     "Mouse (TUI required)" ON)
option(MENU      "Menu (TUI required)"  OFF)
option(UNICODE   "Unicode support"      ON)
option(NLS       "I18n support"         ON)
option(DNS       "DNS support"          ON)
option(IPINFO    "IPINFO support"       ON)
option(IPV6      "IPv6 support"         ON)
option(MPLS      "MPLS decoding"        ON)
set(OPTION_OUTFMT)
option(OUTTXT    "Output text format"   ON)
option(OUTCSV    "Output csv format"    ON)
option(OUTJSON   "Output json format"   ON)
option(OUTTOON   "Output toon format"   ON)
option(OUTXML    "Output xml format"    ON)
set(OPTION_DEBLOG)
option(DEBTUI    "Debug tui syslog"     OFF)
option(DEBPOLL   "Debug poll syslog"    OFF)
option(DEBNET    "Debug net syslog"     OFF)
option(DEBDNS    "Debug dns syslog"     OFF)
option(DEBIPINFO "Debug ipinfo syslog"  OFF)
set(MAN_EXCL)
option(SBIN      "Install to sbin"      OFF)

# cmake-lint: disable=C0301

find_package(PkgConfig REQUIRED)

set(NETDIR "net")
add_subdirectory("${NETDIR}")
target_include_directories("${NAME}" PRIVATE
  "${CMAKE_CURRENT_SOURCE_DIR}/${NETDIR}")

if(TUI)
  set(TUIMODE ON)
  set(TUIDIR "tui")
  add_subdirectory("${TUIDIR}")
  target_include_directories("${NAME}" PRIVATE
    "${CMAKE_CURRENT_SOURCE_DIR}/${TUIDIR}")
  list(APPEND OPTION_LIST "+TUI")
  if(MOUSE)
    list(APPEND OPTION_LIST "+MOUSE")
  else()
    list(APPEND OPTION_LIST "-MOUSE")
    list(APPEND MAN_EXCL M)
  endif()
  if(MENU)
    list(APPEND OPTION_LIST "+MENU")
  else()
    list(APPEND OPTION_LIST "-MENU")
  endif()
else()
  list(APPEND OPTION_LIST "-TUI")
endif()

if(UNICODE)
  check_include_file("wchar.h" HAVE_WCHAR_H)
  check_include_file("wctype.h" HAVE_WCTYPE_H)
  check_include_file("locale.h" HAVE_LOCALE_H)
  check_include_file("langinfo.h" HAVE_LANGINFO_H)
  list(APPEND OPTION_LIST "+UNICODE")
  set(WITH_UNICODE ON)
else()
  list(APPEND OPTION_LIST "-UNICODE")
endif()

if(NLS)
  set(USE_NLS ON)
  find_package(Gettext REQUIRED)
  find_package(Intl REQUIRED)
  if (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR
      CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR
      CMAKE_SYSTEM_NAME STREQUAL "Haiku")
    include_directories(${Intl_INCLUDE_DIRS})
    target_link_libraries("${NAME}" PRIVATE ${Intl_LIBRARIES})
  endif()
  set(LOCALEDIR "${CMAKE_INSTALL_FULL_LOCALEDIR}")
  set(LINGUAS es it pt uk)
  set(mo_files)
  foreach(lang ${LINGUAS})
#    GETTEXT_PROCESS_PO_FILES("${lang}" ALL INSTALL_DESTINATION "${LOCALEDIR}/"
#      PO_FILES "${CMAKE_CURRENT_SOURCE_DIR}/po/${lang}.po")
    set(_po "${CMAKE_CURRENT_SOURCE_DIR}/po/${lang}.po")
    set(_mo "${CMAKE_CURRENT_BINARY_DIR}/${lang}.mo")
    add_custom_command(OUTPUT "${_mo}"
      COMMAND "${GETTEXT_MSGFMT_EXECUTABLE}" -o "${_mo}" "${_po}"
      WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
      DEPENDS "${_po}"
    )
    install(FILES "${_mo}"
      DESTINATION "${CMAKE_INSTALL_LOCALEDIR}/${lang}/LC_MESSAGES/"
      RENAME "${PACKAGE_NAME}.mo")
    list(APPEND mo_files "${_mo}")
  endforeach()
  add_custom_target(pofiles ALL DEPENDS ${mo_files})
  list(APPEND OPTION_LIST "+NLS")
else()
  list(APPEND OPTION_LIST "-NLS")
endif()

if(DNS)
  set(resfn res_mkquery)
  set(reslib resolv)
  check_library_exists("${reslib}" "${resfn}" "${CMAKE_LIBRARY_PATH}"
    HAVE_RES_IN_RES)
  if(NOT HAVE_RES_IN_RES)
    set(res_lib network)
    check_library_exists("${reslib}" "__${resfn}" "${CMAKE_LIBRARY_PATH}"
      HAVE___RES_IN_NET)
    if(NOT HAVE___RES_IN_NET)
      message(WARNING "DNS is enabled, but resolver is not detected: \
suppose it's present in libc")
      set(reslib c)
    endif()
  endif()
  target_link_libraries("${NAME}" PRIVATE -l${reslib})
  #
  set(resfn res_nmkquery)
  check_function_exists("${resfn}" HAVE_RES_NMKQUERY)
  if(NOT HAVE_RES_NMKQUERY)
    check_library_exists("${reslib}" "__${resfn}" "${CMAKE_LIBRARY_PATH}"
      HAVE_RES___NMKQUERY)
    if(HAVE_RES___NMKQUERY)
      set(HAVE_RES_NMKQUERY ON)
    endif()
  endif()
  check_include_file("arpa/nameser.h" HAVE_ARPA_NAMESER_H)
  check_include_file("sys/types.h" HAVE_SYS_TYPES_H)
  target_sources("${NAME}" PRIVATE dns.c)
  list(APPEND OPTION_LIST "+DNS")
  set(ENABLE_DNS ON)
else()
  list(APPEND OPTION_LIST "-DNS")
  list(APPEND MAN_EXCL n)
  list(APPEND MAN_EXCL N)
endif()

list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE)
list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_DARWIN_C_SOURCE)
list(APPEND CMAKE_REQUIRED_DEFINITIONS -D__EXTENSIONS__)
check_symbol_exists(AI_IDN "netdb.h" HAVE_AI_IDN)
if(HAVE_AI_IDN)
  set(IDN ON)
else()
  pkg_check_modules(IDN IMPORTED_TARGET libidn2)
  if(IDN_FOUND)
    set(LIBIDN2 ON)
    set(IDN ON)
  else()
    pkg_check_modules(IDN REQUIRED IMPORTED_TARGET libidn)
    if(IDN_FOUND)
      set(LIBIDN ON)
      set(IDN ON)
    endif()
  endif()
  if(IDN)
    target_link_libraries("${NAME}" PRIVATE PkgConfig::IDN)
  endif()
endif()
if(IDN)
  list(APPEND OPTION_LIST "+IDN")
else()
  list(APPEND OPTION_LIST "-IDN")
endif()

if(IPINFO)
  target_sources("${NAME}" PRIVATE ipinfo.c)
  list(APPEND OPTION_LIST "+IPINFO")
  set(WITH_IPINFO ON)
else()
  list(APPEND OPTION_LIST "-IPINFO")
  list(APPEND MAN_EXCL l)
endif()

if(IPV6)
  list(APPEND OPTION_LIST "+IPV6")
  set(ENABLE_IPV6 ON)
else()
  list(APPEND OPTION_LIST "-IPV6")
  list(APPEND MAN_EXCL 4 6)
endif()

if(MPLS)
  list(APPEND OPTION_LIST "+MPLS")
  set(WITH_MPLS ON)
else()
  list(APPEND OPTION_LIST "-MPLS")
  list(APPEND MAN_EXCL e)
endif()

if(COLOR)
  list(APPEND OPTION_LIST "+COLOR")
  set(USE_COLOR ON)
else()
  list(APPEND OPTION_LIST "-COLOR")
endif()

if(OUTTXT)
  list(APPEND OPTION_OUTFMT "text")
  set(OUTPUT_FORMAT_TXT ON)
else()
  list(APPEND MAN_EXCL ot)
endif()
if(OUTCSV)
  list(APPEND OPTION_OUTFMT "csv")
  set(OUTPUT_FORMAT_CSV ON)
else()
  list(APPEND MAN_EXCL oc)
endif()
if(OUTTOON)
  list(APPEND OPTION_OUTFMT "toon")
  set(OUTPUT_FORMAT_TOON ON)
else()
  list(APPEND MAN_EXCL on)
endif()
if(OUTJSON)
  list(APPEND OPTION_OUTFMT "json")
  set(OUTPUT_FORMAT_JSON ON)
else()
  list(APPEND MAN_EXCL oj)
endif()
if(OUTXML)
  list(APPEND OPTION_OUTFMT "xml")
  set(OUTPUT_FORMAT_XML ON)
else()
  list(APPEND MAN_EXCL ox)
endif()
if(NOT OPTION_OUTFMT)
  list(APPEND MAN_EXCL o)
endif()

if(DEBTUI)
  list(APPEND OPTION_DEBLOG "tui")
  set(LOG_TUI ON)
endif()
if(DEBPOLL)
  list(APPEND OPTION_DEBLOG "poll")
  set(LOG_POLL ON)
endif()
if(DEBNET)
  list(APPEND OPTION_DEBLOG "net")
  set(LOG_NET ON)
endif()
if(DEBDNS)
  list(APPEND OPTION_DEBLOG "dns")
  set(LOG_DNS ON)
endif()
if(DEBIPINFO)
  list(APPEND OPTION_DEBLOG "ipinfo")
  set(LOG_IPINFO ON)
endif()

# test types: uint, ulong
#
set(UTYPES uint ulong)
foreach(t ${UTYPES})
  CHECK_TYPE_SIZE(${t} SIZEOF_T)
  if(SIZEOF_T)
    string(TOUPPER "HAVE_${t}" T_DEF)
    set(${T_DEF} ON)
  endif()
endforeach()

# misc aux checkouts
#
check_include_file("netdb.h" HAVE_NETDB_H)
check_include_file("sys/param.h" HAVE_SYS_PARAM_H)
#
function(fn_checkout)
  foreach(fn IN LISTS FN_LIST)
    string(TOUPPER "HAVE_${fn}" FN_DEF)
    check_library_exists(c "${fn}" "${CMAKE_LIBRARY_PATH}" "${FN_DEF}")
    if(NOT "${${FN_DEF}}")
      foreach(lib IN LISTS FN_LIBS)
        string(TOUPPER "${lib}_${FN_DEF}" LIB_FN_DEF)
        check_library_exists("${lib}" "${fn}"
          "${CMAKE_LIBRARY_PATH}" "${LIB_FN_DEF}")
        if("${${LIB_FN_DEF}}")
          set("${FN_DEF}" ON)
          get_target_property(TGT_LIBS "${NAME}" LINK_LIBRARIES)
          if(NOT "-l${lib}" IN_LIST TGT_LIBS)
            target_link_libraries("${NAME}" PRIVATE "-l${lib}")
          endif()
          break()
        endif()
      endforeach()
      if("${fn}" IN_LIST FN_MUST AND NOT "${${FN_DEF}}")
        message(FATAL_ERROR "No library with ${fn}() found")
      endif()
    endif()
  endforeach()
endfunction()
#
set(FN_LIBS m)
set(FN_LIST pow)
set(FN_MUST pow)
fn_checkout()
#
set(FN_LIBS socket network)
set(FN_LIST socket)
set(FN_MUST socket)
fn_checkout()
#
set(FN_LIBS c)
set(FN_LIST warnx arc4random_uniform localtime_r
  memmem getprogname program_invocation_short_name)
set(FN_MUST warnx)
fn_checkout()
#
check_symbol_exists(IP_TOS "netinet/in.h" HAVE_IP_TOS)
if(NOT HAVE_IP_TOS)
  list(APPEND MAN_EXCL q)
endif()
#

# exclude unset options from man page
file(MAKE_DIRECTORY "${MAN_PATH}")
configure_file("${MAN_PAGE}.in" "${MANUAL}" COPYONLY)
if(MAN_EXCL)
  file(READ "${MANUAL}" MAN_CONTENT)
  foreach(excl IN LISTS MAN_EXCL)
    string(REGEX REPLACE "\(\.ds o${excl} \"\)[^\n]*" "\\1"
      MAN_CONTENT ${MAN_CONTENT})
  endforeach()
  file(WRITE "${MANUAL}" ${MAN_CONTENT})
endif()

# fin
if(OPTION_OUTFMT)
  list(JOIN OPTION_OUTFMT "," OPTION_OUTFMT)
  list(APPEND OPTION_LIST "+OUTFMT=${OPTION_OUTFMT}")
else()
  list(APPEND OPTION_LIST "-OUTFMT")
  set(OPTION_OUTFMT OFF)
endif()
if(OPTION_DEBLOG)
  list(JOIN OPTION_DEBLOG "," OPTION_DEBLOG)
  list(APPEND OPTION_LIST "DEBLOG=${OPTION_DEBLOG}")
else()
  list(APPEND OPTION_LIST "-DEBLOG")
  set(OPTION_DEBLOG OFF)
endif()
list(JOIN OPTION_LIST " " OPTION_LIST)
set(BUILD_OPTIONS "${OPTION_LIST}")
set(CONFIG "config.h")
configure_file("${CONFIG}.cmake" "${CONFIG}" @ONLY)
target_compile_options("${NAME}" PRIVATE -include "${CONFIG}")

message("")
message(STATUS "SOCKET  ${RAWSTR}\t: Socket type")
if(RAW)
message(STATUS "CAP     ${LIBCAP}\t: Linux capabilities")
endif()
message(STATUS "TUI     ${TUI}\t: Text-based UI")
message(STATUS "COLOR   ${TUI}\t: Colored output")
if(TUI)
message(STATUS "MOUSE   ${MOUSE}\t: Mouse support (TUI mode)")
message(STATUS "MENU    ${MENU}\t: Menu library (TUI mode)")
endif()
message(STATUS "UNICODE ${UNICODE}\t: International encoding")
message(STATUS "NLS     ${NLS}\t: NLS messages")
message(STATUS "DNS     ${DNS}\t: Hostname / IP-address resolver")
message(STATUS "IDN     ${IDN}\t: International characters in domain names")
message(STATUS "IPINFO  ${IPINFO}\t: Extra information on IP-address")
message(STATUS "IPV6    ${IPV6}\t: IPv6 support")
message(STATUS "MPLS    ${MPLS}\t: MPLS decoding")
message(STATUS "OUTFMT  ${OPTION_OUTFMT}\t: plain output formats")
message(STATUS "DEBLOG  ${OPTION_DEBLOG}\t: debug via syslog")
message("")


# install
set(EXECDIR "${CMAKE_INSTALL_BINDIR}")
if(SBIN)
  set(EXECDIR "${CMAKE_INSTALL_SBINDIR}")
endif()
install(TARGETS "${NAME}" DESTINATION "${EXECDIR}")

if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.14.0")
  install(DIRECTORY "${MAN_PATH}" TYPE MAN)
else()
  install(DIRECTORY "${MAN_PATH}" DESTINATION ${CMAKE_INSTALL_MANDIR})
endif()

if(LIBCAP)
  find_program(SETCAP setcap)
  set(EXECCAPS "cap_net_raw+p")
  set(EXECFILE "${CMAKE_INSTALL_PREFIX}/${EXECDIR}/${NAME}")
  if(SETCAP)
    message(STATUS "Caps for '${EXECDIR}/${NAME}': ${EXECCAPS}")
    install(CODE "execute_process(COMMAND ${SETCAP} ${EXECCAPS}
      \$ENV{DESTDIR}${EXECFILE})")
  else()
    message(STATUS "NOTE: to run '${EXECFILE}' it needs raw socket permissions")
  endif()
  message("")
endif()

