#!/bin/bash
# vi: se fdm=marker
# R front script for biodb extensions packages, version 1.1.2

SCRIPT_FILE=$(basename $0)
SCRIPT_DIR=$(dirname $0)
VERSION=1.0
DEBUG=0
R_BUILD_FOLDER=${R_BUILD_FOLDER:-$HOME/.local/opt/r}
VERSIONS_INFO_FILENAME=VERSION-INFO.dcf
VERSIONS_FILE="$R_BUILD_FOLDER/$VERSIONS_INFO_FILENAME"
R=R
MIRROR="https://cloud.r-project.org/"
R_VERSION=${R_VERSION:-current}
declare -a ACTIONS=()

# Print help {{{1
################################################################

function print_help {
cat <<END_HELP_MSG
$SCRIPT_FILE, version $VERSION, is a front end for R, allowing to choose between installed R or R beta or R devel versions.

Usage: $SCRIPT_FILE [options] [-- [options passed to R]] 

Options:

   --beta       Install and use beta version of R.

   --clean      Remove everything from build folder.

   --devel      Install and use devel version of R.

   -g, --debug  Debug mode.

   -h, --help   Print this help message.

   -n, --no-current
                Do not use the R binary currently installed on system.

   --print-bin  Print path to R bin.

   --print-home Print path to R folder.

   --r-version <version>
                Choose the version of R to run. Either a version number (4.0, 4.1, ...) or one of "current" (version installed), "release" (official release), "beta" (official prerelease) or "devel" (official development). Default is "current".

   --run        Run the R binary, passing any remaining arguments to it.

   -u, --update-versions
                Update the versions file, that lists downloadable versions of R.

END_HELP_MSG
}

# Error {{{1
################################################################

error() {
	local msg="$*"

	echo "[ERROR] $msg at ${BASH_SOURCE[1]}:${BASH_LINENO[0]}" >&2
	quit 1
}

# Warning {{{1
################################################################

warning() {
	local msg="$*"

	[[ -n $QUIET ]] || echo "[WARNING] $msg at"\
		"${BASH_SOURCE[1]}:${BASH_LINENO[0]}" >&2
}

# Debug {{{1
################################################################

debug() {
	local lvl=$1
	local msg="$*"

	[[ $DEBUG -ge $lvl ]] && echo "[DEBUG] $msg" >&2
}

# Info {{{1
################################################################

info() {
	local msg="$*"

	[[ -n $QUIET ]] || echo "[INFO] $msg" >&2
}

# Quit {{{1
################################################################

function quit {

	local mystatus=$1

	[[ ${#ACTIONS[@]} -eq 0 ]] || warning "Some remaining actions where not run: ${ACTIONS[@]}."

	exit $mystatus
}

# Read args {{{1
################################################################

function read_args {

	local args="$*" # save arguments for debugging purpose
	local print=

	while true ; do
		case $1 in
			--beta)          R_VERSION=beta ;;
			--clean)         ACTIONS+=('clean') ;;
			--devel)         R_VERSION=devel ;;
			-g|--debug)      DEBUG=$((DEBUG + 1)) ;;
			-h|--help)       print_help ; exit 0 ;;
			-n|--no-current) NO_CURRENT=1 ;;
			--print-bin)     ACTIONS+=('select_r' 'print_bin') ;;
			--print-home)    ACTIONS+=('select_r' 'print_home') ;;
			--r-version)     R_VERSION="$2" ; shift ;;
			--run)           ACTIONS+=('select_r' 'run_r') ;;
			-u|--update-versions) ACTIONS+=('download_versions_file') ;;
			-[^-]*)          split_opt=$(echo $1 | sed 's/^-//' | sed 's/\([a-zA-Z]\)/ -\1/g') ; set -- $1$split_opt "${@:2}" ;;
			--)              shift ; break ;;
			*)               break
		esac
		shift
	done

	# Read remaining arguments
	R_ARGS=("$@")

	# Debug
	debug 1 "Arguments are : $args"
	debug 1 "ACTIONS=${ACTIONS}"
	debug 1 "DEBUG=$DEBUG"
	debug 1 "NO_CURRENT=$NO_CURRENT"
	debug 1 "R_ARGS=$R_ARGS"
	debug 1 "R_VERSION=$R_VERSION"
}

# Download versions file {{{1
################################################################

function download_versions_file {

	mkdir -p "$R_BUILD_FOLDER"
	if [[ -f "$VERSIONS_FILE" ]] ; then
		curl -o "$VERSIONS_FILE" -z "$VERSIONS_FILE" "$MIRROR/src/base/$VERSIONS_INFO_FILENAME"
	else
		curl -o "$VERSIONS_FILE" "$MIRROR/src/base/$VERSIONS_INFO_FILENAME"
	fi
}

# Select version {{{1
################################################################

function select_version {

	if [[ -z $SELECTED_VERSION ]] ; then

		# Version name
		if [[ $R_VERSION =~ ^(beta|devel|release|current)$ ]] ; then
			SELECTED_VERSION=$R_VERSION

		# Version number
		elif [[ $R_VERSION =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]] ; then

			local version_name=

			# Try current version
			if [[ -z $NO_CURRENT ]] ; then
				local current_version=$(R --version | head -n 1 | sed 's/^.*version \([^ ]*\).*$/\1/')
				debug 1 current_version=$current_version
				if [[ $current_version =~ ^$R_VERSION(\.[0-9]+)?$ ]] ; then
					version_name=current
				fi
			fi

			# Try downloadable versions
			if [[ -z $version_name ]] ; then
				[[ -f $VERSIONS_FILE ]] || download_versions_file
				version_name=$(grep ": $R_VERSION" "$VERSIONS_FILE" | sed 's/^\(.*\):.*$/\1/')
				[[ -n $version_name ]] || error "Cannot find any version name"\
					"in \"$VERSIONS_FILE\" for version \"$R_VERSION\"."
			fi

			# Set selected version
			case $version_name in
				Next-release) SELECTED_VERSION=beta ;;
				Devel)        SELECTED_VERSION=devel ;;
				Release)      SELECTED_VERSION=release ;;
				current)      SELECTED_VERSION=current ;;
				*)            error "Unknown version name \"$version_name\"." ;;
			esac

		# Unknown version
		else
			error "Unknown version $R_VERSION."
		fi
	fi

	debug 1 "select_version SELECTED_VERSION=$SELECTED_VERSION"
}

# Extract real version {{{1
################################################################

function extract_real_version {

	[[ -n $SELECTED_VERSION ]] || select_version

	if [[ -z $REAL_VERSION ]] ; then

		[[ -f $VERSIONS_FILE ]] || download_versions_file
		case $SELECTED_VERSION in
			beta)    version_name=Next-release ;;
			devel)   version_name=Devel ;;
			release) version_name=Release ;;
			*)       error "Unknown version $SELECTED_VERSION to download." ;;
		esac

		REAL_VERSION=$(grep "^$version_name:" "$VERSIONS_FILE" | sed 's/^[^:]*: *\(.*\)$/\1/')
	fi

	debug 1 "extract_real_version real_version=$REAL_VERSION"
}

# Get download URL {{{1
################################################################

function get_download_url {

	local url=
	[[ -n $SELECTED_VERSION ]] || select_version

	case $SELECTED_VERSION in
		beta)    url=$MIRROR/src/base-prerelease/R-latest.tar.gz ;;
		devel)   url=$MIRROR/src/base-prerelease/R-devel.tar.gz ;;
		release) url=$MIRROR/src/base/R-latest.tar.gz ;;
		current) url= ;;
	esac

	debug 1 "get_download_url URL=$url"
	echo "$url"
}

# Set R folder {{{1
################################################################

function set_r_folder {

	if [[ -z $R_FOLDER ]] ; then

		[[ -n $SELECTED_VERSION ]] || select_version

		if [[ $SELECTED_VERSION == current ]] ; then
			R_FOLDER=$(/usr/bin/env R --slave --no-restore RHOME)
		else
			[[ -n $REAL_VERSION ]] || extract_real_version
			R_FOLDER=$R_BUILD_FOLDER/R-$REAL_VERSION
		fi
	fi

	debug 1 "get_r_folder R_FOLDER=$R_FOLDER"
}

# Set R bin path {{{1
################################################################

function set_r_bin_path {

	if [[ -z $R_BIN_PATH ]] ; then
		R_BIN_PATH=R
		[[ -n $SELECTED_VERSION ]] || select_version
		if [[ $SELECTED_VERSION != current ]] ; then
			set_r_folder
			R_BIN_PATH="$R_FOLDER/bin/R"
		fi
	fi

	debug 1 "set_r_bin_path R_BIN_PATH=$R_BIN_PATH"
}

# Install R {{{1
################################################################

function install_r {

	[[ -d $R_BUILD_FOLDER ]] || mkdir -p "$R_BUILD_FOLDER"
	set_r_folder
	set_r_bin_path

	debug 1 "install_r R_BUILD_FOLDER=$R_BUILD_FOLDER"
	if [[ ! -x $R_BIN_PATH ]] ; then

		debug 1 "install_r R_BIN_PATH=$R_BIN_PATH"
		# Remove possible existing destination folder
		rm -rf "$R_FOLDER"

		# Make temporary folder for extraction
		tmp="$R_BUILD_FOLDER/extract_folder"
		mkdir -p "$tmp"

		# Move to temporary folder
		oldpwd=$(pwd)
		cd "$tmp"

		# Extract
		curl "$(get_download_url)" | tar -xz

		# Create destination folder
		mkdir -p "$R_FOLDER"

		# Move code
		mv "$tmp"/*/* "$R_FOLDER"/.

		# Exit temporary folder
		cd "$oldpwd"

		# Remove temporary folder
		rm -r "$tmp"

		# Build
		cd "$R_FOLDER" && ./configure --prefix="$HOME/.local" >&2
		make -C "$R_FOLDER" >&2
	fi
}

# Select R {{{1
################################################################

function select_r {

	set_r_folder
	set_r_bin_path
	R="$R_BIN_PATH"
	R_HOME="$R_FOLDER"
	[[ $R_VERSION == current ]] || install_r $R_VERSION

	debug 1 "select_r R=$R"
	debug 1 "select_r R_HOME=$R_HOME"
}

# Print bin path {{{1
################################################################

function print_bin {
	echo "$R"
}

# Print path to home {{{1
################################################################

function print_home {
	echo "$R_HOME"
}

# Run R {{{1
################################################################

function run_r {
	export R_HOME
	debug 1 "R_HOME=$R_HOME"
	debug 1 "$R $R_ARGS"
	$R "${R_ARGS[@]}"
}

# Clean build folder {{{1
################################################################

function clean {
	[[ -n $R_BUILD_FOLDER ]] && rm -rf "$R_BUILD_FOLDER"
}

# Run actions {{{1
################################################################

function run_actions {

	# Loop on all actions in the submitted order
	while [[ ${#ACTIONS[@]} -gt 0 ]] ; do

		# Pop first action
		local action=${ACTIONS[0]} # Index starts at 1 in zsh, 0 in bash.
		ACTIONS=("${ACTIONS[@]:1}") # Remove first element
		debug 1 "action=$action"

		# Run
		case $action in
			clean)      ;&
			print_bin)  ;&
			print_home) ;&
			select_r)   ;&
			download_versions_file) ;&
			run_r)      $action ;;
			*)          error "Unknown action \"$action\"." ;;
		esac
	done
}

# Move action to start of array {{{1
################################################################

function move_action_to_start {

	local action="$1"

	if [[ " ${ACTIONS[@]} " == *" $action "* ]] ; then
		declare -a new_actions=($action)
		for a in ${ACTIONS[@]} ; do
			[[ $a == $action ]] || new_actions+=($a)
		done
		ACTIONS=("${new_actions[@]}")
		unset new_actions
	fi
}

# Order actions {{{1
################################################################

function order_actions {

	move_action_to_start "select_r"
	move_action_to_start "download_versions_file"
	move_action_to_start "clean"
}

# Main {{{1
################################################################

read_args "$@"

order_actions
run_actions

quit 0