#
# Try to find libpcap.
#
# To tell this module where to look, a user may set the environment variable
# PCAP_ROOT to point cmake to the *root* of a directory with include and
# lib subdirectories for pcap.dll (e.g WpdPack or npcap-sdk).
# Alternatively, PCAP_ROOT may also be set from cmake command line or GUI
# (e.g cmake -DPCAP_ROOT=C:\path\to\pcap [...])
#

if(WIN32)
  #
  # Building for Windows.
  #
  # libpcap isn't set up to install .pc files or pcap-config on Windows,
  # and it's not clear that either of them would work without a lot
  # of additional effort.  WinPcap doesn't supply them, and neither
  # does Npcap.
  #
  # So just search for them directly.  Look for both pcap and wpcap.
  # Don't bother looking for static libraries; unlike most UN*Xes
  # (with the exception of AIX), where different extensions are used
  # for shared and static, Windows uses .lib both for import libraries
  # for DLLs and for static libraries.
  #
  # We don't directly set PCAP_INCLUDE_DIRS or PCAP_LIBRARIES, as
  # they're not supposed to be cache entries, and find_path() and
  # find_library() set cache entries.
  #
  find_path(PCAP_INCLUDE_DIR pcap.h)

  # The 64-bit Packet.lib is located under /x64
  if(CMAKE_SIZEOF_VOID_P EQUAL 8)
    #
    # For the WinPcap and Npcap SDKs, the Lib subdirectory of the top-level
    # directory contains 32-bit libraries; the 64-bit libraries are in the
    # Lib/x64 directory.
    #
    # The only way to *FORCE* CMake to look in the Lib/x64 directory
    # without searching in the Lib directory first appears to be to set
    # CMAKE_LIBRARY_ARCHITECTURE to "x64".
    #
    set(CMAKE_LIBRARY_ARCHITECTURE "x64")
  endif()
  find_library(PCAP_LIBRARY NAMES pcap wpcap)

  #
  # Do the standard arg processing, including failing if it's a
  # required package.
  #
  include(FindPackageHandleStandardArgs)
  find_package_handle_standard_args(PCAP
    DEFAULT_MSG
    PCAP_INCLUDE_DIR
    PCAP_LIBRARY
  )
  mark_as_advanced(
    PCAP_INCLUDE_DIR
    PCAP_LIBRARY
  )
  if(PCAP_FOUND)
    set(PCAP_LIBRARIES ${PCAP_LIBRARY})
    set(PCAP_INCLUDE_DIRS ${PCAP_INCLUDE_DIR})
  endif()
else(WIN32)
  #
  # Building for UN*X.
  #
  # See whether we were handed a QUIET argument, so we can pass it on
  # to pkg_search_module.  Do *NOT* pass on the REQUIRED argument,
  # because, if pkg-config isn't found, or it is but it has no .pc
  # files for libpcap, that is *not* necessarily an indication that
  # libpcap isn't available - not all systems ship pkg-config, and
  # libpcap didn't have .pc files until libpcap 1.9.0.
  #
  if(PCAP_FIND_QUIETLY)
    set(_quiet "QUIET")
  endif()

  #
  # First, try pkg-config.
  # Before doing so, set the PKG_CONFIG_PATH environment variable
  # to include all the directories in CMAKE_PREFIX_PATH.
  #
  # *If* we were to require CMake 3.1 or later on UN*X,
  # pkg_search_module() would do this for us, but, for now,
  # we're not doing that, in case somebody's building with
  # CMake on some "long-term support" version, predating
  # CMake 3.1, of an OS that supplies an earlier
  # version as a package.
  #
  # If we ever set a minimum of 3.1 or later on UN*X, we should
  # remove the environment variable changes.
  #
  # This is based on code in the CMake 3.12.4 FindPkgConfig.cmake,
  # which is "Distributed under the OSI-approved BSD 3-Clause License."
  #
  find_package(PkgConfig)

  #
  # Get the current PKG_CONFIG_PATH setting.
  #
  set(_pkg_config_path "$ENV{PKG_CONFIG_PATH}")

  #
  # Save it, so we can restore it after we run pkg-config.
  #
  set(_saved_pkg_config_path "${_pkg_config_path}")

  if(NOT "${CMAKE_PREFIX_PATH}" STREQUAL "")
    #
    # Convert it to a CMake-style path, before we add additional
    # values to it.
    #
    if(NOT "${_pkg_config_path}" STREQUAL "")
      file(TO_CMAKE_PATH "${_pkg_config_path}" _pkg_config_path)
    endif()

    #
    # Turn CMAKE_PREFIX_PATH into a list of extra paths to add
    # to _pkg_config_path.
    #
    set(_extra_paths "")
    list(APPEND _extra_paths ${CMAKE_PREFIX_PATH})

    # Create a list of the possible pkgconfig subfolder (depending on
    # the system
    set(_lib_dirs)
    if(NOT DEFINED CMAKE_SYSTEM_NAME
        OR (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU)$"
            AND NOT CMAKE_CROSSCOMPILING))
      if(EXISTS "/etc/debian_version") # is this a debian system ?
        if(CMAKE_LIBRARY_ARCHITECTURE)
          list(APPEND _lib_dirs "lib/${CMAKE_LIBRARY_ARCHITECTURE}/pkgconfig")
        endif()
      else()
        # not debian, check the FIND_LIBRARY_USE_LIB32_PATHS and FIND_LIBRARY_USE_LIB64_PATHS properties
        get_property(uselib32 GLOBAL PROPERTY FIND_LIBRARY_USE_LIB32_PATHS)
        if(uselib32 AND CMAKE_SIZEOF_VOID_P EQUAL 4)
          list(APPEND _lib_dirs "lib32/pkgconfig")
        endif()
        get_property(uselib64 GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS)
        if(uselib64 AND CMAKE_SIZEOF_VOID_P EQUAL 8)
          list(APPEND _lib_dirs "lib64/pkgconfig")
        endif()
        get_property(uselibx32 GLOBAL PROPERTY FIND_LIBRARY_USE_LIBX32_PATHS)
        if(uselibx32 AND CMAKE_INTERNAL_PLATFORM_ABI STREQUAL "ELF X32")
          list(APPEND _lib_dirs "libx32/pkgconfig")
        endif()
      endif()
    endif()
    if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" AND NOT CMAKE_CROSSCOMPILING)
      list(APPEND _lib_dirs "libdata/pkgconfig")
    endif()
    list(APPEND _lib_dirs "lib/pkgconfig")
    list(APPEND _lib_dirs "share/pkgconfig")

    # Check if directories exist and eventually append them to the
    # pkgconfig path list
    foreach(_prefix_dir ${_extra_paths})
      foreach(_lib_dir ${_lib_dirs})
        if(EXISTS "${_prefix_dir}/${_lib_dir}")
          list(APPEND _pkg_config_path "${_prefix_dir}/${_lib_dir}")
          list(REMOVE_DUPLICATES _pkg_config_path)
        endif()
      endforeach()
    endforeach()

    if(NOT "${_pkg_config_path}" STREQUAL "")
      # remove empty values from the list
      list(REMOVE_ITEM _pkg_config_path "")
      file(TO_NATIVE_PATH "${_pkg_config_path}" _pkg_config_path)
      if(UNIX)
        string(REPLACE ";" ":" _pkg_config_path "${_pkg_config_path}")
        string(REPLACE "\\ " " " _pkg_config_path "${_pkg_config_path}")
      endif()
      set(ENV{PKG_CONFIG_PATH} "${_pkg_config_path}")
    endif()
  endif()
  pkg_search_module(CONFIG_PCAP ${_quiet} libpcap)
  set(ENV{PKG_CONFIG_PATH} "${_saved_pkg_config_path}")

  if(NOT CONFIG_PCAP_FOUND)
    #
    # That didn't work.  Try pcap-config.
    #
    find_program(PCAP_CONFIG pcap-config)
    if(PCAP_CONFIG)
      #
      # We have pcap-config; use it.
      #
      if(NOT "${_quiet}" STREQUAL "QUIET")
        message(STATUS "Found pcap-config")
      endif()

      #
      # If this is a vendor-supplied pcap-config, which we define as
      # being "a pcap-config in /usr/bin or /usr/ccs/bin" (the latter
      # is for Solaris and Sun/Oracle Studio), there are some issues.
      # Work around them.
      #
      if("${PCAP_CONFIG}" STREQUAL /usr/bin/pcap-config OR
         "${PCAP_CONFIG}" STREQUAL /usr/ccs/bin/pcap-config)
        #
        # It's vendor-supplied.
        #
        if(APPLE)
          #
          # This is macOS or another Darwin-based OS.
          #
          # That means that /usr/bin/pcap-config it may provide
          # -I/usr/local/include with --cflags and -L/usr/local/lib
          # with --libs; if there's no pcap installed under /usr/local,
          # that will cause the build to fail, and if there is a pcap
          # installed there, you'll get that pcap even if you don't
          # want it.  Remember that, so we ignore those values.
          #
          set(_broken_apple_pcap_config TRUE)
        elseif(CMAKE_SYSTEM_NAME STREQUAL "SunOS" AND CMAKE_SYSTEM_VERSION MATCHES "5[.][0-9.]*")
          #
          # This is Solaris 2 or later, i.e. SunOS 5.x.
          #
          # At least on Solaris 11; there's /usr/bin/pcap-config, which
          # reports -L/usr/lib with --libs, causing the 32-bit libraries
          # to be found, and there's /usr/bin/{64bitarch}/pcap-config,
          # where {64bitarch} is a name for the 64-bit version of the
          # instruction set, which reports -L /usr/lib/{64bitarch},
          # causing the 64-bit libraries to be found.
          #
          # So if we're building 64-bit targets, we replace PCAP_CONFIG
          # with /usr/bin/{64bitarch}; we get {64bitarch} as the
          # output of "isainfo -n".
          #
          if(CMAKE_SIZEOF_VOID_P EQUAL 8)
            execute_process(COMMAND "isainfo" "-n"
              RESULT_VARIABLE ISAINFO_RESULT
              OUTPUT_VARIABLE ISAINFO_OUTPUT
              OUTPUT_STRIP_TRAILING_WHITESPACE
            )
            if(ISAINFO_RESULT EQUAL 0)
              #
              # Success - change PCAP_CONFIG.
              #
              string(REPLACE "/bin/" "/bin/${ISAINFO_OUTPUT}/" PCAP_CONFIG "${PCAP_CONFIG}")
            endif()
          endif()
        endif()
      endif()

      #
      # Now get the include directories.
      #
      execute_process(COMMAND "${PCAP_CONFIG}" "--cflags"
        RESULT_VARIABLE PCAP_CONFIG_RESULT
        OUTPUT_VARIABLE PCAP_CONFIG_OUTPUT
        OUTPUT_STRIP_TRAILING_WHITESPACE
      )
      if(NOT PCAP_CONFIG_RESULT EQUAL 0)
        message(FATAL_ERROR "pcap-config --cflags failed")
      endif()
      separate_arguments(CFLAGS_LIST UNIX_COMMAND ${PCAP_CONFIG_OUTPUT})
      set(CONFIG_PCAP_INCLUDE_DIRS "")
      foreach(_arg IN LISTS CFLAGS_LIST)
        if(_arg MATCHES "^-I")
          #
          # Extract the directory by removing the -I.
          #
          string(REGEX REPLACE "-I" "" _dir ${_arg})
          #
          # Work around macOS (and probably other Darwin) brokenness,
          # by not adding /usr/local/include if it's from the broken
          # Apple pcap-config.
          #
          if(NOT _broken_apple_pcap_config OR
             NOT "${_dir}" STREQUAL /usr/local/include)
            # Add it to CONFIG_PCAP_INCLUDE_DIRS
            list(APPEND CONFIG_PCAP_INCLUDE_DIRS ${_dir})
          endif()
        endif()
      endforeach()

      #
      # Now, get the library directories and libraries for dynamic linking.
      #
      execute_process(COMMAND "${PCAP_CONFIG}" "--libs"
        RESULT_VARIABLE PCAP_CONFIG_RESULT
        OUTPUT_VARIABLE PCAP_CONFIG_OUTPUT
        OUTPUT_STRIP_TRAILING_WHITESPACE
      )
      if(NOT PCAP_CONFIG_RESULT EQUAL 0)
        message(FATAL_ERROR "pcap-config --libs failed")
      endif()
      separate_arguments(LIBS_LIST UNIX_COMMAND ${PCAP_CONFIG_OUTPUT})
      set(CONFIG_PCAP_LIBRARY_DIRS "")
      set(CONFIG_PCAP_LIBRARIES "")
      foreach(_arg IN LISTS LIBS_LIST)
        if(_arg MATCHES "^-L")
          #
          # Extract the directory by removing the -L.
          #
          string(REGEX REPLACE "-L" "" _dir ${_arg})
          #
          # Work around macOS (and probably other Darwin) brokenness,
          # by not adding /usr/local/lib if it's from the broken
          # Apple pcap-config.
          #
          if(NOT _broken_apple_pcap_config OR
             NOT "${_dir}" STREQUAL /usr/local/lib)
            # Add this directory to CONFIG_PCAP_LIBRARY_DIRS
            list(APPEND CONFIG_PCAP_LIBRARY_DIRS ${_dir})
          endif()
        elseif(_arg MATCHES "^-l")
          string(REGEX REPLACE "-l" "" _lib ${_arg})
          list(APPEND CONFIG_PCAP_LIBRARIES ${_lib})
        endif()
      endforeach()

      #
      # Now, get the library directories and libraries for static linking.
      #
      execute_process(COMMAND "${PCAP_CONFIG}" "--libs" "--static"
        RESULT_VARIABLE PCAP_CONFIG_RESULT
        OUTPUT_VARIABLE PCAP_CONFIG_OUTPUT
      )
      if(NOT PCAP_CONFIG_RESULT EQUAL 0)
        message(FATAL_ERROR "pcap-config --libs --static failed")
      endif()
      separate_arguments(LIBS_LIST UNIX_COMMAND ${PCAP_CONFIG_OUTPUT})
      set(CONFIG_PCAP_STATIC_LIBRARY_DIRS "")
      set(CONFIG_PCAP_STATIC_LIBRARIES "")
      foreach(_arg IN LISTS LIBS_LIST)
        if(_arg MATCHES "^-L")
          #
          # Extract the directory by removing the -L.
          #
          string(REGEX REPLACE "-L" "" _dir ${_arg})
          #
          # Work around macOS (and probably other Darwin) brokenness,
          # by not adding /usr/local/lib if it's from the broken
          # Apple pcap-config.
          #
          if(NOT _broken_apple_pcap_config OR
             NOT "${_dir}" STREQUAL /usr/local/lib)
            # Add this directory to CONFIG_PCAP_STATIC_LIBRARY_DIRS
            list(APPEND CONFIG_PCAP_STATIC_LIBRARY_DIRS ${_dir})
          endif()
        elseif(_arg MATCHES "^-l")
          string(REGEX REPLACE "-l" "" _lib ${_arg})
          #
          # Try to find that library, so we get its full path, as
          # we do with dynamic libraries.
          #
          list(APPEND CONFIG_PCAP_STATIC_LIBRARIES ${_lib})
        endif()
      endforeach()

      #
      # We've set CONFIG_PCAP_INCLUDE_DIRS, CONFIG_PCAP_LIBRARIES, and
      # CONFIG_PCAP_STATIC_LIBRARIES above; set CONFIG_PCAP_FOUND.
      #
      set(CONFIG_PCAP_FOUND YES)
    endif()
  endif()

  #
  # If CONFIG_PCAP_FOUND is set, we have information from pkg-config and
  # pcap-config; we need to convert library names to library full paths.
  #
  # If it's not set, we have to look for the libpcap headers and library
  # ourselves.
  #
  if(CONFIG_PCAP_FOUND)
    #
    # Use CONFIG_PCAP_INCLUDE_DIRS as the value for PCAP_INCLUDE_DIRS.
    #
    set(PCAP_INCLUDE_DIRS "${CONFIG_PCAP_INCLUDE_DIRS}")

    #
    # CMake *really* doesn't like the notion of specifying
    # "here are the directories in which to look for libraries"
    # except in find_library() calls; it *really* prefers using
    # full paths to library files, rather than library names.
    #
    foreach(_lib IN LISTS CONFIG_PCAP_LIBRARIES)
      find_library(_libfullpath ${_lib} HINTS ${CONFIG_PCAP_LIBRARY_DIRS})
      list(APPEND PCAP_LIBRARIES ${_libfullpath})
      #
      # Remove that from the cache; we're using it as a local variable,
      # but find_library insists on making it a cache variable.
      #
      unset(_libfullpath CACHE)
   endforeach()

    #
    # Now do the same for the static libraries.
    #
    set(SAVED_CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_FIND_LIBRARY_SUFFIXES}")
    set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
    foreach(_lib IN LISTS CONFIG_PCAP_STATIC_LIBRARIES)
      find_library(_libfullpath ${_lib} HINTS ${CONFIG_PCAP_LIBRARY_DIRS})
      list(APPEND PCAP_STATIC_LIBRARIES ${_libfullpath})
      #
      # Remove that from the cache; we're using it as a local variable,
      # but find_library insists on making it a cache variable.
      #
      unset(_libfullpath CACHE)
    endforeach()
    set(CMAKE_FIND_LIBRARY_SUFFIXES "${SAVED_CMAKE_FIND_LIBRARY_SUFFIXES}")

    #
    # We found libpcap using pkg-config or pcap-config.
    #
    set(PCAP_FOUND YES)
  else(CONFIG_PCAP_FOUND)
    #
    # We didn't have pkg-config, or we did but it didn't have .pc files
    # for libpcap, and we don't have pkg-config, so we have to look for
    # the headers and libraries ourself.
    #
    # We don't directly set PCAP_INCLUDE_DIRS or PCAP_LIBRARIES, as
    # they're not supposed to be cache entries, and find_path() and
    # find_library() set cache entries.
    #
    # Try to find the header file.
    #
    find_path(PCAP_INCLUDE_DIR pcap.h)

    #
    # Try to find the library
    #
    find_library(PCAP_LIBRARY pcap)

    # Try to find the static library (XXX - what about AIX?)
    set(SAVED_CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_FIND_LIBRARY_SUFFIXES}")
    set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
    find_library(PCAP_STATIC_LIBRARY pcap)
    set(CMAKE_FIND_LIBRARY_SUFFIXES "${SAVED_CMAKE_FIND_LIBRARY_SUFFIXES}")

    #
    # This will fail if REQUIRED is set and PCAP_INCLUDE_DIR or
    # PCAP_LIBRARY aren't set.
    #
    include(FindPackageHandleStandardArgs)
    find_package_handle_standard_args(PCAP
      DEFAULT_MSG
      PCAP_INCLUDE_DIR
      PCAP_LIBRARY
    )

    mark_as_advanced(
      PCAP_INCLUDE_DIR
      PCAP_LIBRARY
      PCAP_STATIC_LIBRARY
    )

    if(PCAP_FOUND)
      set(PCAP_INCLUDE_DIRS ${PCAP_INCLUDE_DIR})
      set(PCAP_LIBRARIES ${PCAP_LIBRARY})
      set(PCAP_STATIC_LIBRARIES ${PCAP_STATIC_LIBRARY})
    endif(PCAP_FOUND)
  endif(CONFIG_PCAP_FOUND)
endif(WIN32)