#
# Required input defines:
#   - FILENAME
#   - URL
#   - EXPECTED_SHA256
#   - OUT_DIR
#
#######################################################################
#
# Note:
#
# This contains several functions adapted from CMake modules licensed
# under the CMake license (https://cmake.org/licensing)
# (See below.)
#
########################################################################
#
# CMake - Cross Platform Makefile Generator
# Copyright 2000-2019 Kitware, Inc. and Contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in the
#   documentation and/or other materials provided with the distribution.
#
# * Neither the name of Kitware, Inc. nor the names of Contributors
#   may be used to endorse or promote products derived from this
#   software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#######################################################################
#

set(REQUIRED_INPUT_DEFINES FILENAME URL EXPECTED_SHA256 OUT_DIR)
foreach(_input_define ${REQUIRED_INPUT_DEFINES})
	if(NOT DEFINED ${_input_define} OR "${${_input_define}}" STREQUAL "")
		message( FATAL_ERROR "Missing required input define: ${_input_define}" )
	endif()
endforeach()

#######################################################################

set(_dl_directory "macosx/prebuilt")
set(_extract_directory "macosx/external")

get_filename_component(_full_extract_directory "${_extract_directory}/${OUT_DIR}" ABSOLUTE)

#######################################################################

if(EXISTS "${_full_extract_directory}/.SHA256SumLoc")
	file(READ "${_full_extract_directory}/.SHA256SumLoc" _strings_existing_sha256 ENCODING UTF-8)
	if(_strings_existing_sha256 STREQUAL EXPECTED_SHA256)
		# Already have expected download - skip
		message(STATUS "Already downloaded: ${_full_extract_directory}; skipping")
		return()
	else()
		# Not the expected version / content - remove the existing extracted directory
		file(REMOVE_RECURSE "${_full_extract_directory}")
	endif()
	unset(_strings_existing_sha256)
endif()

set(_download_file TRUE)
if(EXISTS "${_dl_directory}/${FILENAME}")
	message(STATUS "Verifying existing download: ${FILENAME}")
	file(SHA256 "${_dl_directory}/${FILENAME}" _dl_hash)
	if(_dl_hash STREQUAL EXPECTED_SHA256)
		# Skip download (re-use existing)
		message(STATUS "Re-using existing download: ${FILENAME}")
		set(_download_file FALSE)
	else()
		# Remove existing (outdated) download
		message(STATUS "Removing outdated (or corrupt) existing download: ${FILENAME}")
		file(REMOVE "${_dl_directory}/${FILENAME}")
	endif()
endif()

#######################################################################
# Download file (if needed)

if(_download_file)
	
	message(STATUS "Downloading: ${URL}")

	# FileDownloadRetry adapted from: https://github.com/Kitware/CMake/blob/master/Modules/ExternalProject-download.cmake.in
	function(FileDownloadRetry _url _destination _expectedSHA256Hash)
		set(retry_number 3)
		set(_dl_attempts 0)
		set(_dl_success FALSE)
		get_filename_component(_dl_filename "${_destination}" NAME)
		while(NOT _dl_success AND _dl_attempts LESS ${retry_number})
			if(EXISTS "${_destination}")
				file(REMOVE "${_destination}")
			endif()
			file(
				DOWNLOAD "${_url}" "${_destination}"
				INACTIVITY_TIMEOUT 60
				SHOW_PROGRESS
				TLS_VERIFY ON
				STATUS _dl_status
				LOG _dl_log
			)
			list(GET _dl_status 0 _dl_status_code)
			list(GET _dl_status 1 _dl_status_string)
			if (_dl_status_code EQUAL 0)
				# Check hash
				message(STATUS "Verifying download: ${_dl_filename}")
				file(SHA256 "${_destination}" _dl_hash)
				if(NOT _dl_hash STREQUAL _expectedSHA256Hash)
					message(STATUS "SHA256 does not match for \"${_destination}\": (received: ${_dl_hash}) (expecting: ${_expectedSHA256Hash}); removing...")
				else()
					# Successful download
					set(_dl_success TRUE)
				endif()
			else()
				MATH(EXPR _num_attempt "${_dl_attempts}+1")
				string(APPEND _logDLFailures "[Attempt ${_num_attempt}] has failed\n")
				string(APPEND _logDLFailures "status_code: ${_dl_status_code}\n")
				string(APPEND _logDLFailures "status_string: ${_dl_status_string}\n")
				string(APPEND _logDLFailures "--- LOG BEGIN ---\n")
				string(APPEND _logDLFailures "${_dl_log}\n")
				string(APPEND _logDLFailures "--- LOG END ---\n\n")
				message(STATUS "Error (${_dl_status_code}); Attempt (${_num_attempt})...")
			endif()
			MATH(EXPR _dl_attempts "${_dl_attempts}+1")
		endwhile()
	
		if(_dl_success)
			message(STATUS "Download successful")
		else()
			message(FATAL_ERROR "Failed to download: ${_url}\n${_logDLFailures}")
		endif()
	endfunction()
	
	FileDownloadRetry("${URL}" "${_dl_directory}/${FILENAME}" "${EXPECTED_SHA256}")
	
endif()

#######################################################################
# Extracting

# ExtractFile adapted from: https://github.com/Kitware/CMake/blob/master/Modules/ExternalProject.cmake
function(ExtractFile filename directory)

	set(_tar_args "")
	
	if(filename MATCHES "(\\.|=)(7z|tar\\.bz2|tar\\.gz|tar\\.xz|tbz2|tgz|txz|zip)$")
		set(_tar_args xfz)
	endif()

	if(filename MATCHES "(\\.|=)tar$")
		set(_tar_args xf)
	endif()

	if(_tar_args STREQUAL "")
		message(FATAL_ERROR "error: do not know how to extract '${filename}' -- known types are .7z, .tar, .tar.bz2, .tar.gz, .tar.xz, .tbz2, .tgz, .txz and .zip")
		return()
	endif()

	# Make file names absolute:
	#
	get_filename_component(filename "${filename}" ABSOLUTE)
	get_filename_component(directory "${directory}" ABSOLUTE)

	message(STATUS "extracting...
	     src='${filename}'
	     dst='${directory}'")

	if(NOT EXISTS "${filename}")
	  message(FATAL_ERROR "error: file to extract does not exist: '${filename}'")
	endif()

	# Prepare a space for extracting:
	#
	get_filename_component(tmpdir_name "${filename}" NAME_WE)
	set(i 1234)
	while(EXISTS "${directory}/../ex-${tmpdir_name}${i}")
	  math(EXPR i "${i} + 1")
	endwhile()
	set(ut_dir "${directory}/../ex-${tmpdir_name}${i}")
	file(MAKE_DIRECTORY "${ut_dir}")

	# Extract it:
	#
	message(STATUS "extracting... [tar ${_tar_args}]")
	execute_process(
		COMMAND ${CMAKE_COMMAND} -E tar ${_tar_args} ${filename}
		WORKING_DIRECTORY "${ut_dir}"
		RESULT_VARIABLE rv
	)

	if(NOT rv EQUAL 0)
		message(STATUS "extracting... [error clean up]")
		file(REMOVE_RECURSE "${ut_dir}")
		message(FATAL_ERROR "error: extract of '${filename}' failed")
	endif()

	# Analyze what came out of the tar file:
	#
	message(STATUS "extracting... [analysis]")
	file(GLOB contents "${ut_dir}/*")
	list(REMOVE_ITEM contents "${ut_dir}/.DS_Store")
	list(LENGTH contents n)
	if(NOT n EQUAL 1 OR NOT IS_DIRECTORY "${contents}")
	  set(contents "${ut_dir}")
	endif()

	# Move "the one" directory to the final directory:
	#
	message(STATUS "extracting... [rename]")
	file(REMOVE_RECURSE "${directory}")
	get_filename_component(contents "${contents}" ABSOLUTE)
	file(RENAME "${contents}" "${directory}")

	# Clean up:
	#
	message(STATUS "extracting... [clean up]")
	file(REMOVE_RECURSE "${ut_dir}")
	message(STATUS "extracting... done")

endfunction()

ExtractFile("${_dl_directory}/${FILENAME}" "${_full_extract_directory}")

# Save the SHA256
file(WRITE "${_full_extract_directory}/.SHA256SumLoc" "${EXPECTED_SHA256}")

#######################################################################

message(STATUS "Downloaded and extracted to: ${_full_extract_directory}")
