include(CMakeParseArguments)

function(halide_generator_genfiles_dir NAME OUTVAR)
  set(GENFILES_DIR "${CMAKE_BINARY_DIR}/generator_genfiles/${NAME}")
  file(MAKE_DIRECTORY "${GENFILES_DIR}")
  set(${OUTVAR} "${GENFILES_DIR}" PARENT_SCOPE)
endfunction()

# This function adds custom build steps to invoke a Halide generator exectuable
# and produce a static library containing the generated code.
#
# The generator executable must be produced separately, e.g. using a call to the
# function halide_add_generator() or halide_project(...) or add_executable(...) 
# and passed to this function in the GENERATOR_TARGET parameter.
#
# Usage:
#   halide_add_aot_library(<name>
#                          GENERATOR_TARGET <target>
#                          GENERATOR_NAME <string>
#                          GENERATED_FUNCTION <string>
#                          GENERATOR_OUTPUTS <arg> <arg> ...
#                          GENERATOR_ARGS <arg> <arg> ...)
#
#   <name> is the name of the library being defined.
#   GENERATOR_TARGET is the name of the generator executable target, which is assumed to be
#       defined elsewhere.
#   GENERATOR_TARGET is the name of the generator executable target, which is assumed to be
#       defined elsewhere.
#   GENERATOR_NAME is the registered name of the Halide::Generator derived object
#   GENERATED_FUNCTION is the name of the C function to be generated by Halide, including C++ 
#       namespace (if any); if omitted, default to GENERATOR_NAME
#   GENERATOR_OUTPUTS are the values to pass to -e; if omitted, defaults to "h static_library"
#   GENERATOR_ARGS are optional extra arguments passed to the generator executable during
#     build.
function(halide_add_aot_library AOT_LIBRARY_TARGET)

  # Parse arguments
  set(options )
  set(oneValueArgs GENERATOR_TARGET GENERATOR_NAME GENERATED_FUNCTION)
  set(multiValueArgs GENERATOR_ARGS GENERATOR_OUTPUTS)
  cmake_parse_arguments(args "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

  if (args_GENERATED_FUNCTION STREQUAL "")
    set(args_GENERATED_FUNCTION ${args_GENERATOR_NAME})
  endif()

  # Create a directory to contain generator specific intermediate files
  halide_generator_genfiles_dir(${AOT_LIBRARY_TARGET} GENFILES_DIR)

  # Determine the name of the output files
  set(FILTER_LIB "${AOT_LIBRARY_TARGET}${CMAKE_STATIC_LIBRARY_SUFFIX}")
  set(FILTER_HDR "${AOT_LIBRARY_TARGET}.h")

  set(generator_exec_args "-o" "${GENFILES_DIR}")
  if (NOT ${args_GENERATED_FUNCTION} STREQUAL "")
    list(APPEND generator_exec_args "-f" "${args_GENERATED_FUNCTION}" )
  endif()
  if (NOT ${args_GENERATOR_NAME} STREQUAL "")
    list(APPEND generator_exec_args "-g" "${args_GENERATOR_NAME}")
  endif()
  if (NOT ${args_GENERATOR_OUTPUTS} STREQUAL "")
    list(APPEND generator_exec_args "-e" ${args_GENERATOR_OUTPUTS})
  endif()
  # GENERATOR_ARGS always come last
  list(APPEND generator_exec_args ${args_GENERATOR_ARGS})

  if(MSVC)
    # In MSVC, the generator executable will be placed in a configuration specific
    # directory specified by ${CMAKE_CFG_INTDIR}.
    set(generator_exec "${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/${args_GENERATOR_TARGET}${CMAKE_EXECUTABLE_SUFFIX}")
  elseif(XCODE)
    # In Xcode, the generator executable will be placed in a configuration specific
    # directory, so the Xcode variable $(CONFIGURATION) is passed in the custom build script.
    set(generator_exec "${CMAKE_BINARY_DIR}/bin/$(CONFIGURATION)/${args_GENERATOR_TARGET}${CMAKE_EXECUTABLE_SUFFIX}")
  else()
    set(generator_exec "${CMAKE_BINARY_DIR}/bin/${args_GENERATOR_TARGET}${CMAKE_EXECUTABLE_SUFFIX}")
  endif()

  # Add a custom target to invoke the GENERATOR_TARGET and output the Halide
  # generated library.
  add_custom_command(
    OUTPUT "${GENFILES_DIR}/${FILTER_LIB}" "${GENFILES_DIR}/${FILTER_HDR}"
    DEPENDS "${args_GENERATOR_TARGET}"
    COMMAND "${generator_exec}" ${generator_exec_args}
    WORKING_DIRECTORY "${GENFILES_DIR}"
  )

  # Use a custom target to force it to run the generator before the
  # object file for the runner. 
  set(EXEC_GENERATOR_TARGET "${AOT_LIBRARY_TARGET}.exec_generator")
  add_custom_target(${EXEC_GENERATOR_TARGET}
                    DEPENDS "${GENFILES_DIR}/${FILTER_LIB}" "${GENFILES_DIR}/${FILTER_HDR}")

  # Place the target in a special folder in IDEs
  set_target_properties(${EXEC_GENERATOR_TARGET} PROPERTIES FOLDER "generator")

endfunction(halide_add_aot_library)

# Usage:
#   halide_add_aot_library_dependency(TARGET AOT_LIBRARY_TARGET)
function(halide_add_aot_library_dependency TARGET AOT_LIBRARY_TARGET)
    halide_generator_genfiles_dir(${AOT_LIBRARY_TARGET} GENFILES_DIR)
  
    set(FILTER_LIB "${AOT_LIBRARY_TARGET}${CMAKE_STATIC_LIBRARY_SUFFIX}")

    set(EXEC_GENERATOR_TARGET "${AOT_LIBRARY_TARGET}.exec_generator")
    add_dependencies("${TARGET}" "${EXEC_GENERATOR_TARGET}")

    target_link_libraries("${TARGET}" PRIVATE "${GENFILES_DIR}/${FILTER_LIB}")
    target_include_directories("${TARGET}" PRIVATE "${GENFILES_DIR}")

    if (WIN32)
      if (MSVC)
        # /FORCE:multiple allows clobbering the halide runtime symbols in the lib
        set_target_properties("${TARGET}" PROPERTIES LINK_FLAGS "/STACK:8388608,1048576 /FORCE:multiple")
      else()
        set_target_properties("${TARGET}" PROPERTIES LINK_FLAGS "-Wl,--allow-multiple-definition")
      endif()
    else()
      target_link_libraries("${TARGET}" PRIVATE dl pthread z)
    endif()

endfunction(halide_add_aot_library_dependency)

# Legacy wrapper function that calls halide_add_aot_library() + halide_add_aot_library_dependency().
# New code should prefer to call them explicitly.
function(halide_add_generator_dependency)
  # Parse arguments
  set(options )
  set(oneValueArgs TARGET GENERATOR_TARGET GENERATOR_NAME GENERATED_FUNCTION)
  set(multiValueArgs GENERATOR_ARGS)
  cmake_parse_arguments(args "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

  set(AOT_LIBRARY_TARGET "${args_GENERATED_FUNCTION}")
  halide_add_aot_library("${AOT_LIBRARY_TARGET}"
                         GENERATOR_TARGET ${args_GENERATOR_TARGET}
                         GENERATOR_NAME ${args_GENERATOR_NAME}
                         GENERATED_FUNCTION ${args_GENERATED_FUNCTION}
                         GENERATOR_ARGS ${args_GENERATOR_ARGS})
  halide_add_aot_library_dependency(${args_TARGET} "${AOT_LIBRARY_TARGET}")

endfunction(halide_add_generator_dependency)
