
#
# This module will add a target named 'packageupload'.
#
# It has been designed to be included in the build system of a Slicer
# extension.
#
# The new target that will
#  (1) configure the project and build the standard 'package' target,
#  (2) extract metadata from the locally generated .s4ext file
#  (3) extract the list of generated packages from its standard output,
#  (4) append the list of generated package filepaths to a file named PACKAGES.txt,
#  (5) upload the first package.
#
# The following variables are expected to be defined in the including scope:
#  CMAKE_SOURCE_DIR
#  EXTENSION_SUPERBUILD_BINARY_DIR
#  EXTENSION_BUILD_SUBDIRECTORY
#  Slicer_CMAKE_DIR
#  Slicer_REVISION
#  EXTENSION_NAME
#  EXTENSION_OPERATING_SYSTEM
#  EXTENSION_ARCHITECTURE
#
# The following variables are internally set by extracting corresponding values
# from the catalog entry file "<extension_name>.json" copied into the build directory
# by the extension CMake build-system:
#  EXTENSION_EXT_CATEGORY
#  EXTENSION_EXT_ENABLED
#  EXTENSION_EXT_TIER
#  EXTENSION_EXT_DICOM_SUPPORT_RULE
#
# The following variables are internally set by extracting corresponding values
# from the locally generated "<extension_name>.s4ext" file:
#  EXTENSION_EXT_CONTRIBUTORS
#  EXTENSION_EXT_DEPENDS
#  EXTENSION_EXT_RECOMMENDS
#  EXTENSION_EXT_DESCRIPTION
#  EXTENSION_EXT_HOMEPAGE
#  EXTENSION_EXT_ICONURL
#  EXTENSION_EXT_SCREENSHOTURLS
#  EXTENSION_EXT_KEYWORDS
#
# The following variables can either be defined in the including scope or
# as environment variables:
#
#  CTEST_MODEL (default: Experimental)
#  SLICER_EXTENSION_MANAGER_CLIENT_EXECUTABLE
#  SLICER_EXTENSION_MANAGER_URL
#  SLICER_EXTENSION_MANAGER_API_KEY
#
# Then, using  the 'SlicerMacroExtractRepositoryInfo' CMake module, the script
# will also set the following variables:
#  EXTENSION_WC_TYPE
#  EXTENSION_WC_URL
#  EXTENSION_WC_REVISION
#
# Finally, each time the 'packageupload' target is built, this same module will be
# executed with all variables previously defined as arguments.
#

if(NOT DEFINED Slicer_EXTENSIONS_CMAKE_DIR)
  set(Slicer_EXTENSIONS_CMAKE_DIR ${CMAKE_CURRENT_LIST_DIR})
endif()
include(${Slicer_EXTENSIONS_CMAKE_DIR}/SlicerFunctionGenerateExtensionDescription.cmake)

# Macro allowing to set a variable to its default value if not already defined.
# The default value is set with:
#  (1) if set, the value environment variable <var>.
#  (2) if set, the value of local variable variable <var>.
#  (3) if none of the above, the value passed as a parameter.
# Setting the optional parameter 'OBFUSCATE' will display 'OBFUSCATED' instead of the real value.
macro(_seput_set_if_not_defined var defaultvalue)
  set(_obfuscate FALSE)
  foreach(arg ${ARGN})
    if(arg STREQUAL "OBFUSCATE")
      set(_obfuscate TRUE)
    endif()
  endforeach()
  if(DEFINED ENV{${var}} AND NOT DEFINED ${var})
    set(_value "$ENV{${var}}")
    if(_obfuscate)
      set(_value "OBFUSCATED")
    endif()
    message(STATUS "Setting '${var}' variable with environment variable value '${_value}'")
    set(${var} $ENV{${var}})
  endif()
  if(NOT DEFINED ${var})
    set(_value "${defaultvalue}")
    if(_obfuscate)
      set(_value "OBFUSCATED")
    endif()
    message(STATUS "Setting '${var}' variable with default value '${_value}'")
    set(${var} "${defaultvalue}")
  endif()
endmacro()

if(NOT DEFINED PACKAGEUPLOAD)
  set(PACKAGEUPLOAD 0)
endif()

if(NOT PACKAGEUPLOAD)

  _seput_set_if_not_defined(CTEST_MODEL "Experimental")

  _seput_set_if_not_defined(SLICER_EXTENSION_MANAGER_CLIENT_EXECUTABLE "SLICER_EXTENSION_MANAGER_CLIENT_EXECUTABLE-NOTDEFINED")
  _seput_set_if_not_defined(SLICER_EXTENSION_MANAGER_URL "SLICER_EXTENSION_MANAGER_URL-NOTDEFINED")
  _seput_set_if_not_defined(SLICER_EXTENSION_MANAGER_API_KEY "SLICER_EXTENSION_MANAGER_API_KEY-NOTDEFINED" OBFUSCATE)

  set(script_vars
    Slicer_CMAKE_DIR
    Slicer_WC_ROOT
    SLICER_EXTENSION_MANAGER_CLIENT_EXECUTABLE
    SLICER_EXTENSION_MANAGER_URL
    SLICER_EXTENSION_MANAGER_API_KEY
    CTEST_MODEL
    Slicer_REVISION
    EXTENSION_NAME
    EXTENSION_OPERATING_SYSTEM
    EXTENSION_ARCHITECTURE
    )

  # Sanity checks
  set(expected_defined_vars
    CMAKE_SOURCE_DIR
    EXTENSION_SUPERBUILD_BINARY_DIR
    EXTENSION_BUILD_SUBDIRECTORY
    ${script_vars}
    )
  foreach(var ${expected_defined_vars})
    if(NOT DEFINED ${var})
      message(FATAL_ERROR "Variable ${var} is not defined !")
    endif()
  endforeach()


  # Get working copy information
  include(SlicerMacroExtractRepositoryInfo)
  SlicerMacroExtractRepositoryInfo(VAR_PREFIX EXTENSION SOURCE_DIR ${CMAKE_SOURCE_DIR})

  if(NOT "${${EXTENSION_NAME}_FORCED_WC_REVISION}" STREQUAL "")
    set(EXTENSION_WC_REVISION "${${EXTENSION_NAME}_FORCED_WC_REVISION}")
  endif()

  set(EXTENSION_BINARY_DIR ${EXTENSION_SUPERBUILD_BINARY_DIR}/${EXTENSION_BUILD_SUBDIRECTORY})

  set(script_arg_list)
  foreach(varname
    ${script_vars}
    # Variables set by SlicerMacroExtractRepositoryInfo
    EXTENSION_WC_TYPE
    EXTENSION_WC_URL
    EXTENSION_WC_ROOT
    EXTENSION_WC_REVISION
    # Variables set in this script
    EXTENSION_BINARY_DIR
    )
    if(NOT DEFINED ${varname})
      message(FATAL_ERROR "Variable ${varname} is expected to be defined.")
    endif()
    set(script_arg_list "${script_arg_list}
set(${varname} \"${${varname}}\")")
  endforeach()

  set(script_args_file ${CMAKE_CURRENT_BINARY_DIR}/packageupload-command-args.cmake)
  file(WRITE ${script_args_file} "${script_arg_list}")

  set(_cpack_output_file ${EXTENSION_BINARY_DIR}/packageupload_cpack_output.txt)

  add_custom_target(packageupload
    COMMAND ${CMAKE_COMMAND} -E echo "CPack log: ${_cpack_output_file}"
    COMMAND ${CMAKE_COMMAND}
      -DPACKAGEUPLOAD:BOOL=1
      -DCONFIG:STRING=$<CONFIG>
      -DCPACK_OUTPUT_FILE:FILEPATH=${_cpack_output_file}
      -DSCRIPT_ARGS_FILE:FILEPATH=${script_args_file}
      -P ${CMAKE_CURRENT_LIST_FILE}
    COMMENT "Package and upload extension"
    )
  return()
endif()

#-----------------------------------------------------------------------------
# Package and upload
#-----------------------------------------------------------------------------

# Build package target and extract list of generated packages

if(NOT EXISTS "${SCRIPT_ARGS_FILE}")
  message(FATAL_ERROR "Argument 'SCRIPT_ARGS_FILE' is either missing or pointing to an nonexistent file !")
endif()
include(${SCRIPT_ARGS_FILE})

# Sanity checks
set(expected_defined_vars
  CONFIG
  CPACK_OUTPUT_FILE
  )
foreach(var ${expected_defined_vars})
  if(NOT DEFINED ${var})
    message(FATAL_ERROR "Variable ${var} is not defined !")
  endif()
endforeach()

set(CMAKE_MODULE_PATH
  ${Slicer_CMAKE_DIR}
  ${Slicer_CMAKE_DIR}/../Extensions/CMake
  ${CMAKE_MODULE_PATH}
  )

#-----------------------------------------------------------------------------
# Extract extension metadata from ".s4ext" file generated in the build
# directory based on the variables set in the extension CMakeLists.txt
include(SlicerFunctionExtractExtensionDescription)
slicerFunctionExtractExtensionDescription(
  EXTENSION_FILE "${EXTENSION_BINARY_DIR}/${EXTENSION_NAME}.s4ext"
  VAR_PREFIX EXTENSION)

# Extract metadata from the catalog extension entry file (".json") usually
# copied into the build directory by the extension build-system.
slicerFunctionExtractExtensionDescriptionFromJson(
  EXTENSION_FILE "${EXTENSION_BINARY_DIR}/${EXTENSION_NAME}.json"
  VAR_PREFIX EXTENSION)

# Sanity checks
set(expected_defined_vars
  # From ".s4ext" file
  EXTENSION_EXT_DEPENDS
  EXTENSION_EXT_RECOMMENDS
  EXTENSION_EXT_DESCRIPTION
  EXTENSION_EXT_ICONURL
  EXTENSION_EXT_HOMEPAGE
  EXTENSION_EXT_SCREENSHOTURLS
  EXTENSION_EXT_CONTRIBUTORS
  EXTENSION_EXT_KEYWORDS
  # From ".json" file
  EXTENSION_EXT_CATEGORY
  EXTENSION_EXT_TIER
  EXTENSION_EXT_DICOM_SUPPORT_RULE
  )
foreach(var ${expected_defined_vars})
  if(NOT DEFINED ${var})
    message(FATAL_ERROR "Variable ${var} is not defined !")
  endif()
endforeach()

# Rewrite the ".s4ext" with metadata extracted from ".json" and
# ensure the expected metadata are associated with the archive.
slicerFunctionGenerateExtensionDescription(
  EXTENSION_NAME ${EXTENSION_NAME}
  EXTENSION_CATEGORY ${EXTENSION_EXT_CATEGORY}
  EXTENSION_ICONURL ${EXTENSION_EXT_ICONURL}
  EXTENSION_STATUS ${EXTENSION_STATUS}
  EXTENSION_HOMEPAGE ${EXTENSION_EXT_HOMEPAGE}
  EXTENSION_CONTRIBUTORS ${EXTENSION_EXT_CONTRIBUTORS}
  EXTENSION_DESCRIPTION ${EXTENSION_EXT_DESCRIPTION}
  EXTENSION_SCREENSHOTURLS ${EXTENSION_EXT_SCREENSHOTURLS}
  EXTENSION_DEPENDS ${EXTENSION_EXT_DEPENDS}
  EXTENSION_ENABLED ${EXTENSION_EXT_ENABLED}
  EXTENSION_BUILD_SUBDIRECTORY ${EXTENSION_EXT_BUILD_SUBDIRECTORY}
  EXTENSION_TIER ${EXTENSION_EXT_TIER}
  EXTENSION_DICOM_SUPPORT_RULE ${EXTENSION_EXT_DICOM_SUPPORT_RULE}
  EXTENSION_KEYWORDS ${EXTENSION_EXT_KEYWORDS}
  EXTENSION_RECOMMENDS ${EXTENSION_EXT_RECOMMENDS}
  EXTENSION_WC_TYPE ${EXTENSION_WC_TYPE}
  EXTENSION_WC_REVISION ${EXTENSION_WC_REVISION}
  EXTENSION_WC_ROOT ${EXTENSION_WC_ROOT}
  EXTENSION_WC_URL ${EXTENSION_WC_URL}
  DESTINATION_DIR ${EXTENSION_BINARY_DIR}
  SLICER_REVISION ${Slicer_REVISION}
  SLICER_WC_ROOT ${Slicer_WC_ROOT}
  )

#-----------------------------------------------------------------------------
# The following code will build the 'package' target, extract the list
# of generated packages from its standard output and create a file PACKAGES.txt
# containing the list of package paths.

# Setting the environment variable SLICER_EXTENSION_MANAGER_SKIP_PACKAGING_TARGET to
# any non empty value can be used when testing this module. It avoids to wait for a rebuild
# of the project.
set(_build_target 1)
if(NOT "$ENV{SLICER_EXTENSION_MANAGER_SKIP_PACKAGING_TARGET}" STREQUAL "")
  set(_build_target 0)
endif()

if(_build_target)
  execute_process(
    COMMAND ${CMAKE_COMMAND} --build ${EXTENSION_BINARY_DIR} --target package --config ${CONFIG}
    WORKING_DIRECTORY ${EXTENSION_BINARY_DIR}
    OUTPUT_STRIP_TRAILING_WHITESPACE
    OUTPUT_FILE ${CPACK_OUTPUT_FILE}
    RESULT_VARIABLE rv
    )
else()
  set(rv 0)
endif()

# Display CPack output
file(READ ${CPACK_OUTPUT_FILE} cpack_output)
message(${cpack_output})

if(NOT rv EQUAL 0)
  message(FATAL_ERROR "Failed to package project: ${EXTENSION_BINARY_DIR}")
endif()

# File containing the list of filepath corresponding to the generated packages
# or installers
set(package_list_file ${EXTENSION_BINARY_DIR}/PACKAGES.txt)

# Clear file
file(WRITE ${package_list_file} "")

# Extract list of generated packages
set(regexp ".*CPack: - package: (.*) generated\\.")
set(raw_package_list)
file(STRINGS ${CPACK_OUTPUT_FILE} raw_package_list REGEX ${regexp})

foreach(package ${raw_package_list})
  string(REGEX REPLACE ${regexp} "\\1" package_path "${package}" )
  file(APPEND ${package_list_file} "${package_path}\n")
endforeach()


#-----------------------------------------------------------------------------
# The following code will read the list of created packages from PACKAGES.txt
# file and upload the first one.

# Current assumption:
# - Exactly one extension package is expected.
# - Extension catalog entry file ("<extensionname>.json") is expected to be
#   copied into the extension build directory (or inner build directory for
#   SuperBuild extension).
# - Extension metadata not already specified in the catalog entry file are read
#   from "<extensionname>.s4ext" file generated in the extension build directory.
# If this ever changes. The following code would have to be updated.

file(STRINGS ${EXTENSION_BINARY_DIR}/PACKAGES.txt package_list)

list(LENGTH package_list package_count)
if(package_count EQUAL 0)
  message(FATAL_ERROR "Extension package failed to be generated.")
endif()

set(package_uploaded 0)
foreach(p ${package_list})
  if(package_uploaded)
    message(WARNING "Skipping additional package: ${p}")
  else()
    set(package_uploaded 1)
    get_filename_component(package_name "${p}" NAME)

    # Convert to space separated list
    list(JOIN EXTENSION_EXT_DEPENDS " " dependency)
    list(JOIN EXTENSION_EXT_RECOMMENDS " " recommends)

    message("Uploading [${package_name}] to [${SLICER_EXTENSION_MANAGER_URL}]")
    get_filename_component(package_directory ${p} DIRECTORY)
    set(error_file ${package_directory}/slicer_extension_server_upload_errors.txt)
    execute_process(COMMAND
      ${CMAKE_COMMAND} -E env
        LC_ALL=en_US.UTF-8
        LANG=en_US.UTF-8
      ${SLICER_EXTENSION_MANAGER_CLIENT_EXECUTABLE}
        --api-url ${SLICER_EXTENSION_MANAGER_URL}/api/v1
        --api-key ${SLICER_EXTENSION_MANAGER_API_KEY}
          extension upload Slicer ${p}
            --os "${EXTENSION_OPERATING_SYSTEM}"
            --arch "${EXTENSION_ARCHITECTURE}"
            --name "${EXTENSION_NAME}"
            --repo_type "${EXTENSION_WC_TYPE}"
            --repo_url "${EXTENSION_WC_URL}"
            --revision "${EXTENSION_WC_REVISION}"
            --app_revision "${Slicer_REVISION}"
            --category "${EXTENSION_EXT_CATEGORY}"
            --desc "${EXTENSION_EXT_DESCRIPTION}"
            --dependency "${dependency}"
            --recommends "${recommends}"
            --icon_url "${EXTENSION_EXT_ICONURL}"
            --homepage "${EXTENSION_EXT_HOMEPAGE}"
            --screenshots "${EXTENSION_EXT_SCREENSHOTURLS}"
            --contributors "${EXTENSION_EXT_CONTRIBUTORS}"
            --tier "${EXTENSION_EXT_TIER}"
            --dicom_support_rule "${EXTENSION_EXT_DICOM_SUPPORT_RULE}"
            --keywords "${EXTENSION_EXT_KEYWORDS}"
      RESULT_VARIABLE slicer_extension_manager_upload_status
      ERROR_FILE ${error_file}
      )
    message(STATUS "slicer_extension_manager_upload_status: ${slicer_extension_manager_upload_status}")
    if(NOT slicer_extension_manager_upload_status EQUAL 0)
      message(FATAL_ERROR "Failed to upload extension using ${SLICER_EXTENSION_MANAGER_CLIENT_EXECUTABLE}.
See ${error_file} for more details.")
    endif()

  endif()
endforeach()
