#Macro accumulates missing dependencies. They are stored in the following variables:
#_MISDEP_GLOBAL_COUNTER - each invocation of missing_dependency increments it. It is used to create unique variable names that emulate nested list elements
#_MISDEP_APT_PACKAGES_xx - list of required apt packages by item xx (one item is added by each invcation of missing_dependency)
#_MISDEP_SPACK_PACKAGES_xx - list of required Spack modules
#_MISDEP_PYTHON_PACKAGES_xx - list of required python packages
#_MISDEP_DESCRIPTION_xx - description of the requirement
#_MISDEP_REQUIRED_BY_xx - Name of the build component that requires that dependency
function (missing_dependency)
	set(options FINALIZE PREFER_SPACK)
	set(oneValueArgs DESCRIPTION REQUIRED_BY)
	set(multiValueArgs APT_PACKAGES PYTHON_PACKAGES SPACK_PACKAGES)
	cmake_parse_arguments(MISDEP "${options}" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}")
	
	set(VERBOSE_BY_DEFAULT 1) #If TRUE, every time one adds a requirement, it will elso echo it, in case code execution never reaches the FINALIZE stage
	set(SAVE_DEPENDENCIES 1) #If TRUE, every time one adds a requirement, it will elso echo it, in case code execution never reaches the FINALIZE stage
#	get_property(_MISDEP_GLOBAL_COUNTER GLOBAL PROPERTY _MISDEP_GLOBAL_COUNTER)
	
	get_dist_id(OUTVAR DIST_ID)
	if(NOT "${DIST_ID}" STREQUAL "Ubuntu" AND NOT "${DIST_ID}" STREQUAL "LinuxMint")
		set(MISDEP_PREFER_SPACK 1)
	endif()
	
	if("${MISDEP_DESCRIPTION}" STREQUAL "")
	   set(MISDEP_DESCRIPTION "${FO_NICE_NAME}")
	endif()
	if("${MISDEP_APT_PACKAGES}" STREQUAL "")
	   set(MISDEP_APT_PACKAGES "${FO_APT_PACKAGES}")
	endif()
	if("${MISDEP_PYTHON_PACKAGES}" STREQUAL "")
	   set(MISDEP_PYTHON_PACKAGES "${FO_PYTHON_PACKAGES}")
	endif()
	if("${MISDEP_SPACK_PACKAGES}" STREQUAL "")
	   set(MISDEP_SPACK_PACKAGES "${FO_SPACK_PACKAGES}")
	endif()
	if("${MISDEP_REQUIRED_BY}" STREQUAL "")
	   if(NOT "${__NICE_NAME_OF_CURRENTLY_PROCESSED_TARGET}" STREQUAL "")
   	   set(MISDEP_REQUIRED_BY "${__NICE_NAME_OF_CURRENTLY_PROCESSED_TARGET}")
   	elseif(NOT "${__INSTANCE_NAME}" STREQUAL "")
   	   _retrieve_instance_data(${__INSTANCE_NAME} I_PARENTS __I_PARENTS)
   	   _get_nice_instance_names(${__I_PARENTS} MISDEP_REQUIRED_BY)
   	endif()
#   	message(WARNING "MISDEP_REQUIRED_BY: ${MISDEP_REQUIRED_BY}")
	endif()
#	message(STATUS "MISDEP_APT_PACKAGES: ${MISDEP_APT_PACKAGES}")
#	message(STATUS "FO_APT_PACKAGES: ${FO_APT_PACKAGES}")
#	message(STATUS "MISDEP_DESCRIPTION: ${MISDEP_DESCRIPTION}")
#	message(STATUS "FO_NICE_NAME: ${FO_NICE_NAME}")

	
	#Adding the arguments to the nested list structure maintained by us
	if(MISDEP_DESCRIPTION AND MISDEP_REQUIRED_BY AND (MISDEP_APT_PACKAGES OR MISDEP_PYTHON_PACKAGES OR MISDEP_SPACK_PACKAGES))
		#Increment the global counter, so new elements will be put in their own dedicated place in the list
		if(NOT _MISDEP_GLOBAL_COUNTER)
			set(_MISDEP_GLOBAL_COUNTER 1 CACHE INTERNAL _MISDEP_GLOBAL_COUNTER FORCE)
		else()
			math(EXPR _MISDEP_GLOBAL_COUNTER "${_MISDEP_GLOBAL_COUNTER} + 1")
			set(_MISDEP_GLOBAL_COUNTER ${_MISDEP_GLOBAL_COUNTER} CACHE INTERNAL _MISDEP_GLOBAL_COUNTER FORCE)
		endif()
		
		
		if(VERBOSE_BY_DEFAULT)
			set(MSG "${MISDEP_DESCRIPTION} is required by ${MISDEP_REQUIRED_BY}. ")
		endif()
		if(MISDEP_SPACK_PACKAGES)
			set(_MISDEP_SPACK_PACKAGES_${_MISDEP_GLOBAL_COUNTER} ${MISDEP_SPACK_PACKAGES} CACHE INTERNAL _MISDEP_SPACK_PACKAGES_${_MISDEP_GLOBAL_COUNTER} FORCE)
			if(VERBOSE_BY_DEFAULT AND NOT MISDEP_PREFER_SPACK)
				set(MSG "If you want to use Spack you can fix it by\n            spack load ${MISDEP_SPACK_PACKAGES}\n")
			endif()
		endif()
		if(MISDEP_APT_PACKAGES)
			set(_MISDEP_APT_PACKAGES_${_MISDEP_GLOBAL_COUNTER} ${MISDEP_APT_PACKAGES} CACHE INTERNAL _MISDEP_APT_PACKAGES_${_MISDEP_GLOBAL_COUNTER} FORCE)
			if(VERBOSE_BY_DEFAULT AND MISDEP_PREFER_SPACK)
				if("${DIST_ID}" STREQUAL "Ubuntu")
					set(MSG "On Ubuntu you can fix it by\n            sudo apt install ${MISDEP_APT_PACKAGES}\n")
				endif()
			endif()
		endif()
		if(MISDEP_PYTHON_PACKAGES)
			set(_MISDEP_PYTHON_PACKAGES_${_MISDEP_GLOBAL_COUNTER} ${MISDEP_PYTHON_PACKAGES} CACHE INTERNAL _MISDEP_PYTHON_PACKAGES_${_MISDEP_GLOBAL_COUNTER} FORCE)
			if(VERBOSE_BY_DEFAULT)
				if("${DIST_ID}" STREQUAL "Ubuntu")
					set(MSG "On Ubuntu you can fix it by\n            sudo -H pip install ${MISDEP_PYTHON_PACKAGES}\n")
				else()
					set(MSG "You can fix it by\n            pip install ${MISDEP_PYTHON_PACKAGES}\n or \n            pip3 install ${MISDEP_PYTHON_PACKAGES}\n")
				endif()
			endif()
		endif()
		set(_MISDEP_DESCRIPTION_${_MISDEP_GLOBAL_COUNTER} ${MISDEP_DESCRIPTION} CACHE INTERNAL _MISDEP_DESCRIPTION_${_MISDEP_GLOBAL_COUNTER} FORCE)
		set(_MISDEP_REQUIRED_BY_${_MISDEP_GLOBAL_COUNTER} ${MISDEP_REQUIRED_BY} CACHE INTERNAL _MISDEP_REQUIRED_BY_${_MISDEP_GLOBAL_COUNTER} FORCE)
		message(WARNING "${MSG}")
		if(VERBOSE_BY_DEFAULT)
			message(WARNING "${MSG}")
		endif()
	endif()
	
	#Extract the contents of the list to produce a meaningful error message
	get_directory_property(hasParent PARENT_DIRECTORY) #We will finalize only in the most outer CMakeLists.txt
	if(MISDEP_FINALIZE AND _MISDEP_GLOBAL_COUNTER AND NOT hasParent)
		#get a list of all unique apt packages (APT_PKG), and unique python packages (PIP_PKG), and SPACK_PKGules (SPACK_PKG).
		#The list will be flat and we will ignore all the dependencies for now.
		#get a list of all unique descriptions.
		set(PIP_PKG)
		foreach(IDX RANGE 1 ${_MISDEP_GLOBAL_COUNTER})
			if(_MISDEP_PYTHON_PACKAGES_${IDX})
				foreach(PKG ${_MISDEP_PYTHON_PACKAGES_${IDX}})
					list(FIND PIP_PKG ${PKG} TMP_IDX)
					if ("${TMP_IDX}" STREQUAL "-1")
						list(APPEND PIP_PKG ${PKG})
					endif()
				endforeach()
			endif()
		endforeach()
#		message(STATUS "PIP_PKG: ${PIP_PKG}")
		if(PIP_PKG)
			find_package(Pip3)
			find_package(Pip2)
			if(NOT PIP3_FOUND)
				if(NOT PIP2_FOUND)
					missing_dependency(
						DESCRIPTION "Python package installer"
						REQUIRED_BY "Installation process of any Python dependency"
						APT_PACKAGES python3-pip
						)
					set(PIP_VERSION 3)
				else()
					set(PIP_VERSION 2)
				endif()
			else()
				set(PIP_VERSION 3)
			endif()
		endif()
		
		set(APT_PKG)
		set(SPACK_PKG)
		set(DESCRS)
		foreach(IDX RANGE 1 ${_MISDEP_GLOBAL_COUNTER})
			if(_MISDEP_APT_PACKAGES_${IDX})
				foreach(PKG ${_MISDEP_APT_PACKAGES_${IDX}})
					list(FIND APT_PKG ${PKG} TMP_IDX)
					if ("${TMP_IDX}" STREQUAL "-1")
						list(APPEND APT_PKG ${PKG})
					endif()
				endforeach()
#				list(APPEND APT_PKG ${_MISDEP_APT_PACKAGES_${IDX}})
			endif()
			if(_MISDEP_SPACK_PACKAGES_${IDX})
				foreach(PKG ${_MISDEP_SPACK_PACKAGES_${IDX}})
					list(FIND SPACK_PKG ${PKG} TMP_IDX)
					if ("${TMP_IDX}" STREQUAL "-1")
						list(APPEND SPACK_PKG ${PKG})
					endif()
				endforeach()
			endif()
			list(FIND DESCRS ${_MISDEP_DESCRIPTION_${IDX}} TMP_IDX)
			if ("${TMP_IDX}" STREQUAL "-1")
				list(APPEND DESCRS ${_MISDEP_DESCRIPTION_${IDX}})
			endif()
		endforeach()
#		message(STATUS "APT_PKG: ${APT_PKG}")
#		message(STATUS "DESCRS: ${DESCRS}")
		
		
		#Now we have a list of all packages and descriptions. Now it is time to match them together
		#We are going to generate a meaningful list of human-readable dependencies
		#
		#We will iterate DESCR over all unique descriptions. For each description we will iterate over all the 
		#raw description entries that match DESCR. Then we will concatenate all the packages that are added
		#with it, separate for each package source. We will also concatenate all found dependencies.
		#Next we will generate a nice output entry based on those lists.
		set(DESCRS_TXT "")
		foreach(DESCR ${DESCRS})
			set(DESCR_RB "")
			set(DESCR_APT "")
			set(DESCR_PIP "")
			set(DESCR_SPACK "")
#			message(STATUS "DESCR: ${DESCR}")
			foreach(IDX RANGE 1 ${_MISDEP_GLOBAL_COUNTER})
#				message(STATUS "DESCR_RB: ${DESCR_RB}, IDX:${IDX}")
				if("${_MISDEP_DESCRIPTION_${IDX}}" STREQUAL "${DESCR}")
#					message("_MISDEP_DESCRIPTION_${IDX}: ${_MISDEP_DESCRIPTION_${IDX}}")
					if(_MISDEP_APT_PACKAGES_${IDX})
						foreach(ITEM ${_MISDEP_APT_PACKAGES_${IDX}})
							list(FIND DESCR_APT ${ITEM} I)
							if("${I}" STREQUAL "-1")
								list(APPEND DESCR_APT ${ITEM})
							endif()
						endforeach()
					endif()
#					message(STATUS "DESCR_APT: ${DESCR_APT}")
					if(_MISDEP_PYTHON_PACKAGES_${IDX})
						foreach(ITEM ${_MISDEP_PYTHON_PACKAGES_${IDX}})
							list(FIND DESCR_PIP ${ITEM} I)
							if("${I}" STREQUAL "-1")
								list(APPEND DESCR_PIP ${ITEM})
							endif()
						endforeach()
					endif()
#					message(STATUS "_MISDEP_REQUIRED_BY_${IDX}: ${_MISDEP_REQUIRED_BY_${IDX}}")
					if(_MISDEP_SPACK_PACKAGES_${IDX})
						foreach(ITEM ${_MISDEP_SPACK_PACKAGES_${IDX}})
							list(FIND DESCR_SPACK ${ITEM} I)
							if("${I}" STREQUAL "-1")
								list(APPEND DESCR_SPACK ${ITEM})
							endif()
						endforeach()
					endif()
					list(FIND DESCR_RB ${_MISDEP_REQUIRED_BY_${IDX}} I)
					if("${I}" STREQUAL "-1")
						list(APPEND DESCR_RB ${_MISDEP_REQUIRED_BY_${IDX}})
					endif()
#					message(STATUS "DESCR_RB: ${DESCR_RB}")
				endif()
			endforeach()
			list(LENGTH DESCR_RB RB_LEN)
			nice_list_output(OUTVAR DESCR_RB_TXT LIST ${DESCR_RB})
#			message(STATUS "VAR1: ${VAR1}")
#			message(STATUS "DESCR_RB: ${DESCR_RB}")
			
			if(MISDEP_PREFER_SPACK)
				nice_list_output(OUTVAR DESCR_PKG_TXT LIST ${DESCR_SPACK} ${DESCR_PIP})
			else()
				nice_list_output(OUTVAR DESCR_PKG_TXT LIST ${DESCR_APT} ${DESCR_PIP})
			endif()
			
			set(DESCRS_TXT "${DESCRS_TXT}  * ${DESCR} required by ${DESCR_RB_TXT}. \n    Packages: ${DESCR_PKG_TXT}.\n")
		endforeach()
		
		set(APT_TXT)
#		message("APT_PKG: ${APT_PKG}")
		if(APT_PKG)
			set(APT_TXT "        sudo apt install")
			foreach(PKG ${APT_PKG})
				set(APT_TXT "${APT_TXT} ${PKG}")
			endforeach()
			set(APT_TXT "${APT_TXT}\n")
		endif()
		
		set(SPACK_TXT)
#		message("APT_PKG: ${APT_PKG}")
		if(SPACK_PKG)
			set(SPACK_TXT "        . ~/tmp/spack/share/spack/setup_env.sh\n")
			foreach(PKG ${SPACK_PKG})
				set(SPACK_TXT "${SPACK_TXT}        spack load ${PKG}\n")
			endforeach()
#			set(SPACK_TXT "${SPACK_TXT}\n")
		endif()

		set(PIP_TXT)
		if(PIP_PKG)
			if("PIP_VERSION" STREQUAL "2")
				set(PIP_TXT "        sudo -H pip install")
			else()
				set(PIP_TXT "        sudo -H pip3 install")
			endif()
			foreach(PKG ${PIP_PKG})
				set(PIP_TXT "${PIP_TXT} ${PKG}")
			endforeach()
#			set(PIP_TXT "${PIP_TXT}\n")
		endif()
		
		foreach(IDX RANGE 1 ${_MISDEP_GLOBAL_COUNTER})
			unset(_MISDEP_APT_PACKAGES_${IDX} CACHE)
			unset(_MISDEP_SPACK_PACKAGES_${IDX} CACHE)
			unset(_MISDEP_PYTHON_PACKAGES_${IDX} CACHE)
			unset(_MISDEP_DESCRIPTION_${IDX} CACHE)
			unset(_MISDEP_REQUIRED_BY_${IDX} CACHE)
		endforeach()
		unset(_MISDEP_GLOBAL_COUNTER CACHE)
		
		if(SAVE_DEPENDENCIES)
			file(WRITE ${PROJECT_BINARY_DIR}/apt_dependencies.txt "")
			if(APT_PKG)
				foreach(PKG ${APT_PKG})
					file(APPEND ${PROJECT_BINARY_DIR}/apt_dependencies.txt "${PKG} ")
				endforeach()
			endif()

			file(WRITE ${PROJECT_BINARY_DIR}/pip_dependencies.txt "")
			if(PIP_PKG)
				foreach(PKG ${PIP_PKG})
					file(APPEND ${PROJECT_BINARY_DIR}/pip_dependencies.txt "${PKG} ")
				endforeach()
			endif()

			file(WRITE ${PROJECT_BINARY_DIR}/spack_dependencies.txt "")
			if(SPACK_PKG)
				foreach(PKG ${SPACK_PKG})
					file(APPEND ${PROJECT_BINARY_DIR}/spack_dependencies.txt "${PKG} ")
				endforeach()
			endif()
		endif()
		
		if(MISDEP_PREFER_SPACK)
			message(STATUS "\n\n\nSummary of missing system components:\n${DESCRS_TXT}\n\nYou can install everything by executing the following commands:\n\n${SPACK_TXT}${PIP_TXT}        rm CMakeCache.txt\n\n")
		else()
			message(STATUS "\n\n\nSummary of missing system components:\n${DESCRS_TXT}\n\nYou can install everything on Ubuntu by executing the following commands:\n\n${APT_TXT}${PIP_TXT}        rm CMakeCache.txt\n\n")
			
		endif()
	else()
		file(WRITE ${PROJECT_BINARY_DIR}/apt_dependencies.txt "")
		file(WRITE ${PROJECT_BINARY_DIR}/spack_dependencies.txt "")
		file(WRITE ${PROJECT_BINARY_DIR}/pip_dependencies.txt "")
	endif()
endfunction()

function (nice_list_output)
	set(options )
	set(oneValueArgs OUTVAR)
	set(multiValueArgs LIST)
	cmake_parse_arguments(NLO "${options}" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}")
	
	list(LENGTH NLO_LIST TMP_LEN)
	if("${TMP_LEN}" STREQUAL "1")
		set(${NLO_OUTVAR} ${NLO_LIST})
	else()
		set(${NLO_OUTVAR} "")
		foreach(I RANGE 1 ${TMP_LEN})
			math(EXPR TMP "${TMP_LEN}-${I}")
			math(EXPR I0 "${I}-1")
			list(GET NLO_LIST ${I0} LIST_ITEM)
			if(I GREATER 1)
				if("${TMP}" STREQUAL "0")
					set(${NLO_OUTVAR} "${${NLO_OUTVAR}} and")
				else()
					set(${NLO_OUTVAR} "${${NLO_OUTVAR}},")
				endif()
				set(${NLO_OUTVAR} "${${NLO_OUTVAR}} ${LIST_ITEM}")
			else()
				set(${NLO_OUTVAR} "${${NLO_OUTVAR}}${LIST_ITEM}")
			endif()
		endforeach()
	endif()
	set(${NLO_OUTVAR} ${${NLO_OUTVAR}} PARENT_SCOPE)
#	message("NLO_OUTVAR: ${NLO_OUTVAR}: ${${NLO_OUTVAR}}")
endfunction()

function (get_dist_id)
	set(options )
	set(oneValueArgs OUTVAR)
	set(multiValueArgs )
	cmake_parse_arguments(GDI "${options}" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}")
	
	if(NOT GDI_OUTVAR)
		set(GDI_OUTVAR "DIST_ID")
	endif()
	
	execute_process(COMMAND lsb_release -is
					OUTPUT_VARIABLE TMP)
	string(STRIP "${TMP}" TMP)
	set(${GDI_OUTVAR} ${TMP} PARENT_SCOPE)
endfunction()
