#!/bin/bash

# Exit if an error or an unset variable
set -e -u

# make sure unmatched glob gives empty
shopt -s nullglob

root_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
tools_dir=${TOOLS_DIR:-${root_dir}/tools}
downloads_dir=${DL_DIR:-${root_dir}/downloads}
tool_overrides_dir=$root_dir/make/tool_install

batch=${BATCH:-false}
force=false
remove=false
includes=true

for arg in "${@:1}"
do
	[ "$arg" = "-r" ] && remove=true
	[ "$arg" = "-f" ] && force=true
	[ "$arg" = "-n" ] && includes=false

done

tool=${@: -1}

uname=$(uname)
if [[ "$uname" != [LD]* ]]
then
	uname=Windows
fi


# Batch mode
if $batch
then
	CURL_OPTIONS=(--silent -L)
else
	CURL_OPTIONS=(-L)
fi

################################################################################
# Helper functions
################################################################################

function exit_error
{
	error=$?
	echo "${@}"
	exit $error	
}

##  Downloads a file if it doesn't exist
#1  URL
#2  Output filename (optional)
#3+ Additional options to pass to curl
## Sets:
#out_file: path of the downloaded file 
function download_file
{
	out_file="$downloads_dir/${2:-$(basename "$1")}"

	if ! [ -f "$out_file" ]
	then
		mkdir -p "$downloads_dir" && \
		cd "$downloads_dir" && \
		echo "Downloading $1" && \
		curl "${CURL_OPTIONS[@]}" "${@:3}" -o "$out_file" "$1"
	fi
}

## Unzips a file
#1 The file to unzip
#2 The output directory
function zip_extract
{
	unzip -q "$1" -d "$2"
}

## Extracts a 7zip file
#1 The file to extract
#2 The output directory
function sevenzip_extract
{
	if [ "$uname" = Windows ]
	then
		7za.exe x -o"$2" "$1"
	else
		7zr x -o"$2" "$1"
	fi
}

## Extracts a tar file
#1 The file to extract
#2 The output directory
function tar_extract
{
	tar -xf "$1" -C "$2"
}

##  Extracts a file
#1  File to extract
#2  Extract directory (optional)
no_extract=false # Optional
## Sets:
#out_dir: directory the file was extracted into 
function extract_file
{
	out_dir="${2:-.}"

	echo "Extracting $1"

	mkdir -p "$out_dir" && \
	case "$1" in
		*.zip)
			zip_extract "$1" "$out_dir"
			;;
		*.7z)
			sevenzip_extract "$1" "$out_dir"
			;;
		*.tar*)
			tar_extract "$1" "$out_dir"
			;;
		*)
			if $no_extract
			then
				cp "$1" "$out_dir"
			else
				return 1
			fi
	esac
}

## Verifies an md5 file
#1 md5 file
#2 file to check
function md5_verify_file
{
	if [ "$uname" = Darwin ]
	then
		[[ "$(md5 "$2")" = *"$(awk '{print $1}' "$1")"* ]]
	else
		( cd "$downloads_dir" && md5sum -c "$1" )
	fi
}

################################################################################
# Default functions
################################################################################

function validate_target { false; }

function remove
{
	rm -rf "$tools_dir/$tool_install_name"
	rm -f "$tools_dir/$tool".{sh,mk}
}

declare -a depends=()
function install_deps
{
	# Workaround for set -u and empty array
	for dep in "${depends[@]:+${depends}}"
	do
		BATCH="$batch" "${BASH_SOURCE[0]}" "$dep"
	done && \
	source_includes
}

## Downloads and verifies the tool
## Required:
#tool_url: the url to download the tool from
## Optional:
#tool_md5
#tool_md5_url
function download_and_verify
{
	verified=true
	download_file "$tool_url" && \
	downloaded_file=$out_file && \
	if [ -n "${tool_md5_url:-}" ]
	then
		download_file "$tool_md5_url" "$(basename "$downloaded_file").md5" --silent && \
		if ! md5_verify_file "$out_file" "$downloaded_file"
		then
			mv -f "$downloaded_file"{,.rej} && \
			mv -f "$downloaded_file".md5{,.rej} && \
			verified=false
		fi
	elif [ -n "${tool_md5:-}" ]
	then
		if [[ "$tool_md5"* != "$(cd "$downloads_dir" && md5sum "$downloaded_file")" ]]
		then
			mv -f "$downloaded_file"{,.rej} && \
			verified=false
		fi
	fi && \
	$verified
}

function tool_is_installed { [ -e  "$full_tool_install_name" ] || which "$tool" &>/dev/null; }

## Downloads and extracts the tool
## Required:
#tool_url: the url to download the tool from
#tool_install_name: the directory or file the tool will be installed as
## Optional:
#tool_extract_dir: Directory to extract into (useful if build required)
function download_and_extract
{
	local full_tool_install_name="$tools_dir/$tool_install_name"
	if ! tool_is_installed || $force
	then
		download_and_verify || exit_error "Failed to verify $downloaded_file"
		rm -rf "$full_tool_install_name" && \
		extract_file "$downloaded_file" "${tool_extract_dir:-$tools_dir}"
	fi
}

function build_and_install { true; } # Most tools don't need this step

## Write modules that are included by this script and make
## Optional:
bin_dir=""
bin_subdir="" #
module_file=$tool
function write_modules
{
	if [ -n "$bin_subdir" ]
	then
		bin_dir="$tools_dir/$tool_install_name/$bin_subdir"
	fi

	if [ -n "$bin_dir" ]
	then
		local new_path="$bin_dir"':${PATH}'
		# Write shell module file
		echo 'if [[ ":$PATH:" != *":'"$bin_dir"':"* ]]; then export PATH='"$new_path"'; fi' > "$tools_dir/$module_file".sh
		# Write make module file
		echo 'ifeq ($(findstring :'"$bin_dir"':,:$(PATH):),)' > "$tools_dir/$module_file".mk
		echo "export PATH := $new_path" >> "$tools_dir/$module_file".mk
		echo "endif" >> "$tools_dir/$module_file".mk
	fi
}


function source_includes
{
	if $includes
	then
		for module in "$tools_dir"/*.sh
		do
			source "$module"
		done
	fi
}

################################################################################
# Peform tool install
################################################################################
source_includes || exit_error "failed to source includes"

source "$tool_overrides_dir/${tool}.sh"

if $remove
then
	remove || exit_error "Failed to remove ${tool}"
else
	validate_target || exit_error "${tool} is not a valid target"

	install_deps || exit_error "Failed to install dependencies for ${tool}"

	download_and_extract || exit_error "Failed to download and extract ${tool}"

	build_and_install || exit_error "Failed to build and install ${tool}"

	write_modules || exit_error "Failed to write modules for ${tool}"
fi
