#!/bin/bash

# Settings

declare \
	JAR_PATTERN="*upgrade-tool-*.jar" \
	JAVA_BIN="java" \
	REQUIRED_BASH="3.2.57" \
	REQUIRED_JAVA="1.8.0" \
	DEFAULT_LOG_CFG_FILE="upgrade-tool-log4j2.xml"

# Global variables

declare \
	JAVA_PATH \
	ACTION \
	MULE_HOME_OUTDATED \
	MULE_HOME_UPDATED \
	OUTDATED_JAR \
	UPDATED_JAR \
	OLD_AGENT_VERSION \
	NEW_AGENT_VERSION \
	JAR_NAME \
	DRY_RUN="false" \
	LOG_CFG_FILE \
	JAVA_ARGS

# Strings

declare -a MSG
MSG[0]="ERROR: %s version is %s, minimum required is %s, exiting\n"
MSG[1]="ERROR: Java binary not found neither in JAVA nor in JAVA_HOME, exiting\n"
MSG[2]="ERROR: Use absolute paths for MULE_HOMEs, exiting\n" # <--- Not used in Windows
MSG[3]="INFO: Mule Agent is outdated, running upgrade\n"
MSG[4]="INFO: Upgrading Mule Agent from %s to %s\n"
MSG[5]="WARN: Couldn't find any LOG4J configuration file, default configuration will be used\n"
MSG[6]="INFO: Skipping update, already at latest version\n"
MSG[7]="ERROR: MULE_HOME seems invalid, exiting\n"
MSG[8]="Verifying Mule Agent"
MSG[9]="Mule Runtime Upgrade Tool"
MSG[10]="INFO: Mule Agent is not present in the current setup, but it seems it was installed before, copying it to %s\n"
MSG[11]="INFO: Mule Agent not installed, skipping setup\n"
MSG[12]="INFO: No update available, skipping update\n"
MSG[13]="ERROR: Unable to access jarfile\n"
MSG[14]="INFO: Running in dry-run mode, commands to be executed will be just displayed in stdout\n"

function new_separator () {

	declare \
		TITLE \
		LINES \
		UNDERLINE \
		C_GREEN="\033[1;92m" \
		C_RESET="\033[0m"

	# Prepare
	TITLE="${1}"
	LINES=$( printf "%*s" ${#TITLE} "" )
	UNDERLINE="${LINES// /─}"

	# Print
	echo -e "${C_GREEN}"
	printf "%s\n" "${TITLE}"
	printf "%s\n" "${UNDERLINE}"
	echo -e "${C_RESET}"

	return 0
}

function compare_versions () {

	declare -i ERR=0
	declare NAME

	NAME="${1}"                       # Set ${NAME}
	IFS="." read -a CUR <<< "${2}"    # Set ${CUR}
	IFS="." read -a REQ <<< "${3}"    # Set ${REQ}
	unset IFS

	# Compare versions

	if [ ${CUR[0]} -lt ${REQ[0]} ]; then
		ERR=1
	elif [ ${CUR[0]} -eq ${REQ[0]} ]; then
		if [ ${CUR[1]} -lt ${REQ[1]} ]; then
			ERR=1
		elif [ ${CUR[1]} -eq ${REQ[1]} ]; then
			if [ ${CUR[2]} -lt ${REQ[2]} ]; then
				ERR=1
			fi
		fi
	fi

	# Report error

	if [ ${ERR} -eq 0 ]; then
		return 0
	else
		return 1
	fi
}

function check_bash_version () {

	declare -a \
		VERSION \
		ARGS

	declare \
		NAME \
		CUR \
		REQ

	VERSION[0]="${BASH_VERSINFO[@]:0:3}"
	VERSION[1]="${VERSION[0]// /.}"

	NAME="Bash"
	CUR="${VERSION[1]}"
	REQ="${REQUIRED_BASH}"
	IFS=$'\n' ARGS=( ${NAME} ${CUR} ${REQ} )

	compare_versions ${ARGS[@]}
	if [ ${?} -ne 0 ]; then
		printf "${MSG[0]}" "${NAME}" "${CUR}" "${REQ}" 1>&2
		exit 1
	else
		return 0
	fi
}

function search_in_path () {

	type -p ${JAVA_BIN} &>/dev/null

	if [ ${?} -eq 0 ]; then
		JAVA_PATH=$(type -p ${JAVA_BIN})
		return 0
	else
		return 1
	fi
}

function search_in_env () {

	if [ ! -z "${JAVA_HOME}" ]; then
		JAVA_PATH="${JAVA_HOME}/bin/${JAVA_BIN}"
		return 0
	else
		return 1
	fi
}

function search_java_bin () {

	if search_in_path; then
		return 0
	elif search_in_env; then
		return 0
	else
		printf "${MSG[1]}"
		exit 1
	fi
}

function check_java_version () {

	declare -a \
		JAVA_INFO \
		VERSION \
		ARGS

	declare \
		NAME \
		CUR \
		REQ

	IFS=$'\n' JAVA_INFO=( $( ${JAVA_PATH} -version 2>&1 ) )
	VERSION[0]="${JAVA_INFO[0]#*\"}"
	VERSION[1]="${VERSION[0]%\"*}"
	VERSION[2]="${VERSION[1]%_*}"

	NAME="Java"
	CUR="${VERSION[2]}"
	REQ="${REQUIRED_JAVA}"
	IFS=$'\n' ARGS=( ${NAME} ${CUR} ${REQ} )

	compare_versions ${ARGS[@]}
	if [ ${?} -ne 0 ]; then
		printf "${MSG[0]}" "${NAME}" "${CUR}" "${REQ}" 1>&2
		exit 1
	else
		return 0
	fi
}

function run_validations () {

	check_bash_version
	search_java_bin
	check_java_version

	return 0
}

function parse_arguments () {

	while [ -n "${1}" ]; do
		case "${1}" in

			"upgrade" | "rollback" | "status" )
				ACTION="${1}"
				;;

			-o | -o=* | --old-mule-distro | --old-mule-distro=* )
				if [[ "${1}" == *=* ]]; then
					MULE_HOME_OUTDATED="${1#*=}"
				else
					shift 1
					test ${1} && MULE_HOME_OUTDATED="${1}"
				fi
				;;

			-n | -n=* | --new-mule-distro | --new-mule-distro=* )
				if [[ "${1}" == *=* ]]; then
					MULE_HOME_UPDATED="${1#*=}"
				else
					shift 1
					test ${1} && MULE_HOME_UPDATED="${1}"
				fi
				;;

			-d | --dry-run )
				DRY_RUN="true"
				;;

			-l | --log-config )
				if [[ "${1}" == *=* ]]; then
					LOG_CFG_FILE="${1#*=}"
				else
					shift 1
					test ${1} && LOG_CFG_FILE="${1}"
				fi
				;;
		esac
		shift 1;
	done

	return 0
}

function test_homes () {

	# Check that MULE_HOMEs are readable and avoid relative paths

	for i in "${MULE_HOME_OUTDATED}" "${MULE_HOME_UPDATED}"; do
		# If it's not empty
		if [ ! -z "${i}" ]; then

			# If it's a directory
			if [ -d "${i}" ]; then

				# If starts with a slash (absolute path)
				if [[ ! ${i} =~ ^/ ]]; then
					printf "${MSG[2]}" 1>&2
					exit 1
				fi
			else
				printf "${MSG[7]}" 1>&2
				exit 1
			fi
		fi
	done

	return 0
}

function append_java_arg () {
	# If JAVA_ARGS is not empty, append a whitespace
	JAVA_ARGS+=$( [ "${JAVA_ARGS}" ] && echo " " )"${1}"
	return 0
}

function setup_logging () {
	if [ -n "${LOG_CFG_FILE}" ]; then
		FILE="${LOG_CFG_FILE}"
	else
		FILE="${DEFAULT_LOG_CFG_FILE}"
	fi

	if [ -f "${FILE}" ]; then
		append_java_arg "-Dlog4j2.configurationFile=${FILE}"
	else
		printf "${MSG[5]}"
		return 1
	fi
	return 0
}

function print_dryrun_msg () {
	printf "[ DRY-RUN ] %s\n" "${1}"
	return 0
}

function restore_agent () {

	declare \
		SRC \
		DST

	declare -i \
		VALID_SRC \
		VALID_DST

	# After upgrading, mule-agent-plugin doesn't exist, so If that's empty, grab a copy from the backup

	SRC=$( echo -n ${MULE_HOME_OUTDATED}/.upgrade-tool-backup/*plugins/mule-agent-plugin )
	DST=$( echo -n ${MULE_HOME_OUTDATED}/*plugins )

	VALID_SRC=0
	VALID_DST=0

	if [ -d ${SRC} ]; then
		if [[ ${SRC} =~ mule-agent-plugin$ ]]; then
			VALID_SRC=1
		fi
	fi

	if [ -d ${DST} ]; then
		if [ -d ${DST}/mule-agent-plugin ]; then
			VALID_DST=1
		fi
	fi

	if [ ${VALID_SRC} -eq 1 ] && [ ${VALID_DST} -eq 0 ]; then
		printf "${MSG[10]}" ${DST}
		unset IFS && read <<< "\cp -af ${SRC} ${DST}"
		test "${DRY_RUN}" = "false" && command ${REPLY} || print_dryrun_msg "${REPLY}"
		return 0

	else
		return 1
	fi
}

function agent_already_exists () {

	declare -i \
		UPGRADE

	declare \
		OUTDATED_PATH \
		UPDATED_PATH

	UPGRADE=0

	# Old version

	OUTDATED_PATH="${MULE_HOME_OUTDATED}/*plugins/mule-agent-plugin"
	OUTDATED_JAR=$( echo -n ${OUTDATED_PATH}/lib/mule-agent-api-*.*.*.jar )

	if [ ! -f ${OUTDATED_JAR} ]; then
		UPGRADE=1
		printf "${MSG[11]}"
	fi

	# New version

	UPDATED_PATH="${MULE_HOME_UPDATED}/tools"
	UPDATED_JAR=$( echo -n ${UPDATED_PATH}/agent-setup-*.*.*-amc-final.jar )

	if [ ! -f ${UPDATED_JAR} ]; then
		UPGRADE=1
		printf "${MSG[12]}"
	fi

	# Return

	return $UPGRADE
}

function new_agent_version_available () {

	declare -a \
		OLD_VER \
		NEW_VER \
		ARGS

	declare \
		NAME \
		CUR \
		REQ

	OLD_VER[0]=${OUTDATED_JAR}
	OLD_VER[1]=${OLD_VER[0]##*/}
	OLD_VER[2]=${OLD_VER[1]%*.jar}
	OLD_VER[3]=${OLD_VER[2]##*-}

	NEW_VER[0]=${UPDATED_JAR}
	NEW_VER[1]=${NEW_VER[0]##*/}
	NEW_VER[2]=${NEW_VER[1]%-amc-final.jar}
	NEW_VER[3]=${NEW_VER[2]#agent-setup-}

	NAME="Mule Agent"
	CUR="${OLD_VER[3]}"
	REQ="${NEW_VER[3]}"
	IFS=$'\n' ARGS=( ${NAME} ${CUR} ${REQ} )

	# Export info to the global variables:

	OLD_AGENT_VERSION="${OLD_VER[3]}"
	NEW_AGENT_VERSION="${NEW_VER[3]}"

	if compare_versions ${ARGS[@]}; then
		return 1

	else
		return 0
	fi
}

function upgrade_agent () {

	# These are the files we need to migrate manually
	FILES=(
		"bin/amc_setup*"
		"tools/agent-setup*"
	)

	printf "${MSG[3]}"
	printf "${MSG[4]}" "${OLD_AGENT_VERSION}" "${NEW_AGENT_VERSION}"

	# IFS was modified before and it affects read's behavior, unset it:
	unset IFS

	# Copy individual files manually
	read CMD <<< "\cp -f ${MULE_HOME_UPDATED}/${FILES[0]} ${MULE_HOME_OUTDATED}/bin/"
	test "${DRY_RUN}" = "false" && command ${CMD} || print_dryrun_msg "${CMD}"

	read CMD <<< "\cp -f ${MULE_HOME_UPDATED}/${FILES[1]} ${MULE_HOME_OUTDATED}/tools/"
	test "${DRY_RUN}" = "false" && command ${CMD} || print_dryrun_msg "${CMD}"

	# Run the upgrade, output is grayed out
	read CMD <<< "${JAVA_PATH} ${JAVA_OPTS} ${JAVA_ARGS} -jar ${UPDATED_JAR} --update --mule-home ${MULE_HOME_OUTDATED} > /dev/null"
	test "${DRY_RUN}" = "false" && command ${CMD} || print_dryrun_msg "${CMD}"

	return 0
}

function setup_mule_agent () {

	# If the Mule Agent is installed in the current Mule distro, check
	# if the new distro ships a newer version of the Mule Agent
	# If it does, update current Mule Agent by taking it from the newer
	# distro

	if [ "${ACTION}" = "upgrade" ]; then

		# When upgrading, we need both -o and -n
		if [ -z "${MULE_HOME_OUTDATED}" -o -z "${MULE_HOME_UPDATED}" ]; then
			# No action taken
			return 1
		fi

		new_separator "${MSG[8]}"

		if [ "${DRY_RUN}" = "true" ]; then
			printf "%s\n" "${MSG[14]}"
		fi

		restore_agent

		if agent_already_exists; then

			if new_agent_version_available; then
				upgrade_agent

			else
				printf "${MSG[6]}" ${NEW_AGENT_VERSION}
			fi

		else
			return 0
		fi
	fi

	return 0
}

function get_jar_name () {

	JAR_NAME=$( echo -e ${JAR_PATTERN})

	if [ ! -f ${JAR_NAME} ]; then
		printf "${MSG[13]}"
		exit 1
	else
		return 0
	fi
}

function run_program () {
	declare -a CMD_ARGS

	new_separator "${MSG[9]}"

	if get_jar_name; then
		CMD_ARGS=( "${JAVA_OPTS}" "${JAVA_ARGS}" "-jar" "${0%\/*}/${JAR_NAME}" "${@}" )
		command ${JAVA_PATH} ${CMD_ARGS[@]}
	else
		return 1
	fi
}

function main () {

	# Do some checks
	run_validations

	# Parse arguments to get MULE_HOMEs
	parse_arguments "${@}"

	# Test MULE_HOMEs
	test_homes

	# Use the user's own log4j file
	setup_logging

	# Run the JAR
	if run_program "${@}"; then
		setup_mule_agent
	else
		return 1
	fi
}

main "${@}" && exit 0 || exit 1
