#!/bin/sh -e
#
# Ansible Boilerplate Collection: Setup & Maintenance Script.
#

# Detect commands to use:
PYTHON="${PYTHON:-$(command -v python3 2>/dev/null || echo "python3")}"
PIP="${PIP:-$(command -v pip 2>/dev/null || echo "pip")}"
ANSIBLE_GALAXY="${ANSIBLE_GALAXY:-$(command -v ansible-galaxy 2>/dev/null || echo "ansible-galaxy")}"

BASE_D="ansible_galaxy/ansible_collections/alexbarton/boilerplate"

#
# Show usage information on stderr.
#
Usage() {
	{
		echo "$0 <command>"
		echo
		echo "  help       Show this help text and exit."
		echo "  init       Initialize project and boilerplate code."
		echo "  upgrade    Upgrade boilerplate code and dependencies. [Alias: update, up]"
		echo "    --force  Force overwriting an existing role or collection."
		echo
	} >&2
}

#
# Initialize a new project.
#
# Create some default files and call Upgrade() afterwards. This function does not
# overwrite any already existing file.
#
Init() {
	if [ $# -ne 0 ]; then
		Usage
		exit 1
	fi
	if [ -e Makefile.boilerplate ]; then
		echo "This is the upstream project! Don't call \"init\" on it!" >&2
		exit 1
	fi
	echo "Initialize project:"

	for file in \
		README.md \
		LICENSE \
	; do
		test -e "${file}" || touch "${file}"
	done
	mkdir -p .vscode playbooks roles
	test -e "hosts.ini" || Init_HostsIni
	test -e "Makefile" || Init_Makefile
	test -e "requirements.yml" || Init_RequirementsYml

	Upgrade --init
}

#
# Create a Makefile template file.
#
Init_Makefile() {
	echo "Creating \"Makefile\" ..."
	cat >Makefile <<EOF
#
# Makefile
#

SOURCE_ROOT ?= \$(CURDIR)

# Make sure that the Ansible Boilerplate Collection project is set up (its
# files are "available"), and initialize it if not!
_DUMMY := \$(shell test -e ansible_galaxy/ansible_collections/alexbarton/boilerplate/Makefile.boilerplate || bin/ansible-boilerplate upgrade >&2)

default: all

# Include the Ansible Boilerplate Collection Makefile fragment:
include ansible_galaxy/ansible_collections/alexbarton/boilerplate/Makefile.boilerplate

all:

check:

install:

clean:

distclean: clean

maintainer-clean: distclean

.PHONY: default all check install clean distclean maintainer-clean
EOF
}

#
# Create a hosts.ini template file.
#
Init_HostsIni() {
	echo "Creating \"hosts.ini\" ..."
	cat >hosts.ini <<EOF
# Ansible hosts list

[all_managed]
localhost
EOF
}

#
# Create a requirements.yml template file.
#
Init_RequirementsYml() {
	echo "Creating \"requirements.yml\" ..."
	cat >requirements.yml <<EOF
---
# Ansible dependencies

collections:
  - ${BOILERPLATE_COLLECTION_SRC:-alexbarton.boilerplate}

roles:
  []
EOF
}

#
# Upgrade a project.
#
# - Initialize a Python virtual environment (when a ".venv" directory exists
#   or the ansible-galaxy command is not found).
# - Install Ansible when ansible-galaxy command is not found.
# - Install "ansible-boilerplate" collection when not found.
# - Update local "ansible-boilerplate" setup: copy script, create links, ...
# - Upgrade template files.
# - Install/upgrade Python dependencies (from requirements.txt file).
# - Install/upgrade Ansible Galaxy dependencies (from requirements.yml file).
#
# --force: Passed to "ansible-galaxy install" command.
# --init: Upgrade() is called by the Init() function.
#
Upgrade() {
	unset do_force
	while [ $# -gt 0 ]; do
		case "$1" in
			"--force")
				do_force="--force"
				;;
			"--init")
				is_init="--init"
				;;
			*)
				Usage
				exit 1
		esac
		shift
	done
	[ -z "${is_init}" ] && echo "Upgrade project:"

	# Check Python virtual environment
	if [ -d .venv ] || ! command -v "${ANSIBLE_GALAXY}" >/dev/null; then
		# Either an existing ".venv" folder was found or the
		# ansible-galaxy(1) command was not found on the system, so
		# let's use a Python virtual environment!
		echo "Using a Python virtual environment."
		PIP="./.venv/bin/pip"
		ANSIBLE_GALAXY="./.venv/bin/ansible-galaxy"
		if ! [ -x .venv/bin/pip ]; then
			echo "Initializing Python virtual environment ..."
			"${PYTHON}" -m venv .venv
			"${PIP}" install -U pip setuptools
		fi
	fi
	for var in PYTHON PIP ANSIBLE_GALAXY; do
		eval 'echo " - ${var} is \"$'"${var}"'\"."'
	done

	if [ -r requirements.txt ]; then
		echo "Installing Python dependencies ..."
		"${PIP}" install -U -r requirements.txt
	fi

	# Make sure that the "ansible-galaxy" command is available now:
	if ! [ -x "${ANSIBLE_GALAXY}" ]; then
		echo "Oops, \"${ANSIBLE_GALAXY}\" not found!" >&2
		echo "You either need Ansible installed locally or list it as a dependency in" >&2
		echo "the \"requirements.txt\" file of this project!" >&2
		exit 1
	fi

	if [ -r requirements.yml ]; then
		echo "Upgrading Ansible Galaxy dependencies ..."
		# shellcheck disable=SC2248
		"${ANSIBLE_GALAXY}" collection install -U -r requirements.yml ${do_force}
		# shellcheck disable=SC2248
		"${ANSIBLE_GALAXY}" role install -r requirements.yml ${do_force}
	fi

	# Are we running in a dependent project? If so, perform specific upgrade tasks!
	# shellcheck disable=SC2086
	[ -e Makefile.boilerplate ] || Upgrade_Dependent ${is_init}
}

#
# Upgrade steps for dependent projects only.
#
# --init: Upgrade() is called by the Init() function.
#
Upgrade_Dependent() {
	# Verify that the Boilerplate Collection is available now.
	# NOTE: This dependency must be properly listed in the requirements.yml
	# file inside of the (dependent) project!
	"${ANSIBLE_GALAXY}" collection verify --offline alexbarton.boilerplate

	echo "Copying \"boilerplate\" script into bin/ directory ..."
	mkdir -p bin
	cp -av "${BASE_D}/bin/ansible-boilerplate" "bin/ansible-boilerplate"

	echo "Creating symbolic links to files inside of the Boilerplate Collection ..."
	for file in \
		bin/a \
		bin/ap \
		bin/aps \
	; do
		# Create (new) symbolic links, when the target already is a symbolic link or
		# does not yet exists. Don't overwrite existing regular files etc.!
		test -L "${file}" && ln -fsv "../${BASE_D}/${file}" "${file}"
		test -e "${file}" || ln -fsv "../${BASE_D}/${file}" "${file}"
	done

	echo "Upgrading template files from the Boilerplate Collection ..."
	for file in \
		.ansible-lint \
		.editorconfig \
		.gitignore \
		.vscode/settings.json \
		.yamllint.yml \
		ansible.cfg \
		requirements.txt \
	; do
		# shellcheck disable=SC2086
		Upgrade_Template "${file}" ${is_init}
	done
}

#
# Upgrade a template file.
#
# --init: Initialize a new project, therefore create the template file if it
#         does not yet exist.
#
Upgrade_Template() {
	# Does the target directory exist? Skip this template file if not!
	[ -d "$(dirname "$1")" ] || return 0

	# Return when the target file does not exist and not in "init mode":
	[ ! -e "$1" ] && [ "$2" != "--init" ] && return 0

	# Remove the target when it is a symbolic link.
	[ -L "$1" ] && rm -v "$1"

	# Do not override the target when it exists already!
	if [ -e "$1" ]; then
		# Target already exists. Is it different?
		if [ "$2" = "--init" ] && ! cmp "$1" "${BASE_D}/$1"; then
			# Files are not the same! Install new version in parallel:
			install -b -m 0644 -p -v "${BASE_D}/$1" "$1.new"
		fi
	else
		# Target does not yet exist:
		install -b -m 0644 -p -v "${BASE_D}/$1" "$1"
	fi
}

cmd="$1"
[ $# -gt 0 ] && shift

case "${cmd}" in
	"init")
		Init "$@"
		;;
	"upgrade"|"update"|"up")
		Upgrade "$@"
		;;
	"help"|"--help")
		Usage
		;;
	*)
		Usage
		exit 1
esac
exit 0
