# Originally based on this example:
# https://github.com/IvanSafonov/grpc-cmake-example

# Locate and configure the gRPC library
# Adds the following targets:
#  gRPC::grpc - gRPC library
#  gRPC::grpc++ - gRPC C++ library
#  gRPC::grpc++_reflection - gRPC C++ reflection library
#  gRPC::grpc_cpp_plugin - C++ generator plugin for Protocol Buffers

# Generates C++ sources from the .proto files
# args:
#  SRCS - variable to define with autogenerated source files
#  HDRS - variable to define with autogenerated header files
#  WORKSPACE_ROOT - sources will be created in subfolders relative to this directory
#                   defaults to the current source directory
#  INSTALL_DIR - directory where the source files will be created
#                defaults to the current binary directory
#  IMPORT_DIRS - additional directories that will be searched for imported proto files
#  PROTOS - .proto files
function(GRPC_GENERATE_CPP)
  set(_options)
  set(_one_value_args SRCS HDRS WORKSPACE_ROOT INSTALL_DIR)
  set(_multi_value_args IMPORT_DIRS PROTOS)
  cmake_parse_arguments(_GGC "${options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})

  if(NOT _GGC_SRCS)
    message(SEND_ERROR "Error: GRPC_GENERATE_CPP() called without SRCS")
    return()
  endif()

  if(NOT _GGC_HDRS)
    message(SEND_ERROR "Error: GRPC_GENERATE_CPP() called without HDRS")
    return()
  endif()

  if(NOT _GGC_WORKSPACE_ROOT)
    set(_GGC_WORKSPACE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}")
  endif()

  if(NOT _GGC_INSTALL_DIR)
    set(_GGC_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}")
  endif()

  if(NOT _GGC_PROTOS)
    message(SEND_ERROR "Error: GRPC_GENERATE_CPP() called without PROTOS")
    return()
  endif()

  list(APPEND _protobuf_include_args -I ${_GGC_WORKSPACE_ROOT})

  foreach(IMPORT_DIR ${_GGC_IMPORT_DIRS})
    get_filename_component(ABS_IMPORT_DIR ${IMPORT_DIR} ABSOLUTE)
    list(FIND _protobuf_include_args ${ABS_IMPORT_DIR} _contains_already)
    if(${_contains_already} EQUAL -1)
      list(APPEND _protobuf_include_args -I ${ABS_IMPORT_DIR})
    endif()
  endforeach()

  file(MAKE_DIRECTORY "${_GGC_INSTALL_DIR}")

  set(${_GGC_SRCS})
  set(${_GGC_HDRS})
  foreach(FIL ${_GGC_PROTOS})
    file(RELATIVE_PATH REL_FIL ${_GGC_WORKSPACE_ROOT} ${FIL})
    get_filename_component(REL_PATH ${REL_FIL} DIRECTORY)
    get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
    get_filename_component(FIL_WE ${FIL} NAME_WE)

    set(CPP_OUT_DIR "${_GGC_INSTALL_DIR}/${REL_PATH}")
    set(OUT_BASE_FIL "${CPP_OUT_DIR}/${FIL_WE}")
    set(GRPC_SRC_FIL "${OUT_BASE_FIL}.grpc.pb.cc")
    set(GRPC_HDR_FIL "${OUT_BASE_FIL}.grpc.pb.h")
    set(SRC_FIL "${OUT_BASE_FIL}.pb.cc")
    set(HDR_FIL "${OUT_BASE_FIL}.pb.h")

    list(APPEND ${_GGC_SRCS} ${SRC_FIL} ${GRPC_SRC_FIL})
    list(APPEND ${_GGC_HDRS} ${HDR_FIL} ${GRPC_HDR_FIL})

    add_custom_command(
      OUTPUT ${SRC_FIL} ${HDR_FIL}
      COMMAND protobuf::protoc
      ARGS
        --cpp_out "${_GGC_INSTALL_DIR}"
        --grpc_out "${_GGC_INSTALL_DIR}"
        --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN}
        ${_protobuf_include_args}
        ${ABS_FIL}
      DEPENDS ${ABS_FIL} protobuf::protoc gRPC::grpc_cpp_plugin
      COMMENT "Running gRPC compiler on ${FIL}"
      VERBATIM )
  endforeach()

  set_source_files_properties(${${_GGC_SRCS}} ${${_GGC_HDRS}} PROPERTIES GENERATED TRUE)
  set(${_GGC_SRCS} ${${_GGC_SRCS}} PARENT_SCOPE)
  set(${_GGC_HDRS} ${${_GGC_HDRS}} PARENT_SCOPE)
endfunction()

# Generates python sources from the .proto files
# args:
#  SRCS - variable to define with autogenerated source files (pb and grpc)
#  WORKSPACE_ROOT - sources will be created in subfolders relative to this directory
#                   defaults to the current source directory
#  INSTALL_DIR - directory where the source files will be created
#                defaults to the current binary directory
#  IMPORT_DIRS - additional directories that will be searched for imported proto files
#  PROTOS - .proto files
function(GRPC_GENERATE_PYTHON)
  set(_options)
  set(_one_value_args SRCS WORKSPACE_ROOT INSTALL_DIR)
  set(_multi_value_args IMPORT_DIRS PROTOS)
  cmake_parse_arguments(_GGP "${options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})

  if(NOT _GGP_SRCS)
    message(SEND_ERROR "Error: GRPC_GENERATE_PYTHON() called without SRCS")
    return()
  endif()

  if(NOT _GGP_WORKSPACE_ROOT)
    set(_GGP_WORKSPACE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}")
  endif()

  if(NOT _GGP_INSTALL_DIR)
    set(_GGP_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}")
  endif()

  if(NOT _GGP_PROTOS)
    message(SEND_ERROR "Error: GRPC_GENERATE_PYTHON() called without PROTOS")
    return()
  endif()

  list(APPEND _protobuf_include_args -I ${_GGP_WORKSPACE_ROOT})

  foreach(IMPORT_DIR ${_GGP_IMPORT_DIRS})
    get_filename_component(ABS_IMPORT_DIR ${IMPORT_DIR} ABSOLUTE)
    list(FIND _protobuf_include_args ${ABS_IMPORT_DIR} _contains_already)
    if(${_contains_already} EQUAL -1)
      list(APPEND _protobuf_include_args -I ${ABS_IMPORT_DIR})
    endif()
  endforeach()

  file(MAKE_DIRECTORY "${_GGP_INSTALL_DIR}")

  set(${_GGP_SRCS})
  foreach(FIL ${_GGP_PROTOS})
    file(RELATIVE_PATH REL_FIL ${_GGP_WORKSPACE_ROOT} ${FIL})
    get_filename_component(REL_PATH ${REL_FIL} DIRECTORY)
    get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
    get_filename_component(FIL_WE ${FIL} NAME_WE)

    set(PY_OUT_DIR "${_GGP_INSTALL_DIR}/${REL_PATH}")
    set(OUT_BASE_FIL "${PY_OUT_DIR}/${FIL_WE}")
    set(GRPC_FIL "${OUT_BASE_FIL}_pb2_grpc.py")
    set(PB_FIL "${OUT_BASE_FIL}_pb2.py")
    set(INIT_FIL "${PY_OUT_DIR}/__init__.py")

    list(APPEND ${_GGP_SRCS} ${GRPC_FIL} ${PB_FIL} ${INIT_FIL})

    add_custom_command(
      OUTPUT ${GRPC_FIL} ${PB_FIL}
      COMMAND protobuf::protoc
      ARGS
        ${_protobuf_include_args}
        --plugin=protoc-gen-grpc=${GRPC_PYTHON_PLUGIN}
        --python_out "${_GGP_INSTALL_DIR}"
        --grpc_out "${_GGP_INSTALL_DIR}"
        ${ABS_FIL}
      DEPENDS ${ABS_FIL} protobuf::protoc gRPC::grpc_python_plugin
      COMMENT "Running gRPC python compiler on ${FIL}"
      VERBATIM )
    add_custom_command(
      OUTPUT ${INIT_FIL}
      COMMAND touch
      ARGS ${INIT_FIL}
      COMMENT "touching ${INIT_FIL}"
      DEPENDS ${PB_FIL} ${GRPC_FIL}
      VERBATIM )
  endforeach()

  set_source_files_properties(${${_GGP_SRCS}} PROPERTIES GENERATED TRUE)
  set(${_GGP_SRCS} ${${_GGP_SRCS}} PARENT_SCOPE)
endfunction()

# Find gRPC include directory
find_path(GRPC_INCLUDE_DIR grpc/grpc.h)
mark_as_advanced(GRPC_INCLUDE_DIR)

# Find gRPC library
if(NOT TARGET gRPC::grpc)
  find_library(GRPC_LIBRARY NAMES grpc)
  mark_as_advanced(GRPC_LIBRARY)
  add_library(gRPC::grpc UNKNOWN IMPORTED)
  set_target_properties(gRPC::grpc PROPERTIES
    INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
    INTERFACE_LINK_LIBRARIES "-lpthread;-ldl"
    IMPORTED_LOCATION ${GRPC_LIBRARY})
endif()

# Find gRPC C++ library
if(NOT TARGET gRPC::grpc++)
  find_library(GRPC_GRPC++_LIBRARY NAMES grpc++)
  mark_as_advanced(GRPC_GRPC++_LIBRARY)
  add_library(gRPC::grpc++ UNKNOWN IMPORTED)
  set_target_properties(gRPC::grpc++ PROPERTIES
    INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
    INTERFACE_LINK_LIBRARIES gRPC::grpc
    IMPORTED_LOCATION ${GRPC_GRPC++_LIBRARY})
endif()

# Find gRPC C++ reflection library
if(NOT TARGET gRPC::grpc++_reflection)
  find_library(GRPC_GRPC++_REFLECTION_LIBRARY NAMES grpc++_reflection)
  mark_as_advanced(GRPC_GRPC++_REFLECTION_LIBRARY)
  add_library(gRPC::grpc++_reflection UNKNOWN IMPORTED)
  set_target_properties(gRPC::grpc++_reflection PROPERTIES
    INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
    INTERFACE_LINK_LIBRARIES gRPC::grpc++
    IMPORTED_LOCATION ${GRPC_GRPC++_REFLECTION_LIBRARY})
endif()

# Find gRPC CPP generator
if(NOT TARGET gRPC::grpc_cpp_plugin)
  find_program(GRPC_CPP_PLUGIN NAMES grpc_cpp_plugin)
  mark_as_advanced(GRPC_CPP_PLUGIN)
  add_executable(gRPC::grpc_cpp_plugin IMPORTED)
  set_target_properties(gRPC::grpc_cpp_plugin PROPERTIES
    IMPORTED_LOCATION ${GRPC_CPP_PLUGIN})
endif()

# Find gRPC python generator
if(NOT TARGET gRPC::grpc_python_plugin)
  find_program(GRPC_PYTHON_PLUGIN NAMES grpc_python_plugin)
  mark_as_advanced(GRPC_PYTHON_PLUGIN)
  add_executable(gRPC::grpc_python_plugin IMPORTED)
  set_target_properties(gRPC::grpc_python_plugin PROPERTIES
    IMPORTED_LOCATION ${GRPC_PYTHON_PLUGIN})
endif()

include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(gRPC DEFAULT_MSG
  GRPC_LIBRARY GRPC_INCLUDE_DIR GRPC_GRPC++_REFLECTION_LIBRARY GRPC_CPP_PLUGIN GRPC_PYTHON_PLUGIN)
