#!/usr/bin/env bash

# Copyright (C) 2016 Paul Kocialkowski <contact@paulk.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

PROJECT_ACTIONS_GENERIC=(usage download extract update build install release clean)
PROJECT_ACTIONS_GENERIC_IGNORE_CHECK=(usage clean)
PROJECT_ACTIONS_HELPERS=(arguments)

INSTALL_REGEX='\([^:]*\):\(.*\)'

project_include() {
	local project=$1

	local project_path=$( project_path "$project" )

	unset -f "${PROJECT_ACTIONS[@]}"

	source "$project_path/$project"

	project_helper_include "$project"
}

project_helper_include() {
	local project=$1

	local project_path=$( project_path "$project" )
	local include="$project_path/$project-helper"

	if [ -f "$include" ]
	then
		source "$include"
	fi
}

project_check() {
	local project="${1##*/}"

	local project_path="$(project_path "${project}")"

	if ! [[ -f "${project_path}/${project}" ]]; then
		return 1
	fi
}

project_function_check() {
	local project=$1
	local function=$2

	project_include "$project"

	if ! function_check "$function"
	then
		return 1
	fi

	return 0
}

project_action() {
	local action="$1"
	shift
	local project="$1"
	shift
	local arguments="$*"

	(
		set +e

		project_action_check "${action}" "${project}" "$@"

		printf '%s\n' "Project ${project} ${action} (with ${arguments:-no argument})" >&2

		if "${action}" "$@"; then
			printf '\n%s\n' "Project ${project} ${action} (with ${arguments:-no argument}) completed" >&2
		else
			printf '\n%s\n' "Project ${project} ${action} (with ${arguments:-no argument}) failed" >&2
			return 1
		fi
	)
}

project_action_check() {
	local action="$1"
	shift
	local project="$1"
	shift

	(
		set +e

		if ! function_check "${action}_check"; then
			return 1
		fi

		for project_force in ${PROJECTS_FORCE}; do
			if [[ "${project_force}" == "${project}" ]]; then
				return 1
			fi
		done

		(
			set -e
			"${action}_check" "$@"
		)
	)
}

project_action_helper() {
	local helper="$1"
	shift
	local project="$1"
	shift

	if ! function_check "${helper}"; then
		return 0
	fi

	"${helper}" "$@"
}

project_action_arguments() {
	local action="$1"
	shift
	local project="$1"
	shift

	project_include "${project}"

	project_action_arguments_verify_recursive "${action}" "${project}" "$@"
	project_action_arguments_recursive "${action}" "${project}" "$@"
}

project_action_arguments_verify_recursive() {
	local action="$1"
	shift
	local project="$1"
	shift

	local action_helper_arguments

	# Store final argument.
	local argument="${*:$#}"

	local test

	if [[ "$#" -gt 1 ]]; then
		# Set previous arguments.
		set "${@:1:$#-1}"
	elif [[ "$#" -eq 1 ]]; then
		shift
	else
		return 0
	fi

	action_helper_arguments="$(project_action_helper 'arguments' "${project}" "$@")"

	if [[ -n "${action_helper_arguments}" ]]; then
		test="$(printf '%s\n' "${action_helper_arguments}" | grep -e "^${argument}\$" || true)"

		if [[ -z "${test}" ]]; then
			printf '%s\n' "Invalid argument ${argument} for project ${project} action ${action}" >&2
			return 1
		fi
	fi

	project_action_arguments_verify_recursive "${action}" "${project}" "$@"
}

project_action_arguments_recursive() {
	local action="$1"
	shift
	local project="$1"
	shift

	local action_helper_arguments
	local argument

	action_helper_arguments="$(project_action_helper 'arguments' "${project}" "$@" || true)"

	if [[ -z "${action_helper_arguments}" ]]; then
		project_action "${action}" "${project}" "$@"
	else
		# This is to allow space characters in arguments.
		local ifs_save="${IFS}"
		local IFS=$'\n'

		for argument in $(printf '%s\n' "${action_helper_arguments}")
		do
			IFS="${ifs_save}"

			# Only a single argument at a time is returned by the helper.
			project_action_arguments_recursive "${action}" "${project}" "$@" "${argument}"
		done
	fi
}

project_action_projects() {
	local action="$1"
	shift
	local project="$1"
	shift

	local project_path="$(project_path "${project}")"
	local project_projects_path="${project_path}/${CONFIGS}/${PROJECTS}"
	local project_projects_action_path="${project_path}/${CONFIGS}/${PROJECTS}-${action}"
	local arguments
	local path

	if [[ -f "${project_projects_action_path}" ]]; then
		path="${project_projects_action_path}"
	else
		path="${project_projects_path}"
	fi

	# Multiple arguments can be read from the file.
	while read -r arguments; do
		eval "project_action_arguments ${action} ${arguments}"
	done < "${path}"
}

project_path() {
	local project=$1

	local project_path="$root/$PROJECTS/$project"

	printf '%s\n' "$project_path"
}

project_sources_path() {
	local project=$1
	shift
	local repository=$1
	shift

	local sources_path
	local argument
	local path

	# Check downloaded and extracted sources first, using "$project."
	path="$root/$SOURCES/$project"

	for argument in "" "$@"
	do
		if ! [ -z "$argument" ]
		then
			path="$path-$argument"
		fi

		if ! directory_filled_check "$path"
		then
			continue
		fi

		sources_path=$path
	done

	if ! [ -z "$sources_path" ]
	then
		printf '%s\n' "$sources_path"
		return
	fi

	# Check downloaded sources then, using "$repository."
	path="$root/$SOURCES/$repository"

	if directory_filled_check "$path"
	then
		printf '%s\n' "$path"
		return
	fi

	# Check project sources finally, using "$project."
	path="$root/$PROJECTS/$project/$SOURCES"

	for argument in "" "$@"
	do
		if ! [ -z "$argument" ]
		then
			path="$path/$argument"
		fi

		if ! directory_filled_check "$path"
		then
			continue
		fi

		sources_path=$path
	done

	if ! [ -z "$sources_path" ]
	then
		printf '%s\n' "$sources_path"
		return
	fi
}

project_sources_directory_filled_check() {
	local project=$1
	shift

	local sources_path=$( project_sources_path "$project" "$@" )

	test ! -z "$sources_path"
}

project_sources_directory_filled_error() {
	local project=$1
	shift
	local arguments="$*"

	local sources_path=$( project_sources_path "$project" "$@" )

	if ! [ -z "$sources_path" ]
	then
		printf '%s\n' "Sources directory for project $project (with ${arguments:-no argument}) already exists" >&2
		return 1
	else
		return 0
	fi
}

project_sources_directory_missing_empty_error() {
	local project=$1
	shift
	local arguments="$*"

	local sources_path=$( project_sources_path "$project" "$@" )

	if [ -z "$sources_path" ]
	then
		printf '%s\n' "Sources directory for project $project (with ${arguments:-no argument}) missing or empty" >&2
		return 1
	else
		return 0
	fi
}

project_sources_archive() {
	local project=$1
	shift

	local sources_archive
	local argument
	local path="$root/$SOURCES/$project"

	for argument in "" "$@"
	do
		if ! [ -z "$argument" ]
		then
			path="$path-$argument"
		fi

		local archive="$path.$ARCHIVE"

		if ! [ -f "$archive" ]
		then
			continue
		fi

		sources_archive=$archive
	done

	if ! [ -z "$sources_archive" ]
	then
		printf '%s\n' "$sources_archive"
	fi
}

project_sources_archive_extract() {
	local project=$1
	shift
	local arguments="$*"

	local archive=$( project_sources_archive "$project" "$@" )
	local destination=$( dirname "$archive" )

	printf '%s\n' "Extracting source archive for $project (with ${arguments:-no argument})"

	file_verification_check "$archive"
	archive_extract "$archive" "$destination"
}

project_sources_archive_update() {
	local project=$1
	shift
	local arguments="$*"

	local repository=$project
	local sources_path=$( project_sources_path "$project" "$repository" "$@" )
	local archive=$( project_sources_archive "$project" "$@" )
	local destination=$( dirname "$archive" )

	if [ -d "$sources_path" ]
	then
		rm -rf "$sources_path"
	fi

	printf '%s\n' "Extracting source archive for $project (with ${arguments:-no argument})"

	file_verification_check "$archive"
	archive_extract "$archive" "$destination"
}

project_sources_archive_missing_error() {
	local project=$1
	shift
	local arguments="$*"

	local archive=$( project_sources_archive "$project" "$@" )
	if [ -z "$archive" ] || ! [ -f "$archive" ]
	then
		printf '%s\n' "Missing sources archive for $project (with ${arguments:-no argument})" >&2
		return 1
	else
		return 0
	fi
}

project_sources_archive_missing_check() {
	local project=$1
	shift

	local archive=$( project_sources_archive "$project" "$@" )
	if [ -z "$archive" ] || ! [ -f "$archive" ]
	then
		return 0
	else
		return 1
	fi
}

project_sources_prepare() {
	local project="$1"
	local sources_path="$2"

	# Not implemented yet / May end up not being needed
	#project_sources_prepare_blobs
	project_sources_prepare_patch "${project}" "${sources_path}" "$@"
}

project_sources_prepare_patch() {
	local project="$1"
	local sources_path="$2"

	local project_path="$(project_path "${project}")"
	local patches_path="${project_path}/${PATCHES}"

	for patch in "${patches_path}"/[!.]*.@(patch|diff); do
		diff_patch_file "${sources_path}" "${patch}"
	done
}

project_blobs_path() {
	local project=$1
	shift

	local project_path=$( project_path "$project" )
	local configs_path="$project_path/$CONFIGS"
	local argument
	local path

	for argument in "" "$@"
	do
		if ! [ -z "$argument" ]
		then
			if [ -z "$path" ]
			then
				path="$argument"
			else
				path="$path/$argument"
			fi
		fi

		local blobs_path="$configs_path/$path/$BLOBS"

		if [ -f "$blobs_path" ]
		then
			printf '%s\n' "$blobs_path"
			return
		fi
	done

	printf '%s\n' "$blobs_path"
}

project_blobs_ignore_path() {
	local project=$1
	shift

	local project_path=$( project_path "$project" )
	local configs_path="$project_path/$CONFIGS"
	local argument
	local path

	for argument in "" "$@"
	do
		if ! [ -z "$argument" ]
		then
			if [ -z "$path" ]
			then
				path="$argument"
			else
				path="$path/$argument"
			fi
		fi

		blobs_ignore_path="$configs_path/$path/$BLOBS_IGNORE"

		if [ -f "$blobs_ignore_path" ]
		then
			printf '%s\n' "$blobs_ignore_path"
			return
		fi
	done
}

project_arguments_targets() {
	local project=$1
	shift

	local project_path=$( project_path "$project" )
	local targets_path="$project_path/$CONFIGS"
	local argument

	for argument in "$@"
	do
		targets_path="$targets_path/$argument"
	done

	targets_path="$targets_path/$TARGETS"

	if [ -f "$targets_path" ]
	then
		cat "$targets_path"
	fi
}

project_usage_actions() {
	local project="$1"
	shift

	printf '\n%s\n' 'Generic actions:'

	(
		for action in "${PROJECT_ACTIONS_GENERIC[@]}"; do
			if function_check "${action}"; then
				printf '%s\n' "  ${action}"
			fi
		done
	)

	if [[ "$#" -gt 0 ]]; then
		printf '\n%s\n' 'Specific actions:'

		(
			for action in "$@"; do
				printf '%s\n' "  ${action}"
			done
		)
	fi
}

project_usage_arguments() {
	local project="$1"
	shift

	printf '\n%s\n' 'Arguments:'

	project_usage_arguments_recursive "${project}" '  ' "$@"
}

project_usage_arguments_recursive() {
	local project="$1"
	shift
	local spacing="$1"
	shift

	local action_helper_arguments
	local argument

	action_helper_arguments="$(project_action_helper 'arguments' "${project}" "$@")"

	if [[ -n "${action_helper_arguments}" ]]; then
		for argument in ${action_helper_arguments}; do
			printf '%s\n' "${spacing}${argument}"
			project_usage_arguments_recursive "${project}" "  ${spacing}" "$@" "${argument}"
		done
	fi
}

project_download_git() {
	local project=$1
	shift
	local repository=$1
	shift
	local urls=$1
	shift

	requirements "git"

	if ! git_project_check "$repository"
	then
		project_sources_directory_filled_error "$project" "$repository" "$@"

		git_project_clone "$repository" "$urls"
	fi

	git_project_prepare "$project" "$repository" "$@"
}

project_download_check_git() {
	local project=$1
	shift
	local repository=$1
	shift

	requirements "git"

	git_project_check "$repository"
	git_project_prepare_check "$project" "$repository" "$@"
}

project_download_archive() {
	local project="$1"
	shift
	local archive_uri="$1"
	shift
	local archive_dsig_uri="$1"

	local archive="${archive_uri##*/}"
	local compress_fmt="${archive##*.tar}"

	local directory_prefix="${root}/${SOURCES}"
	local archive_path="${root}/${SOURCES}/${archive}"
	local sources_path="${root}/${SOURCES}/${project}"

	if [[ "${compress_fmt#*.}" != "${ARCHIVE#*.}" ]]; then
		ARCHIVE="tar${compress_fmt}"
	fi

	# TODO: Split this code block into separate functions
	# Archive verification will be included at that point in time
	if ! project_sources_directory_filled_check "${project}"; then
		download_wrapper "${directory_prefix}" "${archive_uri}" "${archive_dsig_uri}"
		archive_extract "${archive_path}" "${directory_prefix}"

		mv "${archive_path%.tar*}" "${sources_path}"
	fi

	# Patch the source, if necessary
	project_sources_prepare "${project}" "${sources_path}"
}

project_download_check_archive() {
	local project="$1"
	local sources_path="$2"

	# TODO: Write the following function
	#project_sources_archive_extract_check "${project}" "${sources_path}"
}

project_extract() {
	local project=$1
	shift

	local repository=$project

	if ! project_sources_directory_filled_check "$project" "$repository" "$@"
	then
		project_sources_archive_missing_error "$project" "$@" || return 1
		project_sources_archive_extract "$project" "$@"
	fi
}

project_extract_check() {
	local project=$1
	shift

	local repository=$project

	project_sources_directory_filled_check "$project" "$repository" "$@"
}

project_update_git() {
	local project=$1
	shift
	local repository=$1
	shift

	requirements "git"

	project_sources_directory_missing_empty_error "$project" "$repository" "$@"

	if git_project_check "$repository"
	then
		git_project_update "$project" "$repository" "$@"
	else
		if ! project_sources_archive_missing_check "$project" "$@"
		then
			project_sources_archive_update "$project" "$@"
		fi
	fi
}

project_update_check_git() {
	local project=$1
	shift
	local repository=$1
	shift

	requirements "git"

	if ! git_project_check "$repository"
	then
		# Git repository should always be updated (even if upstream didn't progress).
		# For instance, this is useful for testing new versions of patches without changing revision.
		return 1
	else
		project_sources_archive_missing_check "$project" "$@"
	fi
}

project_build_check() {
	local project=$1
	shift

	local project_path=$( project_path "$project" )
	local build_path=$( project_build_path "$project" "$@" )
	local source_file_path
	local argument
	local rule
	local path

	for argument in "" "$@"
	do
		if ! [ -z "$argument" ]
		then
			if [ -z "$path" ]
			then
				path="$argument"
			else
				path="$path/$argument"
			fi
		fi

		configs_install_path="$project_path/$CONFIGS/$path/$INSTALL"

		if ! [ -f "$configs_install_path" ]
		then
			continue
		fi

		while read -r rule
		do
			source=$( printf '%s\n' "$rule" | sed "s/$INSTALL_REGEX/\\1/g" )
			source_path="$build_path/$source"

			# Source may contain a wildcard.
			path_wildcard_expand "$source_path" | while read -r source_file_path
			do
				if ! [ -f "$source_file_path" ] && ! [ -d "$source_file_path" ]
				then
					false
				fi
			done
		done < "$configs_install_path"
	done
}

project_build_path() {
	local project=$1
	shift

	local build_path="$root/$BUILD/$project"
	local argument

	for argument in "$@"
	do
		build_path="$build_path-$argument"
	done

	printf '%s\n' "$build_path"
}

project_build_directory_missing_empty_error() {
	local project=$1
	shift
	local arguments="$*"

	local build_path=$( project_build_path "$project" "$@" )

	if ! directory_filled_check "$build_path"
	then
		printf '%s\n' "Build directory for project $project (with ${arguments:-no argument}) missing or empty" >&2
		return 1
	else
		return 0
	fi
}

project_install() {
	local project=$1
	shift

	local project_path=$( project_path "$project" )
	local build_path=$( project_build_path "$project" "$@" )
	local install_path=$( project_install_path "$project" "$@" )
	local source_file_path
	local argument
	local rule
	local path

	# Install built files first.
	for argument in "" "$@"
	do
		if ! [ -z "$argument" ]
		then
			if [ -z "$path" ]
			then
				path="$argument"
			else
				path="$path/$argument"
			fi
		fi

		configs_install_path="$project_path/$CONFIGS/$path/$INSTALL"

		if ! [ -f "$configs_install_path" ]
		then
			continue
		fi

		project_build_directory_missing_empty_error "$project" "$@"

		while read -r rule
		do
			source=$( printf '%s\n' "$rule" | sed "s/$INSTALL_REGEX/\\1/g" )
			source_path="$build_path/$source"

			destination=$( printf '%s\n' "$rule" | sed "s/$INSTALL_REGEX/\\2/g" )
			destination_path="$install_path/$destination"
			destination_directory_path=$( dirname "$destination_path" )

			mkdir -p "$destination_directory_path"

			# Source may contain a wildcard.
			path_wildcard_expand "$source_path" | while read -r source_file_path
			do
				cp -rT "$source_file_path" "$destination_path"
			done
		done < "$configs_install_path"
	done

	path=""

	# Install install files then.
	for argument in "" "$@"
	do
		if ! [ -z "$argument" ]
		then
			if [ -z "$path" ]
			then
				path="$argument"
			else
				path="$path/$argument"
			fi
		fi

		install_install_path="$project_path/$INSTALL/$path/$INSTALL"

		if ! [ -f "$install_install_path" ]
		then
			continue
		fi

		while read -r rule
		do
			source=$( printf '%s\n' "$rule" | sed "s/$INSTALL_REGEX/\\1/g" )
			source_path="$project_path/$INSTALL/$path/$source"

			destination=$( printf '%s\n' "$rule" | sed "s/$INSTALL_REGEX/\\2/g" )
			destination_path="$install_path/$destination"
			destination_directory_path=$( dirname "$destination_path" )

			mkdir -p "$destination_directory_path"

			# Source may contain a wildcard.
			path_wildcard_expand "$source_path" | while read -r source_file_path
			do
				cp -rT "$source_file_path" "$destination_path"
			done
		done < "$install_install_path"
	done
}

project_install_check() {
	local project=$1
	shift

	local project_path=$( project_path "$project" )
	local build_path=$( project_build_path "$project" "$@" )
	local install_path=$( project_install_path "$project" "$@" )
	local argument
	local rule
	local path

	# Install built files first.
	for argument in "" "$@"
	do
		if ! [ -z "$argument" ]
		then
			if [ -z "$path" ]
			then
				path="$argument"
			else
				path="$path/$argument"
			fi
		fi

		configs_install_path="$project_path/$CONFIGS/$path/$INSTALL"

		if ! [ -f "$configs_install_path" ]
		then
			continue
		fi

		project_build_directory_missing_empty_error "$project" "$@"

		while read -r rule
		do
			destination=$( printf '%s\n' "$rule" | sed "s/$INSTALL_REGEX/\\2/g" )
			destination_path="$install_path/$destination"

			if ! [ -f "$destination_path" ] && ! [ -d "$destination_path" ]
			then
				false
			fi
		done < "$configs_install_path"
	done

	path=""

	# Install install files then.
	for argument in "" "$@"
	do
		if ! [ -z "$argument" ]
		then
			if [ -z "$path" ]
			then
				path="$argument"
			else
				path="$path/$argument"
			fi
		fi

		install_install_path="$project_path/$INSTALL/$path/$INSTALL"

		if ! [ -f "$install_install_path" ]
		then
			continue
		fi

		while read -r rule
		do
			destination=$( printf '%s\n' "$rule" | sed "s/$INSTALL_REGEX/\\2/g" )
			destination_path="$install_path/$destination"

			if ! [ -f "$destination_path" ] && ! [ -d "$destination_path" ]
			then
				false
			fi
		done < "$install_install_path"
	done
}

project_install_path() {
	local project=$1
	shift

	local install_path="$root/$INSTALL/$project"
	local argument

	for argument in "$@"
	do
		install_path="$install_path-$argument"
	done

	printf '%s\n' "$install_path"
}

project_install_directory_missing_empty_error() {
	local project=$1
	shift
	local arguments="$*"

	local install_path=$( project_install_path "$project" "$@" )

	if ! directory_filled_check "$install_path"
	then
		printf '%s\n' "Install directory for project $project (with ${arguments:-no argument}) missing or empty" >&2
		return 1
	else
		return 0
	fi
}

project_release_path() {
	local project=$1
	shift
	local prefix=$1

	local release_path="$root/$RELEASE/$prefix"

	# Special care for tools and systems, that depend on the host arch.
	if [ "$prefix" = "$SYSTEMS" ] || [ "$prefix" = "$TOOLS" ]
	then
		local machine=$( uname -m )

		release_path="$release_path/$machine/$project"
	else
		release_path="$release_path/$project"
	fi

	printf '%s\n' "$release_path"
}

project_release_archive_path() {
	local project=$1
	shift
	local prefix=$1
	shift

	local release_path=$( project_release_path "$project" "$prefix" )
	local argument
	local path="$project"

	for argument in "$@"
	do
		path="$path-$argument"
	done

	local archive_path="$release_path/$path.$ARCHIVE"

	printf '%s\n' "$archive_path"
}

project_release_rootfs_path() {
	local project=$1
	shift
	local prefix=$1
	shift

	local release_path=$( project_release_path "$project" "$prefix" )
	local argument
	local path="$project"

	for argument in "$@"
	do
		path="$path-$argument"
	done

	local rootfs_path="$release_path/$path.$ARCHIVE"

	printf '%s\n' "$rootfs_path"
}

project_release_sources_archive_path() {
	local project=$1
	shift

	local sources_path="$root/$SOURCES/"
	local release_path
	local argument
	local path="$project"

	for argument in "" "$@"
	do
		if ! [ -z "$argument" ]
		then
			path="$path-$argument"
		fi

		local directory_path="$sources_path/$path"

		if ! directory_filled_check "$directory_path"
		then
			continue
		fi

		release_path=$path
	done

	if ! [ -z "$release_path" ]
	then
		local archive_path="$root/$RELEASE/$SOURCES/$project/$release_path.$ARCHIVE"

		printf '%s\n' "$archive_path"
	fi
}

project_release_sources_archive_create() {
	local project=$1
	shift
	local arguments="$*"

	local repository=$project
	local archive_path=$( project_release_sources_archive_path "$project" "$@" )
	local sources_path=$( project_sources_path "$project" "$repository" "$@" )

	printf '%s\n' "Releasing sources archive for $project (with ${arguments:-no argument})"

	archive_create "$archive_path" "$sources_path"
	file_verification_create "$archive_path"
}

project_release_sources_archive_exists_check() {
	local project=$1
	shift

	local archive_path=$( project_release_sources_archive_path "$project" "$@" )
	if [ -z "$archive_path" ] || ! [ -f "$archive_path" ]
	then
		return 1
	else
		return 0
	fi
}

project_release_sources_git() {
	local project=$1
	shift
	local repository=$1
	shift

	requirements "git"

	project_sources_directory_missing_empty_error "$project" "$repository" "$@"

	if git_project_check "$repository"
	then
		if ! git_project_release_check "$project" "$repository" "$@"
		then
			git_project_release "$project" "$repository" "$@"
		fi
	else
		if ! project_release_sources_archive_exists_check "$project" "$@"
		then
			project_release_sources_archive_create "$project" "$@"
		fi
	fi
}

project_release_check_sources_git() {
	local project=$1
	shift
	local repository=$1
	shift

	requirements "git"

	if git_project_check "$repository"
	then
		git_project_release_check "$project" "$repository" "$@"
	else
		project_release_sources_archive_exists_check "$project" "$@"
	fi
}

project_release_install() {
	local project=$1
	shift
	local prefix=$1
	shift

	local install_path=$( project_install_path "$project" "$@" )
	local release_path=$( project_release_path "$project" "$prefix" )
	local directory_path
	local path

	project_install_directory_missing_empty_error "$project" "$@"

	local files=$( find "$install_path" -type f || true )
	local file

	printf '%s\n' "$files" | while read -r file
	do
		path="$release_path/$file"
		directory_path=$( dirname "$path" )

		mkdir -p "$directory_path"

		cp "$install_path/$file" "$path"
		file_verification_create "$path"
	done
}

project_release_install_check() {
	local project=$1
	shift
	local prefix=$1
	shift

	local install_path=$( project_install_path "$project" "$@" )
	local release_path=$( project_release_path "$project" "$prefix" )
	local path

	project_install_directory_missing_empty_error "$project" "$@"

	local files=$( find "$install_path" -type f || true )
	local file

	printf '%s\n' "$files" | while read -r file
	do
		path="$release_path/$file"

		file_exists_check "$path"
	done
}

project_release_install_archive() {
	local project=$1
	shift
	local prefix=$1
	shift

	project_install_directory_missing_empty_error "$project" "$@"

	project_release_install_archive_create "$project" "$prefix" "$@"
}

project_release_install_archive_check() {
	local project=$1
	shift

	project_release_install_archive_exists_check "$project" "$@"
}

project_release_install_archive_create() {
	local project=$1
	shift
	local prefix=$1
	shift
	local arguments="$*"

	local install_path=$( project_install_path "$project" "$@" )
	local archive_path=$( project_release_archive_path "$project" "$prefix" "$@" )

	printf '%s\n' "Releasing $prefix archive for $project (with ${arguments:-no argument})"

	archive_create "$archive_path" "$install_path"
	file_verification_create "$archive_path"
}

project_release_install_archive_exists_check() {
	local project=$1
	shift
	local prefix=$1
	shift

	local archive_path=$( project_release_archive_path "$project" "$prefix" "$@" )

	file_exists_check "$archive_path"
}

project_release_install_rootfs() {
	local project=$1
	shift
	local prefix=$1
	shift

	project_install_directory_missing_empty_error "$project" "$@"

	if ! project_release_install_rootfs_exists_check "$project" "$prefix" "$@"
	then
		project_release_install_rootfs_create "$project" "$prefix" "$@"
	fi
}

project_release_install_rootfs_check() {
	local project=$1
	shift

	project_release_install_rootfs_exists_check "$project" "$@"
}

project_release_install_rootfs_create() {
	local project=$1
	shift
	local prefix=$1
	shift
	local arguments="$*"

	local install_path=$( project_install_path "$project" "$@" )
	local rootfs_path=$( project_release_rootfs_path "$project" "$prefix" "$@" )

	printf '%s\n' "Releasing $prefix rootfs for $project (with ${arguments:-no argument})"

	rootfs_create "$rootfs_path" "$install_path"
	file_verification_create "$rootfs_path"
}

project_release_install_rootfs_exists_check() {
	local project=$1
	shift
	local prefix=$1
	shift

	local rootfs_path=$( project_release_rootfs_path "$project" "$prefix" "$@" )

	file_exists_check "$rootfs_path"
}

project_clean() {
	local project=$1
	shift

	project_clean_build "$project" "$@"
	project_clean_install "$project" "$@"
	project_clean_release "$project" "$@"
}

project_clean_build() {
	local project=$1
	shift

	local build_path=$( project_build_path "$project" "$@" )

	rm -rf "$build_path"
}

project_clean_install() {
	local project=$1
	shift

	local install_path=$( project_install_path "$project" "$@" )

	rm -rf "$install_path"
}

project_clean_release() {
	local project=$1
	shift

	local prefix

	for prefix in "$SOURCES" "$SYSTEMS" "$IMAGES" "$TOOLS" "$DOCS"
	do
		local release_path=$( project_release_path "$project" "$prefix" )

		rm -rf "$release_path"
	done
}

project_clean_rootfs() {
	local project=$1
	shift

	project_clean_build "$project" "$@"
	project_clean_rootfs_install "$project" "$@"
	project_clean_release "$project" "$@"

}

project_clean_rootfs_install() {
	local project=$1
	shift

	local install_path=$( project_install_path "$project" "$@" )

	execute_root rm -rf "$install_path"

}

project_file_path() {
	local project=$1
	shift
	local directory=$1
	shift
	local file=$1
	shift

	local project_path=$( project_path "$project" )
	local path="$project_path/$directory"
	local argument
	local file_path

	for argument in "" "$@"
	do
		if ! [ -z "$argument" ]
		then
			path="$path/$argument"
		fi

		if ! [ -f "$path/$file" ]
		then
			continue
		fi

		file_path="$path/$file"
	done

	if [ -z "$file_path" ]
	then
		return 1
	fi

	printf '%s\n' "$file_path"
}

project_file_test() {
	local file_path=$( project_file_path "$@" )

	test -f "$file_path"
}

project_file_contents() {
	local file_path=$( project_file_path "$@" )

	if [ -f "$file_path" ]
	then
		cat "$file_path"
	fi
}

project_file_contents_herit() {
	local project=$1
	shift
	local directory=$1
	shift
	local file=$1
	shift

	local project_path=$( project_path "$project" )
	local path="$project_path/$directory"
	local argument
	local file_path

	for argument in "" "$@"
	do
		if ! [ -z "$argument" ]
		then
			path="$path/$argument"
		fi

		file_path="$path/$file"

		if ! [ -f "$file_path" ]
		then
			continue
		fi

		cat "$file_path"
	done
}