include(CMakeParseArguments)
include(SwiftTranslateFlag)

# Populate the variable 'args' in the parent scope with a keyword
# argument list.  We read the variables options, ${k}_keyword, and
# ACCT_${k} from the parent scope, for each ${k} in the list of
# keyword names other than COMMAND accepted by
# add_custom_command_target.
#
#   ${k}_keyword must expand to ${k} if ${k} was passed to
#   add_custom_command_target, and be empty otherwise.
#
#   ACCT_${k} must expand to the list of arguments to
#   add_custom_command_target marked by ${k}, and be empty otherwise.
#
function(_make_acct_argument_list)
  set(args)
  foreach(k ${ARGN})
    list(FIND options ${k} option_index)
    if(${option_index} EQUAL -1)
      list(APPEND args ${${k}_keyword} ${ACCT_${k}})
    else()
      list(APPEND args ${${k}_keyword})
    endif()
  endforeach()
  set(args ${args} PARENT_SCOPE)
endfunction()

# Add a custom command/target pair.  Use this instead of
# add_custom_command because it provides proper dependency tracking
# when used with parallel builds and the 'Unix Makefiles' generator.
# See https://www.cmake.org/Bug/view.php?id=10082
#
# The CMake documentation for add_custom_command quoth,
#
#    "Do not list the output in more than one independent target that
#     may build in parallel or the two instances of the rule may
#     conflict (instead use add_custom_target to drive the command and
#     make the other targets depend on that one)."
#
# This function implements the suggested pattern.
#
# add_custom_command_target(
#   dependency_out_var_name
#
#   COMMAND command1 [ARGS] [args1...]
#   [COMMAND command2 [ARGS] [args2...] ...]
#
#   OUTPUT output1 [output2 ...]
#   [MAIN_DEPENDENCY depend]
#   [DEPENDS [depends...]]
#   [IMPLICIT_DEPENDS <lang1> depend1
#   [<lang2> depend2] ...]
#   [WORKING_DIRECTORY dir]
#   [COMMENT comment] [VERBATIM] [APPEND]
#   [ALL]
#   [SOURCES src1 [src2...]])
#
# dependency_out_var_name is the name of a variable, to be set in the
# parent scope with the name of a target that all targets using the
# OUTPUT should depend on.  For example:
#
#   add_custom_command_target(
#       TheDependency
#       COMMAND echo "int main() {}" ">"  z.c
#       OUTPUT z.c
#       VERBATIM
#       DEPENDS z.c.gyb)
#
#   add_executable(exe1 z.c)
#   add_dependencies(exe1 ${TheDependency})
#   add_executable(exe2 z.c)
#   add_dependencies(exe2 ${TheDependency})
#
# **Note1**: all COMMAND arguments must immediately follow
# dependency_out_var_name or this function will misbehave.
#
# **Note2**: any subdirectories that define targets dependent on
# OUTPUT ${o} should invoke:
#
#   set_source_files_properties(${o} PROPERTIES GENERATED true)
#
# All arguments other than ALL, SOURCES, and dependency_out_var_name
# are forwarded to add_custom_command; arguments ALL, SOURCES, and
# WORKING_DIRECTORY are forwarded to add_custom_target.  See the
# documentation of those functions for a description of all arguments.
#
# How This Function Works
#
# CMake offers one way to add new build rules: add_custom_command.
# Most people, however, overlook its actual semantics.
# add_custom_command does *not* create a target.  The CMake
# documentation declareth,
#
#   "A target created in the same directory (CMakeLists.txt file) that
#    specifies any output of the custom command as a source file is
#    given a rule to generate the file using the command at build
#    time."
#
# Therefore, when two targets built in parallel depend on an output of
# the same custom command, they may race to rebuild that output.
# Hilarity ensues.  You might not notice this effect depending on the
# generator you use, but it happens with 'Unix Makefiles'.
#
# By injecting a target into the dependency graph between the custom
# command output and any targets that depend on that output, we force
# the output to be built before starting on any of its dependent
# targets.
function(add_custom_command_target dependency_out_var_name)
  # Parse the arguments.  We don't look for COMMAND arguments because
  # they don't follow the pattern supported by cmake_parse_arguments.
  # As a result, they end up in ACCT_UNPARSED_ARGUMENTS and are
  # forwarded verbatim.
  set(options ALL VERBATIM APPEND IDEMPOTENT)
  set(single_value_args
      MAIN_DEPENDENCY WORKING_DIRECTORY COMMENT CUSTOM_TARGET_NAME)
  set(multi_value_args OUTPUT DEPENDS IMPLICIT_DEPENDS SOURCES)
  cmake_parse_arguments(
      ACCT # prefix
      "${options}" "${single_value_args}" "${multi_value_args}" ${ARGN})
  set(ACCT_COMMANDS ${ACCT_UNPARSED_ARGUMENTS})

  if("${ACCT_CUSTOM_TARGET_NAME}" STREQUAL "")
    # Construct a unique name for the custom target.
    # Use a hash so that the file name does not push the OS limits for filename
    # length.
    list(GET ACCT_OUTPUT 0 output_filename)
    string(MD5 target_md5
        "add_custom_command_target${CMAKE_CURRENT_BINARY_DIR}/${output_filename}")
    get_filename_component(output_filename_basename "${output_filename}" NAME)
    set(target_name
        "add_custom_command_target-${target_md5}-${output_filename_basename}")
  else()
    set(target_name "${ACCT_CUSTOM_TARGET_NAME}")
  endif()

  if((NOT ACCT_IDEMPOTENT) OR (ACCT_IDEMPOTENT AND NOT TARGET "${target_name}"))
    # For each keyword argument k that was passed to this function, set
    # ${k}_keyword to ${k}.  That will allow us to use the incantation
    # '${${k}_keyword} ${ACCT_${k}}' to forward the arguments on.
    foreach(var ${options} ${single_value_args} ${multi_value_args})
      translate_flag(ACCT_${var} ${var} ${var}_keyword)
    endforeach()

    _make_acct_argument_list(
      OUTPUT MAIN_DEPENDENCY DEPENDS
      IMPLICIT_DEPENDS WORKING_DIRECTORY COMMENT VERBATIM APPEND)
    add_custom_command(${ACCT_COMMANDS} ${args})

    _make_acct_argument_list(ALL WORKING_DIRECTORY SOURCES)
    add_custom_target(
        "${target_name}" ${args}
        DEPENDS ${ACCT_OUTPUT}
        COMMENT "${ACCT_OUTPUT}")
    set_target_properties(
        "${target_name}" PROPERTIES
        FOLDER "add_custom_command_target artifacts")
  endif()

  # "Return" the name of the custom target
  set("${dependency_out_var_name}" "${target_name}" PARENT_SCOPE)
endfunction()
