message (STATUS "Running with CMake ${CMAKE_VERSION}")
cmake_minimum_required (VERSION 3.15)

project (agentx_pp VERSION 2.6.1 LANGUAGES CXX)
enable_language (CXX)

include (CheckIncludeFiles)
include (CheckFunctionExists)
include (CheckSymbolExists)
include (CheckLibraryExists)
include (CheckStructHasMember)
include (CheckTypeSize)

set (CMAKE_CXX_STANDARD 14)

list (APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules)

option (OPTION_NAMESPACE  "Use Namespaces" ON)
option (OPTION_PROXY  "Enable Proxy" ON)
option (OPTION_PROXY_FORWARDER  "Enable Proxy Forwarder" ON)
option (OPTION_MASTER  "Enable support for master agent" ON)
option (OPTION_SUBAGENT  "Enable support for subagent" ON)
option (OPTION_AGENPRO_MASTER  "build with AgenPro master templates (usually disabled)" OFF)
option (OPTION_AGENPRO_SUBAGENT  "build with AgenPro subagent templates (usually disabled)" OFF)
option (OPTION_EXAMPLES  "build examples" ON)

set (VERSION ${PROJECT_VERSION})
set (AGENTX_PP_MAJOR_VERSION ${PROJECT_VERSION_MAJOR})
set (AGENTX_PP_MINOR_VERSION ${PROJECT_VERSION_MINOR})
set (AGENTX_PP_MICRO_VERSION ${PROJECT_VERSION_PATCH})

message (STATUS "agentx++ Version ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")

check_include_files (sys/types.h CNF_HAVE_SYS_TYPES_H)
check_include_files (sys/stat.h CNF_HAVE_SYS_STAT_H)
check_include_files ("cctype;cerrno;climits;csignal;cstddef;cstdio;cstdlib;cstring;ctime" CNF_STDCXX_98_HEADERS)
check_include_files ("iostream" CNF_HAVE_IOSTREAM)
check_include_files ("stdlib.h;stddef.h" CNF_STDC_HEADERS)
check_include_files (stdlib.h CNF_HAVE_STDLIB_H)
check_include_files (string.h CNF_HAVE_STRING_H)
check_include_files (memory.h CNF_HAVE_MEMORY_H)
check_include_files (strings.h CNF_HAVE_STRINGS_H)
check_include_files (inttypes.h CNF_HAVE_INTTYPES_H)
check_include_files (ctype.h CNF_HAVE_CTYPE_H)
check_include_files (ctype.h HAVE_CTYPE_H)
check_include_files (signal.h CNF_HAVE_SIGNAL_H)
check_include_files (errno.h CNF_HAVE_ERRNO_H)
check_include_files (time.h CNF_HAVE_TIME_H)
check_include_files (unistd.h CNF_HAVE_UNISTD_H)
check_include_files (sys/unistd.h CNF_HAVE_SYS_UNISTD_H)
check_include_files (stdint.h CNF_HAVE_STDINT_H)
check_include_files (sys/time.h CNF_HAVE_SYS_TIME_H)
check_include_files (sys/param.h CNF_HAVE_SYS_PARAM_H)
check_include_files (sys/timeb.h CNF_HAVE_SYS_TIMEB_H)
check_include_files (winsock2.h CNF_HAVE_WINSOCK2_H)
check_include_files (ws2tcpip.h CNF_HAVE_WS2TCPIP_H)
check_include_files (wspiapi.h CNF_HAVE_WSPIAPI_H)
check_include_files (winsock.h CNF_HAVE_WINSOCK_H)
check_include_files (netdb.h CNF_HAVE_NETDB_H)
check_include_files (sys/socket.h CNF_HAVE_SYS_SOCKET_H)
check_include_files (arpa/inet.h CNF_HAVE_ARPA_INET_H)
check_include_files (arpa/inet.h HAVE_ARPA_INET_H)
check_include_files (netinet/in.h CNF_HAVE_NETINET_IN_H)
check_include_files (poll.h CNF_HAVE_POLL_H)
check_include_files (sys/select.h CNF_HAVE_SYS_SELECT_H)
check_include_files (io.h CNF_HAVE_IO_H)
check_include_files (process.h CNF_HAVE_PROCESS_H)
check_include_files (pthread.h CNF_HAVE_PTHREAD_H)
check_function_exists ("strcasecmp" CNF_HAVE_STRCASECMP)
check_function_exists ("stricmp" CNF_HAVE_STRICMP)
check_function_exists ("getpid" CNF_HAVE_GETPID)
check_function_exists ("_getpid" CNF_HAVE__GETPID)

check_function_exists ("clock_gettime" HAVE_CLOCK_GETTIME)
check_function_exists ("getaddrinfo" HAVE_GETADDRINFO)
check_function_exists ("gethostbyaddr" HAVE_GETHOSTBYADDR)
check_function_exists ("gethostbyaddr_r" HAVE_GETHOSTBYADDR_R)
check_function_exists ("gethostbyname" HAVE_GETHOSTBYNAME)
check_function_exists ("gethostbyname2" HAVE_GETHOSTBYNAME2)
check_function_exists ("gethostbyname_r" HAVE_GETHOSTBYNAME_R)
check_function_exists ("gethostname" HAVE_GETHOSTNAME)
check_function_exists ("getpid" HAVE_GETPID)
check_function_exists ("gettimeofday" HAVE_GETTIMEOFDAY)
check_function_exists ("htonl" HAVE_HTONL)
check_function_exists ("inet_aton" HAVE_INET_ATON)
check_function_exists ("inet_ntoa" HAVE_INET_NTOA)
check_function_exists ("inet_ntop" HAVE_INET_NTOP)
check_function_exists ("inet_pton" HAVE_INET_PTON)
check_include_files (inttypes.h HAVE_INTTYPES_H)
check_include_files (io.h HAVE_IO_H)
check_function_exists ("isdigit" HAVE_ISDIGIT)
check_include_files (limits.h HAVE_LIMITS_H)
check_function_exists ("localtime_r" HAVE_LOCALTIME_R)
check_function_exists ("malloc" HAVE_MALLOC)
check_include_files (memory.h HAVE_MEMORY_H)
check_function_exists ("memset" HAVE_MEMSET)
check_include_files (netdb.h HAVE_NETDB_H)
check_include_files (netinet/in.h HAVE_NETINET_IN_H)
check_function_exists ("poll" HAVE_POLL)
check_include_files (poll.h HAVE_POLL_H)
check_include_files (process.h HAVE_PROCESS_H)
check_include_files (pthread.h HAVE_PTHREAD) # :-(
check_function_exists ("realloc" HAVE_REALLOC)
check_function_exists ("select" HAVE_SELECT)
check_include_files (signal.h HAVE_SIGNAL_H)
check_function_exists ("socket" HAVE_SOCKET)
check_include_files (stdint.h HAVE_STDINT_H)
check_include_files (stdio.h HAVE_STDIO_H)
check_include_files (stdlib.h HAVE_STDLIB_H)
check_function_exists ("strcasecmp" HAVE_STRCASECMP)
check_function_exists ("strchr" HAVE_STRCHR)
check_function_exists ("strerror" HAVE_STRERROR)
check_function_exists ("stricmp" HAVE_STRICMP)
check_include_files (strings.h HAVE_STRINGS_H)
check_include_files (string.h HAVE_STRING_H)
check_function_exists ("strstr" HAVE_STRSTR)
check_include_files (sys/param.h HAVE_SYS_PARAM_H)
check_include_files (sys/select.h HAVE_SYS_SELECT_H)
check_include_files (sys/socket.h HAVE_SYS_SOCKET_H)
check_include_files (sys/stat.h HAVE_SYS_STAT_H)
check_include_files (sys/timeb.h HAVE_SYS_TIMEB_H)
check_include_files (sys/time.h HAVE_SYS_TIME_H)
check_include_files (sys/types.h HAVE_SYS_TYPES_H)
check_include_files (sys/unistd.h HAVE_SYS_UNISTD_H)
check_include_files (time.h HAVE_TIME_H)
check_include_files (unistd.h HAVE_UNISTD_H)
check_include_files (winsock2.h HAVE_WINSOCK2_H)
check_include_files (winsock.h HAVE_WINSOCK_H)
check_include_files (ws2tcpip.h HAVE_WS2TCPIP_H)
check_include_files (wspiapi.h HAVE_WSPIAPI_H)
check_function_exists ("_getpid" HAVE__GETPID)

check_type_size ("pthread_t" SIZEOF_PTHREAD_T LANGUAGE CXX)
check_type_size ("long" SIZEOF_LONG LANGUAGE CXX)
check_type_size ("int" SIZEOF_INT LANGUAGE CXX)

if (SIZEOF_PTHREAD_T EQUAL SIZEOF_LONG)
  set (AGENTPP_OPAQUE_PTHREAD_T "long")
elseif (SIZEOF_PTHREAD_T EQUAL SIZEOF_INT)
  set (AGENTPP_OPAQUE_PTHREAD_T "long")
else ()
  message (FATAL_ERROR "Can't map pthread_t to int or long")
endif ()
message (STATUS "Found AGENTPP_OPAQUE_PTHREAD_T: ${AGENTPP_OPAQUE_PTHREAD_T}")


set (SOCKADDR_HEADERS)
if (CNF_HAVE_WINSOCK2_H)
   set (SOCKADDR_HEADERS ${SOCKADDR_HEADERS} winsock2.h)
endif ()
if (CNF_HAVE_WS2TCPIP_H)
   set (SOCKADDR_HEADERS ${SOCKADDR_HEADERS} ws2tcpip.h)
endif ()
if (CNF_HAVE_WINSOCK_H)
   set (SOCKADDR_HEADERS ${SOCKADDR_HEADERS} winsock.h)
endif ()
if (CNF_HAVE_NETDB_H)
   set (SOCKADDR_HEADERS ${SOCKADDR_HEADERS} netdb.h)
endif ()
if (CNF_HAVE_SYS_SOCKET_H)
   set (SOCKADDR_HEADERS ${SOCKADDR_HEADERS} sys/socket.h)
endif ()
if (CNF_HAVE_ARPA_INET_H)
   set (SOCKADDR_HEADERS ${SOCKADDR_HEADERS} arpa/inet.h)
endif ()
if (CNF_HAVE_NETINET_IN_H)
   set (SOCKADDR_HEADERS ${SOCKADDR_HEADERS} netinet/in.h)
endif ()

check_struct_has_member ("struct sockaddr_storage"
            ss_family "${SOCKADDR_HEADERS}"
            CNF_HAVE_STRUCT_SOCKADDR_STORAGE_SS_FAMILY LANGUAGE CXX)

check_struct_has_member ("struct sockaddr_storage"
            __ss_family "${SOCKADDR_HEADERS}"
            CNF_HAVE_STRUCT_SOCKADDR_STORAGE___SS_FAMILY LANGUAGE CXX)

check_type_size ("bool" SIZEOF_BOOL LANGUAGE CXX)

if (SIZEOF_BOOL)
  message (STATUS "HAVE_BOOL")
  set (NEED_DEFINE_BOOL 0)
else ()
  message (STATUS "NO HAVE_BOOL")
  set (NEED_DEFINE_BOOL 1)
endif ()

if (OPTION_PROXY)
  set (WITH_PROXY 1)
else ()
  set (WITH_PROXY 0)
endif ()

if (OPTION_PROXY_FORWARDER)
  set (WITH_PROXY_FORWARDER 1)
else ()
  set (WITH_PROXY_FORWARDER 0)
endif ()

if (OPTION_NAMESPACE)
  set (WITH_NAMESPACE 1)
else ()
  set (WITH_NAMESPACE 0)
endif ()

if (OPTION_MASTER)
  set (WITH_MASTERE 1)
else ()
  set (WITH_MASTER 0)
endif ()

if (OPTION_SUBAGENT)
  set (WITH_SUBAGENT 1)
else ()
  set (WITH_SUBAGENT 0)
endif ()


if (CNF_STDC_HEADERS)
  set (STDC_HEADERS 1)
else ()
  set (STDC_HEADERS 0)
endif ()

if (CNF_STDCXX_98_HEADERS)
  set (STDCXX_98_HEADERS 1)
else ()
  set (STDCXX_98_HEADERS 0)
endif ()

check_include_files (malloc.h HAVE_MALLOC_H)
check_include_files ("sys/param.h;sys/mount.h" HAVE_SYS_MOUNT_H)
check_function_exists ("strcasecmp" HAVE_STRCASECMP)

set (CMAKE_REQUIRED_LINK_OPTIONS "-lpthread")
check_function_exists (pthread_mutex_timedlock HAVE_PTHREAD_MUTEX_TIMEDLOCK)

find_package (snmp_pp)
if (NOT SNMP_PP_FOUND)
  message (FATAL_ERROR " Lib snmp++ NOT found, try to call cmake with '-D SNMP_PP_ROOT_DIR=/install/path')")
endif ()

find_package (agent_pp)
if (NOT AGENT_PP_FOUND)
  message (FATAL_ERROR " Lib agent++ NOT found, try to call cmake with '-D AGENT_PP_ROOT_DIR=/install/path')")
endif ()

configure_file (${CMAKE_CURRENT_SOURCE_DIR}/cmake_config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/config.h)
configure_file (${CMAKE_CURRENT_SOURCE_DIR}/cmake_libagentx.h.in ${CMAKE_CURRENT_SOURCE_DIR}/libagentx.h)
configure_file (${CMAKE_CURRENT_SOURCE_DIR}/include/agentx_pp/agentx++.h.in ${CMAKE_CURRENT_SOURCE_DIR}/include/agentx_pp/agentx++.h)

set (CMAKE_CXX_FLAGS  "${CMAKE_CXX_FLAGS} -DHAVE_CONFIG_H")

if (MSVC)
    # warning level 4 (all warnings as errors: /WX)
    add_compile_options (/W4 /WX)
else ()
    # lots of warnings (-Wextra -pedantic)
    add_compile_options (-Wall )
endif ()

include_directories (${CMAKE_BINARY_DIR})
include_directories (.)
include_directories (include)
include_directories (include/agent_pp)
include_directories (include/system)
include_directories (agent_pp)

include_directories (${SNMP_PP_INCLUDE_DIR})
link_libraries (${SNMP_PP_LIBRARIES})

include_directories (${AGENT_PP_INCLUDE_DIR})
link_libraries (${AGENT_PP_LIBRARIES})


set (MY_HEADER_FILES
  include/agentx_pp/agentpp_agentx_mib.h
  include/agentx_pp/agentx_def.h
  include/agentx_pp/agentx.h
  include/agentx_pp/agentx++.h
  include/agentx_pp/agentx_index.h
  include/agentx_pp/agentx_master.h
  include/agentx_pp/agentx_mib.h
  include/agentx_pp/agentx_node.h
  include/agentx_pp/agentx_pdu.h
  include/agentx_pp/agentx_peer.h
  include/agentx_pp/agentx_queue.h
  include/agentx_pp/agentx_reg.h
  include/agentx_pp/agentx_request.h
  include/agentx_pp/agentx_session.h
  include/agentx_pp/agentx_subagent.h
  include/agentx_pp/agentx_threads.h
)

set (MY_HEADER_LIB_FILES
  libagentx.h
)

set (MY_SRC_FILES
  src/agentpp_agentx_mib.cpp
  src/agentx.cpp
  src/agentx_index.cpp
  src/agentx_master.cpp
  src/agentx_mib.cpp
  src/agentx_node.cpp
  src/agentx_pdu.cpp
  src/agentx_peer.cpp
  src/agentx_queue.cpp
  src/agentx_reg.cpp
  src/agentx_request.cpp
  src/agentx_session.cpp
  src/agentx_subagent.cpp
)

add_library (agentx++ SHARED
  ${MY_HEADER_FILES}
  ${MY_HEADER_LIB_FILES}
  config.h
  ${MY_SRC_FILES}
)

set_property (TARGET agentx++ PROPERTY POSITION_INDEPENDENT_CODE 1)
# Set .so version as autoconf does: agentx++ version 1.2.3 will go to .so.12.0.3!
set_target_properties (agentx++ PROPERTIES VERSION ${PROJECT_VERSION_MAJOR}${PROJECT_VERSION_MINOR}.0.${PROJECT_VERSION_PATCH} SOVERSION ${PROJECT_VERSION_MAJOR}${PROJECT_VERSION_MINOR})

macro (subdirlist result curdir)
  #message (INFO " # Get subdirectories for ${curdir} ")
  file (GLOB children RELATIVE ${curdir} ${curdir}/*)
  set (dirlist "")
  foreach (child ${children})
    #message (INFO " ## check ${child}")
    if (IS_DIRECTORY ${curdir}/${child})
      #message (INFO " ### Add ${child}")
      list (APPEND dirlist ${child})
    endif ()
  endforeach ()
  set (${result} ${dirlist})
endmacro ()

set (EXE_BASE_DIRS )

if (OPTION_AGENPRO_AGENT OR OPTION_AGENPRO_SUBAGENT)
  list (APPEND EXE_BASE_DIRS agenpro)
endif ()

if (OPTION_EXAMPLES)
  list (APPEND EXE_BASE_DIRS examples)
endif ()

foreach (EXE_BASE_DIR ${EXE_BASE_DIRS})
  message (INFO " # Process dir ${EXE_BASE_DIR}")
  subdirlist (EXE_SUB_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/${EXE_BASE_DIR})
  foreach (EXE_SUB_DIR ${EXE_SUB_DIRS})
    message (INFO " ## Process executable in ${EXE_BASE_DIR}/${EXE_SUB_DIR}")

    if (OPTION_PROXY_FORWARDER AND
       (${EXE_SUB_DIR} STREQUAL "proxy") AND
       (${EXE_BASE_DIR} STREQUAL "examples"))
      message (INFO " ## Do not build ${EXE_BASE_DIR}/${EXE_SUB_DIR}, as OPTION_PROXY_FORWARDER is enabled")
      continue ()
    endif ()

    if (${EXE_BASE_DIR} STREQUAL "agenpro")
      if ((${EXE_SUB_DIR} STREQUAL "master") AND
         NOT OPTION_AGENPRO_AGENT)
        message (INFO " ## Do not build ${EXE_BASE_DIR}/${EXE_SUB_DIR}, as OPTION_AGENPRO_AGENT is disabled")
        continue ()
      endif ()
      if ((${EXE_SUB_DIR} STREQUAL "subagent") AND
         NOT OPTION_AGENPRO_SUBAGENT)
        message (INFO " ## Do not build ${EXE_BASE_DIR}/${EXE_SUB_DIR}, OPTION_AGENPRO_SUBAGENT is disabled")
        continue ()
      endif ()
    endif ()

    if (${EXE_BASE_DIR} STREQUAL "examples")
      if (${EXE_SUB_DIR} STREQUAL "subagent")
        message (INFO " ## examples/subagent needs special handling...")
        continue ()
      endif ()
    endif ()

    file (GLOB EXE_SRC
      "${CMAKE_CURRENT_SOURCE_DIR}/${EXE_BASE_DIR}/${EXE_SUB_DIR}/include/*.h"
      "${CMAKE_CURRENT_SOURCE_DIR}/${EXE_BASE_DIR}/${EXE_SUB_DIR}/src/*.cpp"
      )

    add_executable (${EXE_BASE_DIR}${EXE_SUB_DIR} ${EXE_SRC})
    target_link_libraries (${EXE_BASE_DIR}${EXE_SUB_DIR} agentx++ ${AGENT_PP_LIBRARIES} ${SNMP_PP_LIBRARIES})
    target_include_directories (${EXE_BASE_DIR}${EXE_SUB_DIR} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/${EXE_BASE_DIR}/${EXE_SUB_DIR}/include/")
    set_target_properties (${EXE_BASE_DIR}${EXE_SUB_DIR} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${EXE_BASE_DIR}/${EXE_SUB_DIR}/src")
    set_target_properties (${EXE_BASE_DIR}${EXE_SUB_DIR} PROPERTIES OUTPUT_NAME "${EXE_SUB_DIR}")

  endforeach ()
endforeach ()


if (OPTION_EXAMPLES)
  message (INFO " ## Process executable examples/subagent")

  set (EXE_INC 
      ${CMAKE_CURRENT_SOURCE_DIR}/examples/subagent/include/agentpp_notifytest_mib.h
      ${CMAKE_CURRENT_SOURCE_DIR}/examples/subagent/include/agentpp_test_mib.h
      ${CMAKE_CURRENT_SOURCE_DIR}/examples/subagent/include/atm_mib.h
      ${CMAKE_CURRENT_SOURCE_DIR}/examples/subagent/include/if_mib.h
    )

  set (EXE_SRC
      ${CMAKE_CURRENT_SOURCE_DIR}/examples/subagent/src/atm_mib.cpp
      ${CMAKE_CURRENT_SOURCE_DIR}/examples/subagent/src/if_mib.cpp
      ${CMAKE_CURRENT_SOURCE_DIR}/examples/subagent/src/subagent.cpp
    ) 

  set (EXE_SRC_2
      ${CMAKE_CURRENT_SOURCE_DIR}/examples/subagent/src/agentpp_notifytest_mib.cpp
      ${CMAKE_CURRENT_SOURCE_DIR}/examples/subagent/src/agentpp_test_mib.cpp
      ${CMAKE_CURRENT_SOURCE_DIR}/examples/subagent/src/atm_mib.cpp
      ${CMAKE_CURRENT_SOURCE_DIR}/examples/subagent/src/if_mib.cpp
      ${CMAKE_CURRENT_SOURCE_DIR}/examples/subagent/src/subagent2.cpp
    ) 

  add_executable (subagent ${EXE_SRC} ${EXE_INC})
  target_link_libraries (subagent agentx++ ${AGENT_PP_LIBRARIES} ${SNMP_PP_LIBRARIES})
  target_include_directories (subagent PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/examples/subagent/include/")
  set_target_properties (subagent PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/examples/subagent/src")

  add_executable (subagent2 ${EXE_SRC_2} ${EXE_INC})
  target_link_libraries (subagent2 agentx++ ${AGENT_PP_LIBRARIES} ${SNMP_PP_LIBRARIES})
  target_include_directories (subagent2 PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/examples/subagent/include/")
  set_target_properties (subagent2 PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/examples/subagent/src")

endif ()


add_library (agentx++_static STATIC
  ${MY_HEADER_FILES}
  ${MY_SRC_FILES}
)

install (TARGETS agentx++ DESTINATION lib)
install (TARGETS agentx++_static DESTINATION lib)
install (FILES ${MY_HEADER_LIB_FILES} DESTINATION include)
install (FILES ${MY_HEADER_FILES} DESTINATION include/agentx_pp)

