#!/bin/bash

# Requirements: git-bash
# https://git-scm.com/download/win

set -Eeuo pipefail

trap cleanup SIGINT SIGTERM ERR EXIT

ScriptFullname=$(readlink -e "$0")
ScriptName=$(basename "$0")
ScriptDir=$(dirname "$ScriptFullname")

# Useful things
source "$ScriptDir/common.lib"

# Common
SteamPath=$(reg_readkey "HKCU\Software\Valve\Steam" "SteamPath")
DocumentsPath=$(reg_readkey "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" "Personal")
ThirdPartyBin="$ScriptDir/3rd-party-bin"

# Usefull KF2 executables / Paths / Configs
KFDoc="$DocumentsPath/My Games/KillingFloor2"
KFPath="$SteamPath/steamapps/common/killingfloor2"
KFWin64="$KFPath/Binaries/Win64"
KFEditor="$KFWin64/KFEditor.exe"
KFEditorPatcher="$KFWin64/kfeditor_patcher.exe"
KFEditorMergePackages="$KFWin64/KFEditor_mergepackages.exe"
KFGame="$KFWin64/KFGame.exe"
KFWorkshop="$KFPath/Binaries/WorkshopUserTool.exe"
KFUnpublish="$KFDoc/KFGame/Unpublished"
KFPublish="$KFDoc/KFGame/Published"
KFEditorConf="$KFDoc/KFGame/Config/KFEditor.ini"

# Source filesystem
MutSource="$ScriptDir/.."
MutPubContent="$MutSource/PublicationContent"
MutConfig="$MutSource/Config"
MutLocalization="$MutSource/Localization"
MutBuildConfig="$MutSource/build.cfg"
MutTestConfig="$MutSource/test.cfg"

# Steam workshop upload filesystem
KFUnpublishBrewedPC="$KFUnpublish/BrewedPC"
KFUnpublishPackages="$KFUnpublishBrewedPC/Packages"
KFUnpublishScript="$KFUnpublishBrewedPC/Script"
KFUnpublishConfig="$KFUnpublish/Config"
KFUnpublishLocalization="$KFUnpublish/Localization"
KFPublishBrewedPC="$KFPublish/BrewedPC"
KFPublishPackages="$KFPublishBrewedPC/Packages"
KFPublishScript="$KFPublishBrewedPC/Script"
KFPublishConfig="$KFPublish/Config"
KFPublishLocalization="$KFPublish/Localization"

# Tmp files
MutWsInfo="$KFDoc/wsinfo.txt"
KFEditorConfBackup="$KFEditorConf.backup"

function show_help ()
{
	cat <<EOF
Usage: $0 OPTION

Build, pack, test and upload your kf2 packages to the Steam Workshop.

Available options:
   -ib, --init-build   generate $(basename "$MutBuildConfig") with build parameters 
   -it, --init-test    generate $(basename "$MutTestConfig") with test parameters 
    -i, --init         the same as "./$ScriptName --init-build; ./$ScriptName --init-test"
    -c, --compile      build package(s)
    -b, --brew         compress *.upk and place inside *.u
   -bm, --brew-manual  the same (almost) as above, but with patched kfeditor by @notpeelz
    -u, --upload       upload package(s) to the Steam Workshop 
    -t, --test         run local single player test with $(basename "$MutTestConfig") parameters
    -v, --version      show version
    -h, --help         show this help
  
Shortcuts for multiple options:
   -cb                 compile, brew
   -cu                 compile, upload
  -cbm                 compile, brew_manual
  -cbu                 compile, brew, upload
 -cbmu                 compile, brew_manual, upload
   -ct                 compile, run_test
  -cbt                 compile, brew, run_test
 -cbmt                 compile, brew_manual, run_test
EOF
}

function show_version ()
{
	cat <<EOF
$ScriptName $(git describe 2> /dev/null)
EOF
}

function cleanup()
{
	trap - SIGINT SIGTERM ERR EXIT
	restore_kfeditorconf
}

function backup_kfeditorconf ()
{
	cp -f "$KFEditorConf" "$KFEditorConfBackup"
}

function restore_kfeditorconf ()
{
	if [[ -f "$KFEditorConfBackup" ]]; then
		mv -f "$KFEditorConfBackup" "$KFEditorConf"
	fi
}

function init_build ()
{
	local PackageList=""
	
	:> "$MutBuildConfig"
	
	while read -r Package
	do
		if [[ -z "$PackageList" ]]; then
			PackageList="$Package"
		else
			PackageList="$PackageList $Package"
		fi
	done < <(find "$MutSource" -mindepth 2 -maxdepth 2 -type d -ipath '*/Classes' | sed -r 's|.+/([^/]+)/[^/]+|\1|' | sort)
	
	cat > "$MutBuildConfig" <<EOF
# Build parameters 

# If True - compresses the mutator when compiling
# Scripts will be stored in binary form
# (reduces the size of the output file)
StripSource="True"

# Mutators to be compiled
# Specify them with a space as a separator,
# Mutators will be compiled in the specified order 
PackageBuildOrder="$PackageList"

# Mutators that will be uploaded to the workshop
# Specify them with a space as a separator,
# The order doesn't matter 
PackageUpload="$PackageList"
EOF
}

function read_build_settings ()
{
	if ! [[ -f "$MutBuildConfig" ]]; then init_build; fi
	
	if bash -n "$MutBuildConfig"; then
		# shellcheck source=./.shellcheck/build.cfg
		source "$MutBuildConfig"
	else
		echo "$MutBuildConfig broken! Check this file before continue or create new one using $0 --init-build"
		return 1
	fi
}

function read_test_settings ()
{
	if ! [[ -f "$MutTestConfig" ]]; then init_test;	fi
	
	if bash -n "$MutTestConfig"; then
		# shellcheck source=./.shellcheck/test.cfg
		source "$MutTestConfig"
	else
		echo "$MutTestConfig broken! Check this file before continue or create new one using $0 --init-test"
		return 1
	fi
}

function merge_package () # $1: What, $2: Where
{
	local ModificationTime=""
	local ModificationTimeNew=""
	local PID=""
	
	ModificationTime=$(stat -c %y "$KFWin64/$2")
	CMD //C "cd /D $(cygpath -w "$KFWin64") && $(basename "$KFEditorMergePackages") make $1 $2" &
	PID="$!"
	while ps -p "$PID" &> /dev/null
	do
		ModificationTimeNew="$(stat -c %y "$KFWin64/$2")"
		if [[ "$ModificationTime" != "$ModificationTimeNew" ]]; then # wait for write
			while ps -p "$PID" &> /dev/null
			do
				ModificationTime="$ModificationTimeNew"
				sleep 1
				ModificationTimeNew="$(stat -c %y "$KFWin64/$2")"
				if [[ "$ModificationTime" == "$ModificationTimeNew" ]]; then # wait for write finish
					kill "$PID"
					rm -f "$KFWin64/$1" # cleanup (auto)
					return 0
				fi
			done
		fi
		sleep 1
	done
	
	rm -f "$KFWin64/$1" # cleanup (manual)
}

function merge_packages () # $1: Mutator name
{
	cp -f "$KFUnpublishScript/$1.u" "$KFWin64"
	
	while read -r Upk
	do
		cp -f "$MutSource/$1/$Upk" "$KFWin64"
		merge_package "$Upk" "$1.u"
	done < <(find "$MutSource/$1" -type f -name '*.upk' -printf "%f\n")
}

function compiled ()
{
	for Package in $PackageBuildOrder
	do
		if ! test -f "$KFUnpublishScript/$Package.u"; then
			return 1
		fi
	done
}

function compile ()
{
	local StripSourceArg=""
	local PID=""
	
	read_build_settings

	if ! command -v multini &> /dev/null; then
		get_latest_multini "$ThirdPartyBin/multini.exe"
	fi
	
	backup_kfeditorconf
	
	multini --del "$KFEditorConf" 'ModPackages' 'ModPackages'
	for Package in $PackageBuildOrder
	do
		multini --add "$KFEditorConf" 'ModPackages' 'ModPackages' "$Package"
	done
	multini --set "$KFEditorConf" 'ModPackages' 'ModPackagesInPath' "$(cygpath -w "$MutSource")"
	
	rm -rf "$KFUnpublish" "$KFPublish"
	
	mkdir -p "$KFUnpublishPackages" "$KFUnpublishScript"
	
	for Package in $PackageBuildOrder
	do
		find "$MutSource/$Package" -type f -name '*.upk' -exec cp -f {} "$KFUnpublishPackages" \;
	done
	
	if [[ -d "$MutLocalization" ]]; then
		mkdir -p "$KFUnpublishLocalization"
		cp -rf "$MutLocalization"/* "$KFUnpublishLocalization"
	fi
	
	if [[ -d "$MutConfig" ]]; then
		mkdir -p "$KFUnpublishConfig"
		cp -rf "$MutConfig"/* "$KFUnpublishConfig"
	fi
	
	if is_true "$StripSource"; then StripSourceArg="-stripsource"; fi
	
	CMD //C "$(cygpath -w "$KFEditor") make $StripSourceArg -useunpublished" &
	PID="$!"
	while ps -p "$PID" &> /dev/null
	do
		if compiled; then kill "$PID"; break; fi
		sleep 1
	done 
	
	restore_kfeditorconf
}

function publish_common ()
{
	if [[ -d "$MutLocalization" ]]; then
		mkdir -p "$KFPublishLocalization"
		cp -rf "$MutLocalization"/* "$KFPublishLocalization"
	fi
	
	if [[ -d "$MutConfig" ]]; then
		mkdir -p "$KFPublishConfig"
		cp -rf "$MutConfig"/* "$KFPublishConfig"
	fi
}

function brewed ()
{
	for Package in $PackageUpload
	do
		if ! test -f "$KFPublishBrewedPC/$Package.u"; then
			return 1
		fi
	done
}

function brew_cleanup ()
{
	for Package in $PackageBuildOrder
	do
		if ! echo "$PackageUpload" | grep -Pq "(^|\s+)$Package(\s+|$)"; then
			find "$KFPublishBrewedPC" -type f -name "$Package.u" -delete
			find "$MutSource/$Package" -type f -name '*.upk' -printf "%f\n" | xargs -I{} find "$KFPublishBrewedPC" -type f -name {} -delete
		fi
	done
	
	rm -f "$KFPublishBrewedPC"/*.tmp
}

function brew ()
{
	local PID=""
	
	read_build_settings
	
	if ! compiled ; then
		echo "You must compile packages before brewing. Use $0 --compile for this."
		exit 1
	fi
	
	rm -rf "$KFPublish"
	
	mkdir -p "$KFPublishBrewedPC"
	
	CMD //C "cd /D $(cygpath -w "$KFWin64") && $(basename "$KFEditor") brewcontent -platform=PC $PackageUpload -useunpublished" &
	PID="$!"
	while ps -p "$PID" &> /dev/null
	do
		if brewed; then kill "$PID"; break; fi
		sleep 1
	done
	
	publish_common
	brew_cleanup
	
	find "$KFPublishBrewedPC" -type d -empty -delete
}

function brew_manual ()
{
	read_build_settings
	
	if ! compiled ; then
		echo "You must compile packages before brewing. Use $0 --compile for this."
		exit 1
	fi
	
	rm -rf "$KFPublish"
	
	mkdir -p "$KFPublishBrewedPC"

	if ! [[ -x "$KFEditorPatcher" ]]; then
		get_latest_kfeditor_patcher "$KFEditorPatcher"
	fi
	
	CMD //C "cd /D $(cygpath -w "$KFWin64") && $(basename "$KFEditorPatcher")"
	
	for Package in $PackageUpload
	do
		merge_packages "$Package"
		mv "$KFWin64/$Package.u" "$KFPublishBrewedPC"
	done
	
	publish_common
	
	find "$KFPublishBrewedPC" -type d -empty -delete
}

# Uploading without brewing
function publish_unpublished ()
{
	mkdir -p "$KFPublishBrewedPC" "$KFPublishScript" "$KFPublishPackages"
		
	for Package in $PackageUpload
	do
		cp -f "$KFUnpublishScript/$Package.u" "$KFPublishScript"
		find "$MutSource/$Package" -type f -name '*.upk' -exec cp -f {} "$KFPublishPackages" \;
	done
	
	publish_common
	
	find "$KFPublishBrewedPC" -type d -empty -delete
}

function upload ()
{
	local PreparedWsDir=""
	
	read_build_settings
	
	if ! compiled ; then
		echo "You must compile packages before uploading. Use $0 --compile for this."
		exit 1
	fi
	
	if ! [[ -d "$KFPublish" ]]; then
		publish_unpublished
	fi
	
	find "$KFPublishBrewedPC" -type d -empty -delete
	
	PreparedWsDir=$(mktemp -d -u -p "$KFDoc")

	cat > "$MutWsInfo" <<EOF
\$Description "$(cat "$MutPubContent/description.txt")"
\$Title "$(cat "$MutPubContent/title.txt")"
\$PreviewFile "$(cygpath -w "$MutPubContent/preview.png")"
\$Tags "$(cat "$MutPubContent/tags.txt")"
\$MicroTxItem "false"
\$PackageDirectory "$(cygpath -w "$PreparedWsDir")"
EOF
	
	cp -rf "$KFPublish" "$PreparedWsDir"
	
	CMD //C "$(cygpath -w "$KFWorkshop") $(basename "$MutWsInfo")"
	
	rm -rf "$PreparedWsDir"
	rm -f "$MutWsInfo"
}

function init_test ()
{
	local AviableMutators=""
	local AviableGamemodes=""
	
	read_build_settings
	
	for Package in $PackageUpload
	do
		# find available mutators
		while read -r MutClass
		do
			if [[ -z "$AviableMutators" ]]; then
				AviableMutators="$Package.$MutClass"
			else
				AviableMutators="$AviableMutators,$Package.$MutClass"
			fi
		done < <(grep -rihPo '\s.+extends\s(KF)?Mutator' "$MutSource/$Package" | awk '{ print $1 }')
		
		# find available gamemodes
		while read -r GamemodeClass
		do
			if [[ -z "$AviableGamemodes" ]]; then
				AviableGamemodes="$Package.$GamemodeClass"
			else
				AviableGamemodes="$AviableGamemodes,$Package.$GamemodeClass"
			fi
		done < <(grep -rihPo '\s.+extends\sKFGameInfo_' "$MutSource/$Package" | awk '{ print $1 }')
	done
	
	if [[ -z "$AviableGamemodes" ]]; then
		AviableGamemodes="KFGameContent.KFGameInfo_Survival"
	fi
	
	cat > "$MutTestConfig" <<EOF
# Test parameters 

# Map:
Map="KF-Nuked"

# Game:
# Survival:       KFGameContent.KFGameInfo_Survival
# WeeklyOutbreak: KFGameContent.KFGameInfo_WeeklySurvival
# Endless:        KFGameContent.KFGameInfo_Endless
# Objective:      KFGameContent.KFGameInfo_Objective
# Versus:         KFGameContent.KFGameInfo_VersusSurvival
Game="$AviableGamemodes"

# Difficulty:
# Normal:         0
# Hard:           1
# Suicide:        2
# Hell:           3
Difficulty="0"

# GameLength:
# 4  waves:       0
# 7  waves:       1
# 10 waves:       2
GameLength="0"

# Mutators
Mutators="$AviableMutators"

# Additional parameters
Args=""
EOF
}

function run_test ()
{
	local UseUnpublished=""
	
	read_build_settings
	read_test_settings
	
	if ! brewed; then UseUnpublished="-useunpublished"; fi
	
	CMD //C "$(cygpath -w "$KFGame") $Map?Difficulty=$Difficulty?GameLength=$GameLength?Game=$Game?Mutator=$Mutators?$Args $UseUnpublished" -log
}

function debug ()
{
	set -o xtrace
}

export PATH="$PATH:$ThirdPartyBin"

if [[ $# -eq 0 ]]; then show_help; exit 0; fi
case $1 in
# Options
	  -h|--help             ) show_help                             ;;
	  -v|--version          ) show_version                          ;;
	 -ib|--init-build       ) init_build                            ;;
	 -it|--init-test        ) init_test                             ;;
	  -i|--init             ) init_build; init_test                 ;;
	  -c|--compile          ) compile                               ;;
	  -b|--brew             ) brew                                  ;;
	 -bm|--brew-manual      ) brew_manual                           ;;
	  -u|--upload           ) upload                                ;;
	  -t|--test             ) run_test                              ;;
# Shortcuts
	  -cb                   ) compile; brew                         ;;
	  -cu                   ) compile;              upload          ;;
	  -cbm                  ) compile; brew_manual                  ;;
	  -cbu                  ) compile; brew;        upload          ;;
	  -cbmu                 ) compile; brew_manual; upload          ;;
	  -ct                   ) compile;              run_test        ;;
	  -cbt                  ) compile; brew;        run_test        ;;
	  -cbmt                 ) compile; brew_manual; run_test        ;;
# Debug
	  -dh                   ) debug; show_help                      ;;
	  -dv                   ) debug; show_version                   ;;
	  -dib                  ) debug; init_build                     ;;
	  -dit                  ) debug; init_test                      ;;
	  -di                   ) debug; init_build; init_test          ;;
	  -dc                   ) debug; compile                        ;;
	  -db                   ) debug; brew                           ;;
	  -dbm                  ) debug; brew_manual                    ;;
	  -du                   ) debug; upload                         ;;
	  -dt                   ) debug; run_test                       ;;
	  -dcb                  ) debug; compile; brew                  ;;
	  -dcu                  ) debug; compile;              upload   ;;
	  -dcbm                 ) debug; compile; brew_manual           ;;
	  -dcbu                 ) debug; compile; brew;        upload   ;;
	  -dcbmu                ) debug; compile; brew_manual; upload   ;;
	  -dct                  ) debug; compile;              run_test ;;
	  -dcbt                 ) debug; compile; brew;        run_test ;;
	  -dcbmt                ) debug; compile; brew_manual; run_test ;;
# Other
	    *                   ) echo "Command not recognized: $1"; exit 1 ;;
esac