#!/usr/bin/env bash

source dz-common

# Server container base directories
MPMISSIONS="${SERVER_FILES}/mpmissions"

mkdir -p ${SERVER_PROFILE}/battleye

# Server configuration file
SERVER_CFG_FILE="serverDZ.cfg"
SERVER_CFG_DST="${SERVER_PROFILE}/${SERVER_CFG_FILE}"
SERVER_CFG_SRC="${FILES}/${SERVER_CFG_FILE}"

# Command line parameters except mod, as that is handled separately.
parameters="-config=${SERVER_CFG_DST} -port=${port} -freezecheck -BEpath=${SERVER_PROFILE}/battleye -profiles=${SERVER_PROFILE} -nologs"

# Where mods are installed.
WORKSHOP_DIR="/mods/${release_client_appid}"

# Backups
BACKUP_DIR="${HOME}/backup"
if [ ! -d "${BACKUP_DIR}" ]
then
	mkdir -p "${BACKUP_DIR}"
fi

mod_command_line=""

# Functions

# Usage
usage(){
  echo -e "
${red}Bad option or arguments! ${yellow}${*}${default}

Usage: ${green}$(basename $0)${yellow} option [ arg1 [ arg2 ] ]

Options and arguments:

  a|activate id - Activate an installed DayZ Workshop items by id or index
  b|backup - Backup the mission storage files in all mission directories
  c|config - Update the internal serverDZ.cfg file from files/serverDZ.cfg on the host. Presents a unified diff if the internal file doesn't match the host file
  d|deactivate id - Deactivate an installed DayZ Workshop items by id or index - Keeps the mod files but excludes it from the mod parameter
  f|force - Forcibly kill the server. Use only as a last resort if the server won't shut down
  l|list - List Workshop items and their details
  n|rcon - Connect to the server using a python RCON client
  r|restart - Restart the server without restarting the container
  s|status - Shows the server's status: Running, uptime, mods, parameters, mod parameter, etc.
  stop - Stop the server
${default}"
  exit 1
}

loadconfig(){
	if [ ! -f "${SERVER_INSTALL_FILE}" ]
	then
		echo
		echo -e "The DayZ server files are not installed. You need to do this first in the web UI."
		echo
		exit 1
	fi
	# Handle the initial server configuration file
	if [ ! -f ${SERVER_CFG_DST} ]
	then
		echo "Creating initial server configuration file"
		cp "${SERVER_CFG_SRC}" "${SERVER_CFG_DST}"
	fi
	# battleye config and rconpassword setup
	# The server creates a new file from this file, which it then uses.
	# Let's make sure to delete it first
	BE_SERVER_FILE="${SERVER_PROFILE}/battleye/beserver_x64.cfg"
	ALT_BE_SERVER_FILE=$(find ${SERVER_PROFILE}/battleye -name "beserver_x64_active*")
	if [ ! -f "${BE_SERVER_FILE}" ] && [ ! -f "${ALT_BE_SERVER_FILE}" ]
	then
		passwd=$(openssl rand -base64 8 | tr -dc 'A-Za-z0-9')
		if [ "${passwd}" == "" ]
		then
			passwd=$(< /dev/urandom tr -dc 'A-Za-z0-9' | head -c10)
		fi
		if [ "${passwd}" == "" ]
		then
			printf "[ ${red}FAIL${default} ] Could not generate a passwort for RCON!\nOpen the Battleye config with 'dayzserver rcon'."
			exit 1
		else
			cat > "${BE_SERVER_FILE}" <<EOF
RConPassword ${passwd}
RestrictRCon 0
RConPort ${rcon_port}
EOF
		fi
		printf "[ ${cyan}INFO${default} ] New RCON password: ${yellow}${passwd}${default}\n"
	else
		if [ -f "${BE_SERVER_FILE}" ]
		then
			FILE="${BE_SERVER_FILE}"
		elif [ -f "${ALT_BE_SERVER_FILE}" ]
		then
			FILE="${ALT_BE_SERVER_FILE}"
		fi
		passwd=$(grep RConPassword ${FILE} | awk '{print $2}')
#		printf "[ ${cyan}INFO${default} ] Using existing RCON password: ${yellow}${passwd}${default}\n"
	fi
	cp /usr/local/py3rcon/configexample.json ~/py3rcon.config.json
	jq --arg port 2303 --arg rcon_password b0fNIBVfkM \
		'.logfile="py3rcon.log" | .loglevel=0 | .server.port=$port | .server.rcon_password=$rcon_password | del(.repeatMessage)' \
		/usr/local/py3rcon/configexample.json \
		> ~/py3rcon.config.json
}

# Make sure to clean up and report on exit, as these files remain in the container's volume
report() {
  if [[ ${DONT_START} != "" ]]
  then
    exit 0
  fi
	rm -f /tmp/mod_command_line /tmp/parameters
	echo
	echo -e "${yellow}========================================== error.log =========================================="
	find "${SERVER_PROFILE}" -name error.log -exec head {} \; -exec tail -n 30 {} \; -exec rm -f {} \;
	echo
	echo -e "========================================== script*.log ========================================"
	find "${SERVER_PROFILE}" -name "script*.log" -exec head {} \; -exec tail -n 30 {} \; -exec rm -f {} \;
	echo
	echo -e "========================================== *.RPT =============================================="
	find "${SERVER_PROFILE}" -name "*.RPT" -exec ls -la {} \; -exec tail -n 30 {} \; -exec rm -f {} \;
	echo
	echo -e "========================================== End log ======================================${default}"
	# Back these files up into a new directory with the current time stamp in the name
	DIR="${SERVER_PROFILE}/logs/$(date +%Y-%m-%d-%H-%M-%S)"
	mkdir -p ${DIR}
	cd ${SERVER_PROFILE}
	mv -v *.log *.RPT *.mdmp ${DIR} 2> /dev/null
}

mergexml(){
  echo
	# First copy the pristine files from upstream
	echo -e "${green}Copying upstream XML files into local mpmissions for map ${MAP}${default}":
	find /mpmissions/${MAP} \( -name "cfgeconomycore.xml" -o -name "cfgeventspawns.xml" -o -name "cfgenvironment.xml" \) -exec cp -v {} ${SERVER_FILES}{} \;
	# Follow https://community.bistudio.com/wiki/DayZ:Central_Economy_mission_files_modding
	# First, remove any existing files, via the mod_ prefix
	rm -rf ${MPMISSIONS}/${MAP}/mod_*
	for link in $(ls -tdr ${SERVER_PROFILE}/@* 2> /dev/null)
	do
		ID=$(readlink ${link} | awk -F/ '{print $NF}')
		# Going to have to maintain a matrix of file names -> root node -> child node permutations
		C=""
		FOUND=0
		for i in "CFGSPAWNABLETYPES:spawnabletypes:type" "EVENTS:events:event" "TYPES:types:type"
		do
			var=$(echo ${i} | cut -d: -f1)
			CHECK=$(echo ${i} | cut -d: -f2)
			CHILD=$(echo ${i} | cut -d: -f3)
			if [ -f "${WORKSHOP_DIR}/${ID}/${var,,}.xml" ]
			then
				if [[ ${FOUND} = 0 ]]
				then
					MODNAME=$(get_mod_name ${ID})
					echo
					echo -e "${green}Adding mod ${MODNAME}${default}"
					FOUND=1
				fi
				echo -n "Copy "
				mkdir -p ${MPMISSIONS}/${MAP}/mod_${ID}
				cp -v ${WORKSHOP_DIR}/${ID}/${var,,}.xml ${MPMISSIONS}/${MAP}/mod_${ID}/${var,,}.xml
				C+="-s / -t elem -n file -a /file -t attr -n name -v ${var,,}.xml -a /file -t attr -n type -v ${CHECK} -m /file /ce "
			fi
		done
		if [[ ${C} != "" ]]
		then
			# Merge into the current mpmissions file
			echo "Create new XML node <ce folder=\"mod_${ID}\"> -> ${MPMISSIONS}/${MAP}/cfgeconomycore.xml"
			find ${MPMISSIONS}/${MAP} -name cfgeconomycore.xml -exec \
				xmlstarlet ed -L -s / -t elem -n ce \
				-a /ce -t attr -n folder -v "mod_${ID}" ${C} \
				-m /ce /economycore {} \;
		fi
		# These are merged directly into the upstream file
		for i in "CFGEVENTSPAWNS:eventposdef:event" "CFGENVIRONMENT:env:territories/territory"
		do
			var=$(echo ${i} | cut -d: -f1)
			CHECK=$(echo ${i} | cut -d: -f2)
			CHILD=$(echo ${i} | cut -d: -f3)
			if [ -f "${WORKSHOP_DIR}/${ID}/${var,,}.xml" ]
			then
				echo "Merge XML ${WORKSHOP_DIR}/${ID}/${var,,}.xml -> ${MPMISSIONS}/${MAP}/${var,,}.xml"
				rm -f /tmp/x /tmp/y
				xmlmerge -o /tmp/x ${WORKSHOP_DIR}/${ID}/${var,,}.xml ${MPMISSIONS}/${MAP}/${var,,}.xml
				xmlstarlet fo /tmp/x > /tmp/y
				# Ensure the XML is valid
				xmllint --noout /tmp/y || (
					echo
					echo "Merged XML file ${MPMISSIONS}/${MAP}/${var,,}.xml is not valid! Can't continue!"
					echo
					exit 1
				)
				mv /tmp/y ${MPMISSIONS}/${MAP}/${var,,}.xml
			fi
		done
	done
	if [ -d ${SERVER_PROFILE}/custom ]
	then
		# First, remove any existing files, via the custom_ prefix
		rm -rf ${MPMISSIONS}/${MAP}/custom_*
		for dir in $(ls ${SERVER_PROFILE}/custom 2> /dev/null)
		do
			FOUND=0
			C=""
			for i in "CFGSPAWNABLETYPES:spawnabletypes:type" "EVENTS:events:event" "TYPES:types:type" "GLOBALS:globals:var"
			do
				var=$(echo ${i} | cut -d: -f1)
				CHECK=$(echo ${i} | cut -d: -f2)
				CHILD=$(echo ${i} | cut -d: -f3)
				if [ -f "${SERVER_PROFILE}/custom/${dir}/${var,,}.xml" ]
				then
					if [[ ${FOUND} = 0 ]]
					then
						echo
						echo -e "${green}Adding custom integration ${dir}${default}"
						FOUND=1
					fi
					mkdir -p ${MPMISSIONS}/${MAP}/custom_${dir}
					echo -n "Copy "
					cp -v ${SERVER_PROFILE}/custom/${dir}/${var,,}.xml ${MPMISSIONS}/${MAP}/custom_${dir}/${var,,}.xml
					C+="-s / -t elem -n file -a /file -t attr -n name -v ${var,,}.xml -a /file -t attr -n type -v ${CHECK} -m /file /ce "
				fi
			done
			if [[ ${C} != "" ]]
			then
				# Merge into the current mpmissions file
				echo "Create new XML node <ce folder=\"custom_${dir}\"> -> ${MPMISSIONS}/${MAP}/cfgeconomycore.xml"
				find ${MPMISSIONS}/${MAP} -name cfgeconomycore.xml -exec \
					xmlstarlet ed -L -s / -t elem -n ce \
					-a /ce -t attr -n folder -v "custom_${dir}" ${C} \
					-m /ce /economycore {} \;
			fi
		done
	fi
}

# Start the server in the foreground
start(){
	# If we're developing, just block the container
	if [[ ${DEVELOPMENT} = "1" ]]
	then
		echo "DEVELOPMENT mode, blocking. Unset DEVELOPMENT in the current environment to run the server."
		trap '
			echo "Caught SIGTERM/SIGINT..."
			exit 0
		' SIGTERM SIGINT
		while [ true ]
		do
			sleep 1
		done
		exit 0
	fi
	# Ensure mpmissions has at least one map. If not, copy it from the local read-only volume that stores pristine mpmissons directories
	if [ ! -d "${MPMISSIONS}/${MAP}" ] && [ -d "/mpmissions/${MAP}" ]
	then
		echo
		echo "Performing one-time copy of ${MAP} mpmissions..."
		echo
		cp -av /mpmissions/${MAP} ${MPMISSIONS}
	fi
	# Do the report on exit. Set here so that it only happens once we're starting the server, and not for other actions.
	trap '
		report
	' EXIT
	get_mods
	mergexml
	if [[ ${DONT_START} != "" ]]
	then
		echo
		echo "Not starting server, as DONT_START is set"
		echo
		exit 0
	fi
	echo
	cd ${SERVER_FILES}
	# Run the server. Allow docker to restart the container if the script exits with a code other than 0. This is so we can
	# safely shut the container down without killing the server within.
	printf "[ ${green}DayZ${default} ] Server starting...\n"
	# Save the mod command line and parameters that were used to start the server, so status reflects the running server's
	# actual status with those
	echo ${mod_command_line} > /tmp/mod_command_line
	echo ${parameters} > /tmp/parameters
	./DayZServer "${mod_command_line}" ${parameters}
	EXIT_CODE=$?
	if [ -f ${SERVER_FILES}/restart ]
	then
		rm -f ${SERVER_FILES}/restart
		EXIT_CODE=42
	fi
	printf "\n[ ${yellow}DayZ${default} ] Server exited. Exit code: ${EXIT_CODE}\n"
	exit ${EXIT_CODE}
}

# Restarts the server by forcing an exit code other than 0, causing docker to restart the container.
restart(){
	touch "${SERVER_FILES}/restart"
	echo "Restarting DayZ server..."
	kill -TERM $(pidof DayZServer)
}

# Stops the server cleanly and exits 0, which will stop the container.
stop(){
	echo "Stopping DayZ server..."
	kill -TERM $(pidof DayZServer)
}

# Forcibly kill the server, should it be necessary.
force(){
	echo "Forcibly stopping DayZ server..."
	kill -KILL $(pidof DayZServer)
}

# Handle any changes in the server config file by allowing them to be merged after viewing a diff.
config(){
	if ! diff -q "${SERVER_CFG_DST}" "${SERVER_CFG_SRC}"
	then
		echo "========================================================================="
		diff -Nau --color "${SERVER_CFG_DST}" "${SERVER_CFG_SRC}" | more
		echo "========================================================================="
		if prompt_yn "The new server configuration file differs from what's installed. Use it?"
		then
			echo "Updating the server configuration file"
			cp "${SERVER_CFG_SRC}" "${SERVER_CFG_DST}"
		else
			echo "NOT updating the server configuration file"
		fi
	else
		echo "No differences found between ${SERVER_CFG_SRC} and ${SERVER_CFG_DST}"
	fi
}

# Activate / Deactivate a mod
activate(){
	# W(hich?) a or d
	W=${1}
	# Pop that off so we can loop over the rest
	shift
	# Default values are when activating
	WW=""
	COLOR="${green}"
	if [[ ${W} = 0 ]]
	then
		# Deactivating instead
		WW="de"
		COLOR="${red}"
	fi
	# Loop over the rest of the argument(s)
	for i in ${@}
	do
		# Get the mod id and name
		ID=$(get_mod_id ${i} ${W})
		MODNAME=$(get_mod_name ${ID})
		# Toggle state or report nothing burger
		pushd "${SERVER_PROFILE}" > /dev/null
		if [[ ${W} = 0 ]] && [ -L "${SERVER_PROFILE}/@${MODNAME}" ]
		then
			echo -n "Removing mod symlink: "
			rm -vf "${SERVER_PROFILE}/@${MODNAME}"
		elif [[ ${W} = 1 ]]
		then
			echo -n "Creating mod symlink: "
			ln -sfv "${WORKSHOP_DIR}/${ID}" "${SERVER_PROFILE}/@${MODNAME}"
		else
			echo -e "Mod id ${ID} - ${COLOR}${MODNAME}${default} - is already ${WW}active"
		fi
		copy_keys ${W} ${ID}
		echo -e "Mod id ${ID} - ${COLOR}${MODNAME}${default} ${WW}activated"
		popd > /dev/null
	done
	status
}

# Our internal RCON
rcon(){
	exec /usr/local/py3rcon/py3rcon.py --gui ~/py3rcon.config.json
}

# List mods
activelist(){
	X=1
  C="${green}"
	spaces="                            "
	have=no
  for link in $(ls -tdr ${SERVER_PROFILE}/@* 2> /dev/null)
	do
	  if [[ ${have} = "no" ]]
	  then
	    have="yes"
      echo -e "\n     ID         Name                             URL                                                                Size"
      echo "------------------------------------------------------------------------------------------------------------------------"
    fi
		ID=$(readlink ${link} | awk -F/ '{print $NF}')
	  MODNAME=$(get_mod_name ${ID})
		SIZE=$(du -sh "${WORKSHOP_DIR}/${ID}" | awk '{print $1}')
		printf "${C}%.3d  %s %.30s    %s  https://steamcommunity.com/sharedfiles/filedetails/?id=%s  %s${default}\n" ${X} ${ID} "${MODNAME}" "${spaces:${#MODNAME}+1}" ${ID} ${SIZE}
		X=$((X+1))
	done
	if [[ ${have} = "no" ]]
  then
    echo -ne "${red}none${default}"
  fi
}

get_map_name(){
  MAP="none"
  # Map name
  if [[ -f ${SERVER_CFG_DST} ]]
  then
    MAP=$(grep -E "template=" ${SERVER_CFG_DST} | grep -vE "^//" | cut -d= -f2 | cut -d\; -f1 | tr -d '"')
  fi
  echo ${MAP}
}

# Display the status of everything
status(){
  loadconfig
	INSTALLED="${NO}"
	RUNNING="${NO}"

	# DayZ Server files installation
	if [ -f "${SERVER_INSTALL_FILE}" ]
	then
		INSTALLED="${YES}"
	fi
	# Running or not
	if pidof DayZServer > /dev/null
	then
		# Uptime
		D=$(date +%s)
		F=$(date +%s -r ${SERVER_PROFILE}/server_console.log)
		DAYS=$(( (${D} - ${F}) / 86400 ))
#		UPTIME=$(date --date="$(( ${D} - ${F} ))" +"${DAYS} days %H:%M:%S")
		UPTIME="${DAYS} days "$(date -d@$(($(date +%s) - $(date +%s -r ${SERVER_PROFILE}/server_console.log))) -u +"%H hours %M minutes %S seconds")

		RUNNING="${YES}\nUptime: ${green}${UPTIME}${default}"
		# Current parameters
		RUNNING="${RUNNING}\nRunning Parameters: $(cat /tmp/parameters)\nRunning mod parameter: $(cat /tmp/mod_command_line)"
	fi
  MAP=${MAP}
	# Number of mods plus the list denoting on or off
	echo -ne "
Server files installed: ${INSTALLED}"
	if [[ "${INSTALLED}" = "${NO}" ]]
	then
		echo
		echo
		exit 0
	fi
	get_mods
	echo -ne "
Active mods: "
  activelist
	echo -e "${MODS}
Server running: ${RUNNING}
Working parameters: ${parameters}
Working mod parameter: ${mod_command_line}"
	if [[ "${INSTALLED}" = "${YES}" ]]
	then
		echo "Map: ${MAP}"
	fi
	echo
}

backup(){
	cd ${MPMISSIONS}
	DATE=$(date +'%Y-%m-%d-%H-%M-%S')
	for i in $(ls)
	do
		B="${BACKUP_DIR}/${DATE}/"
		echo "Backing up ${i} to ${B}..."
		mkdir -p ${B} 1> /dev/null
		cp -a "${i}" "${B}"
	done
	cp -a /profiles ${B}
}

MAP=$(get_map_name)

# Capture the first argument and shift it off so we can pass $@ to every function
C=${1}
shift || {
	usage
}

case "${C}" in
	a|activate)
		activate 1 "${@}"
		;;
	add)
		add "${@}"
		;;
	b|backup)
		backup "${@}"
		;;
	c|config)
		config "${@}"
		;;
	d|deactivate)
		activate 0 "${@}"
		;;
	f|force)
		force
		;;
	i|install)
		install "${@}"
		;;
	l|list)
		list "${@}"
		;;
	login)
		login "${@}"
		;;
	n|rcon)
		rcon "${@}"
		;;
	r|restart)
		restart "${@}"
		;;
	start)
		start "${@}"
		;;
	s|status)
		status "${@}"
		;;
	stop)
		stop "${@}"
		;;
	*)
		usage "$*"
	;;
esac
