#! /usr/bin/env bash
## -*- sh -*-
## @configure_input@

## Get program name
PROG=$(basename $0)

## Default value for num_jobs is all available cores
let num_jobs=$(getconf _NPROCESSORS_ONLN)

function usage {
	cat <<-EOF
		$PROG:
		Compile a Rivet analysis plugin library from one or more sources

		Usage: $PROG [options] [libname] source1 [...] [compiler_flags]

		<libname> can be a path, provided the filename is of the form 'Rivet*.so'
		If <libname> is not specified, the default name is 'RivetAnalysis.so'.

		To make special build variations you can add appropriate compiler flags
		to the arguments and these will be passed directly to the compiler. For
		example, for a debug build of your plugin library, add '-g', and for a
		32 bit build on a 64 bit system add '-m32'.

		Options:
		  -h | --help:      display this help message
		  -j NUM            number of parallel compile jobs [$num_jobs]
		  -r | --with-root: add ROOT link options (requires root-config on system)
		  -n | --cmd | --dry-run:
		                    just print the generated commands, do not execute
		  -k | --keep:      keep intermediate files
		  -v | --verbose:   write out debug info

	EOF
}


##################
## Option handling
# http://www.bahmanm.com/blogs/command-line-options-how-to-parse-in-bash-using-getopt

## Translate long options to short
# https://stackoverflow.com/a/5255468
args=()
for arg
do
	case "$arg" in
	   # translate our long options
	   --help            ) args+=("-h");;
	   --with-root       ) args+=("-r");;
	   --cmd | --dry-run ) args+=("-n");;
	   --keep            ) args+=("-k");;
	   --verbose         ) args+=("-v");;
	   # pass through anything else
	   *                 ) args+=("$arg");;
	esac
done
## Reset the translated args
set -- "${args[@]}"

## Now we can process with getopt
while getopts ":hj:rnkv" opt; do
	case $opt in
		h)  usage; exit 0 ;;
		j)  num_jobs=$OPTARG ;;
		r)  with_root=yes ;;
		n)  only_show=yes ;;
		k)  keep_tmps=yes ;;
		v)  debug=yes ;;
		\?) echo "Unknown option -$OPTARG" >&2; exit 1 ;;
		:)  echo "Option -$OPTARG requires an argument" >&2; exit 1 ;;
	esac
done
## Remove options
shift $((OPTIND-1))

if [[ -n "$RIVET_BUILD_ALLCAT" ]]; then num_jobs=1; fi

## Check num_jobs is a number
if [[ ! $num_jobs -eq $num_jobs ]]; then
	echo "Unknown argument to -j" >&2; exit 1
fi
if [[ $num_jobs -lt 1 ]]; then
	echo "Number of jobs must be positive" >&2; exit 1
fi

#echo "$with_root"
#echo "$only_show"
#echo "$@"

## Need some args left at this point
if [[ $# -lt 1 ]]; then
	usage >&2
	exit 1
fi

## Get and check the target library name
libname="$1"
match1=$(basename "$libname" | grep -E -- '^.*\.so')
match2=$(basename "$libname" | grep -E -- '^Rivet.*\.so')
if test -n "$match1"; then
	if test -z "$match2"; then
		echo "Library name '$libname' does not have the required 'Rivet*.so' name pattern" >&2
		exit 1
	fi
	## If we're using the first arg as the library name, shift it off the positional list
	shift
else
	if [[ -z $only_show ]]; then
		echo "Using default library name 'RivetAnalysis.so'"
	fi
	libname="RivetAnalysis.so"
fi

## Again need some args left at this point
if [[ $# -lt 1 ]]; then
	usage >&2
	exit 1
fi


##################
## Now assemble the build flags


## These variables need to exist, may be used in later substitutions
## Note no use of $DESTDIR... we ignore it so that destdir can be used
## for temp installs later copied to /
prefix="@prefix@"
exec_prefix="@exec_prefix@"
datarootdir="@datarootdir@"

## Work out shared library build flags by platform
if [[ $(uname) == "Darwin" ]]; then
  ## macOS
  shared_flags="-undefined dynamic_lookup -bundle"
else
  ## Unix
  shared_flags="-shared -fPIC"
fi

## Get Rivet system C++ compiler (fall back to $CXX and then g++ if needed)
mycxx=g++
rivetcxx=$(which $(echo "@RIVETCXX@" | awk '{print $1}') 2> /dev/null)
abscxx=$(which "$CXX" 2> /dev/null)
if [[ -x "$rivetcxx" ]]; then
	mycxx="@CXX@"
elif [[ -x "$abscxx" ]]; then
	mycxx="$CXX"
fi

## Get Rivet system C++ compiler flags
# Add compile time CXXFLAGS 
if [[ -n "@AM_CXXFLAGS@" ]]; then
	mycxxflags="@AM_CXXFLAGS@"
fi
if [[ -n "@RIVETCXXFLAGS@" ]]; then
	mycxxflags="$mycxxflags @RIVETCXXFLAGS@"
fi

## Remove the "-shared" flag from clang
grepclang1=$(echo "$mycxx" | grep "clang" 2> /dev/null)
grepclang2=$($mycxx --version | grep "clang" 2> /dev/null)
if [[ -n "$grepclang1" || -n "$grepclang2" ]]; then
    #shared_flags=$(echo $shared_flags | sed -e 's/-shared//g')
    shared_flags="-Wno-unused-command-line-argument $shared_flags"
fi

## Get Rivet system C preprocessor flags (duplicating that in rivet-config.in)
if [[ -n "$RIVET_BUILD_BEFORE_INSTALL" ]]; then
    irivet="@top_srcdir@/include"
else
    irivet="@includedir@"
fi
test -n "$irivet" && mycppflags="$mycppflags -I${irivet}"
@ENABLE_HEPMC_3_TRUE@ihepmc_1="@HEPMC3INCPATH@ @CPPFLAGS@"
@ENABLE_HEPMC_3_FALSE@ihepmc_2="@HEPMCINCPATH@"
test -n "$ihepmc_1" && mycppflags="$mycppflags -I${ihepmc_1}"
test -n "$ihepmc_2" && mycppflags="$mycppflags -I${ihepmc_2}"
iyoda="@YODAINCPATH@"
test -n "$iyoda" && mycppflags="$mycppflags -I${iyoda}"
ifastjet="@FASTJETINCPATH@"
test -n "$ifastjet" && mycppflags="$mycppflags -I${ifastjet}"
ifjcontrib="@FJCONTRIBCPPFLAGS@"
test -n "$ifjcontrib" && mycppflags="$mycppflags ${ifjcontrib}"

## Get Rivet system linker flags (duplicating that in rivet-config.in)
myldflags=""
if [[ -n "$RIVET_BUILD_BEFORE_INSTALL" ]]; then
    lrivet="@top_builddir@/src/.libs"
else
    lrivet="@libdir@"
fi
test -n "$lrivet" && myldflags="$myldflags -L${lrivet}"
@ENABLE_HEPMC_3_TRUE@lhepmc_1="@HEPMC3LIBPATH@"
@ENABLE_HEPMC_3_FALSE@lhepmc_2="@HEPMCLIBPATH@"
test -n "$lhepmc_1" && myldflags="$myldflags -L${lhepmc_1}"
test -n "$lhepmc_2" && myldflags="$myldflags -L${lhepmc_2}"
lyoda="@YODALIBPATH@"
test -n "$lyoda" && myldflags="$myldflags -L${lyoda}"
lfastjet="@FASTJETLIBADD@"
test -n "$lfastjet" && myldflags="$myldflags ${lfastjet}"
## Detect whether the linker accepts the --no-as-needed flag and prepend the linker flag with it if possible
if (cd /tmp && echo -e 'int main() { return 0; }' > $$.cc; $mycxx -Wl,--no-as-needed $$.cc -o $$ 2> /dev/null); then
  myldflags="-Wl,--no-as-needed $myldflags"
fi

# Add build time AM_LDFLAGS(missing?) and LDFLAGS to our linker flags 
if [[ -n "@RIVETLDFLAGS@" ]] ; then
    myldflags="$myldflags @RIVETLDFLAGS@"
fi

## Get ROOT flags if needed
if [[ -n $with_root ]]; then
	rootcxxflags=$(root-config --cflags 2> /dev/null)
	rootldflags=$(root-config --libs 2> /dev/null)
fi


##################
## Assemble and run build machinery

## Split sources into buckets, one for each core
let idx=1
sources=""
for src in "$@"
do
	if [[ -s "$src" ]]; then
        sources="$sources $src"
		buckets[$idx]="${buckets[$idx]} $src"
        if [[ -n $RIVET_BUILD_NOCAT ]]; then
		    let idx=idx+1
        else
            let idx=(idx%$num_jobs)+1
        fi
	else
		if [[ ${src:0:1} == "-" ]]; then
			## Found a user option
			usercxxflags="$usercxxflags $src"
		else
			echo "Warning: $src not found" >&2
		fi
	fi
done

## May be less than num_jobs
let num_buckets=${#buckets[@]}

if [[ $num_buckets -lt 1 ]]; then
	echo "Error: no source files found" >&2
	exit 2
fi

## Loop over buckets
for idx in $(seq 1 $num_buckets); do

	## DO NOT SIMPLIFY, the OS X mktemp can't deal with suffixes directly
	tmpfile="$(mktemp tmp.XXXXXXXX)"
	mv "$tmpfile" "$tmpfile.cc"
	tmpfile="$tmpfile.cc"

    ## Concatenate the analysis codes into a single .cc, with separator lines to fool the preprocessor!
	for i in $(echo ${buckets[$idx]}); do #< find the real way to do this!
		echo -e "\n#line 1 \"$i\"" >> "$tmpfile"  #< include a terminating newline, in case missing
		cat "$i" >> "$tmpfile"
	done

    ## Record the concatenated temp-file path
	if [[ -s "$tmpfile" ]]; then
		srcnames[$idx]="$tmpfile"
	fi
done
objnames=("${srcnames[@]/%.cc/.o}")

if [[ -z "$debug" ]]; then silencer="@"; fi
tmpmakefile=$(mktemp Makefile.tmp.XXXXXXXXXX)
cat > "$tmpmakefile" <<EOF
RCXX = $mycxx

RCXXFLAGS = $shared_flags $mycxxflags $rootcxxflags

RCPPFLAGS = $mycppflags

RLDFLAGS = $myldflags $rootldflags

RLIBS = -lRivet

RUSERFLAGS = $usercxxflags

$libname : ${objnames[@]}
	${silencer}\$(RCXX) \$^  \$(RCXXFLAGS) \$(CXXFLAGS) \$(LDFLAGS) \$(RLDFLAGS) \$(RLIBS) \$(LIBS) -o \$@ \$(RUSERFLAGS)

%.o : %.cc
	${silencer}\$(RCXX) \$(RCXXFLAGS) \$(CXXFLAGS) \$(CPPFLAGS) \$(RCPPFLAGS) -c -o \$@ \$^ \$(RUSERFLAGS)
EOF


## Declare a cleanup command, to be run on exit or abort
if [[ -z $keep_tmps ]]; then
    function _cleanup() {
	    rm -f "${srcnames[@]}" "${objnames[@]}" "$tmpmakefile"
    }
    trap _cleanup EXIT
fi


## Show effective build command and run the (split) compilation
effcmd="$mycxx -o \"$libname\" $shared_flags $mycppflags $CPPFLAGS $mycxxflags $rootcxxflags $CXXFLAGS $myldflags $rootldflags -lRivet $LDFLAGS $LIBS $sources $usercxxflags"
echo "$effcmd"
if [[ -n $debug ]]; then
    echo "Contents of generated Makefile ($tmpmakefile):"
	echo "====="
	cat "$tmpmakefile"
    echo
fi

## If we're in a chain of make calls,  make's jobserver tells us how many jobs
## to run via MAKEFLAGS. We can't specify -j explicitly in that case
if [[ -z ${MAKEFLAGS} ]]; then
    jflag="-j ${num_jobs}"
fi

if [[ -z $only_show ]]; then
	make ${jflag} -f "$tmpmakefile" || exit 3
fi
