function (add_formatter name)
  cmake_parse_arguments(PARSE_ARGV 1 _add_formatter
    "VERSIONED;EXCLUDE_FROM_ALL"
    "TYPE;TOOLS"
    "")

  if (_add_formatter_UNPARSED_ARGUMENTS)
    message(FATAL_ERROR
      "Unparsed arguments: ${_add_formatter_UNPARSED_ARGUMENTS}")
  endif ()

  if (_add_formatter_TYPE STREQUAL "check")
  elseif (_add_formatter_TYPE STREQUAL "reformat")
  else ()
    message(FATAL_ERROR
      "The formatter type of ${name} must be either `check` or `reformat` "
      "(given `${_add_formatter_TYPE}`).")
  endif ()

  set_property(GLOBAL APPEND
    PROPERTY "formatter_${name}_versioned" "${_add_formatter_VERSIONED}")
  set_property(GLOBAL APPEND
    PROPERTY "formatter_${name}_type" "${_add_formatter_TYPE}")

  set(_add_formatter_tools_exist TRUE)
  foreach (_add_formatter_tool IN LISTS _add_formatter_TOOLS)
    find_program("TOOL_${_add_formatter_tool}"
      NAMES "${_add_formatter_tool}"
      DOC   "The path to the ${_add_formatter_tool} formatting tool")
    if (NOT TOOL_${_add_formatter_tool})
      set(_add_formatter_tools_exist FALSE)
    endif ()
  endforeach ()

  if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/format.${name}")
    set(_add_formatter_script "${CMAKE_CURRENT_SOURCE_DIR}/format.${name}")
  else()
    set(_add_formatter_script "${CMAKE_CURRENT_BINARY_DIR}/format.${name}")
  endif()

  set_property(GLOBAL APPEND
    PROPERTY formatters "${name}")
  set_property(GLOBAL APPEND
    PROPERTY "formatter_${name}_tools" "${_add_formatter_tools_exist}")
  set_property(GLOBAL APPEND
    PROPERTY "formatter_${name}_script" "${_add_formatter_script}")
  set_property(GLOBAL
    PROPERTY "formatter_${name}_exclude_from_all" "${_add_formatter_EXCLUDE_FROM_ALL}")

  option("ENABLE_${name}" "Install the ${name} formatter script" "${_add_formatter_tools_exist}")

  if (ENABLE_${name})
    set(version_message "")
    if (_add_formatter_VERSIONED)
      set("TOOL_VERSIONS_${name}" ""
        CACHE STRING "Versions of the tool available")
      set(version_message "${TOOL_VERSIONS_${name}}")
      if (version_message)
        string(REPLACE ";" ", " version_message "${version_message}")
        set(version_message " (versions: ${version_message})")
      endif ()
    endif ()
    message(STATUS "Enabling the ${name} formatter.${version_message}")
    if (NOT _add_formatter_tools_exist)
      message(WARNING
        "The ${name} formatter is enabled, but required tools are not found.")
    endif ()
    install_formatter("${name}")
  else ()
    message(STATUS "Disabling the ${name} formatter.")
  endif ()
endfunction ()

function (install_formatter name)
  get_property(script GLOBAL PROPERTY "formatter_${name}_script")
  install(
    PROGRAMS    "${script}"
    DESTINATION "bin"
    COMPONENT   "formatters")
  if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/${name}.yaml.in")
    set(yaml_in "${CMAKE_CURRENT_BINARY_DIR}/${name}.yaml.in")
  else()
    set(yaml_in "${CMAKE_CURRENT_SOURCE_DIR}/${name}.yaml.in")
  endif()
  configure_file(
    "${yaml_in}"
    "${CMAKE_CURRENT_BINARY_DIR}/${name}.yaml"
    @ONLY)
  install(
    FILES       "${CMAKE_CURRENT_BINARY_DIR}/${name}.yaml"
    DESTINATION "share/formatters/config"
    COMPONENT   "examples")
endfunction ()

add_subdirectory(c)
add_subdirectory(generic)
add_subdirectory(python)
add_subdirectory(rust)

get_property(formatters GLOBAL
  PROPERTY formatters)
if (formatters)
  set(formatters_build)
  set(formatters_install)
  foreach (formatter IN LISTS formatters)
    if (NOT ENABLE_${formatter})
      continue ()
    endif ()

    # Skip explicitly excluded formatters.
    get_property(formatter_exclude_from_all GLOBAL
      PROPERTY "formatter_${formatter}_exclude_from_all")
    if (formatter_exclude_from_all)
      continue ()
    endif ()

    # Do not reformat with check-only tools.
    get_property(formatter_type GLOBAL
      PROPERTY "formatter_${formatter}_type")
    if (NOT formatter_type STREQUAL "reformat")
      continue ()
    endif ()

    get_property("formatter_script" GLOBAL
      PROPERTY "formatter_${formatter}_script")
    string(APPEND formatters_build "format_all '${formatter}' '${formatter_script}'\n")
    string(APPEND formatters_install "format_all '${formatter}' 'format.${formatter}'\n")
  endforeach ()

  set(formatter_calls "${formatters_build}")
  configure_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/format.all.in"
    "${CMAKE_BINARY_DIR}/bin/format.all"
    @ONLY)

  set(formatter_calls "${formatters_install}")
  configure_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/format.all.in"
    "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/format.all"
    @ONLY)
  install(
    PROGRAMS    "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/format.all"
    DESTINATION "bin"
    COMPONENT   "formatters")
endif ()
