#! /usr/bin/env bash

################################################################################
# Functions
################################################################################

eecho()
{
	echo "$@" 1>&2
}

ecat()
{
	cat 1>&2
}

panic()
{
	eecho "ERROR: $*"
	exit 1
}

log()
{
	eecho "$(basename "$0"): $*"
}

find_python()
{
	local name=
	local python_path=
	for name in python python3 python2; do
		local path=$(type -P "$name") || path=
		if [ -n "$path" ]; then
			if [ -x "$path" ]; then
				python_path="$path"
				break
			fi
		fi
	done
	if [ -z "$python_path" ]; then
		return 1
	fi
	echo "$python_path"
}

realpath()
{
	local path="$1"
	"$python_path" -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' \
	  "$path"
}

is_abs_path()
{
	[ $# -eq 1 ] || return 1
	local path="$1"
	"$python_path" \
	  -c 'import os, sys; print("{:d}".format(os.path.isabs(sys.argv[1])))' \
	  "$path"
}

get_clang_version()
{
	local program="${1:-clang++}"
	"$program" --version | awk '
		(NR==1){
			for (i = 1; i <= NF; ++i) {
				if (tolower($i) == "version") {
					++i;
					print $i;
					break;
				}
			}
		}
	'
}

is_ccache()
{
	[ $# -eq 1 ] || return 1
	local program_path="$1"
	local program_canonpath="$(realpath "$program_path")" || return 1
	local target_basename="$(basename "$program_canonpath")" || return 1
	local dir="$(dirname "$program_path")" || return 1
	local result=0
	case "$target_basename" in
	*ccache*)
		result=1;;
	esac
	case "$dir" in
	*ccache*)
		result=1;;
	esac
	echo "$result"
}

################################################################################
# Early Initialization
################################################################################

# NOTE: The python program must be located before is_abs_path or realpath
# is used.
python_path="$(find_python)" || panic "cannot find python"

################################################################################
# Command-Line Processing
################################################################################

usage()
{
	echo "BAD USAGE: $*"
	exit 2
}

self_canonpath="$(realpath "$0")" || \
  panic "cannot get real path of program"
self_dir="$(dirname "$self_canonpath")" || \
  panic "cannot get directory of program"

default_names=(clang++)

verbose=0
names=()
path_spec=
program_path=

command_line_args=("$@")

while getopts :vn:p:P: option; do
	case "$option" in
	v)
		verbose=$((verbose + 1));;
	n)
		names+=("$OPTARG");;
	p)
		path_spec="$OPTARG";;
	P)
		program_path="$OPTARG";;
	\?)
		usage "invalid option $option";;
	esac
done
shift $((OPTIND - 1))

if [ ${#names[@]} -eq 0 ]; then
	names=("${default_names[@]}")
fi
if [ -z "$path_spec" ]; then
	path_spec="$PATH"
fi
if [ -n "$program_path" ]; then
	abs=$(is_abs_path "$program_path") || \
	  panic "is_abs_path failed"
	if [ "$abs" -eq 0 ]; then
		usage "program path is not absolute"
	fi
fi

keys=("$@")

################################################################################
# Determine Clang Information
################################################################################

sde_top_dir="$SDE_TOP_DIR"
if [ -n "$sde_top_dir" ]; then
	sde_top_dir="$(realpath "$sde_top_dir")" || \
	  panic "realpath failed"
fi

if [ "$verbose" -ge 3 ]; then
	log "command line: $0 ${command_line_args[*]}"
fi
if [ "$verbose" -ge 2 ]; then
	log "path: $path_spec"
fi

# If the path for the program is given, ensure that it is valid.
if [ -n "$program_path" ]; then

	new_program_path="$program_path"
	new_program_canonpath=

	program_path=
	program_canonpath=

	dir="$(dirname "$new_program_path")" || \
	  panic "cannot get dirname"

	ccache="$(is_ccache "$dir")" || \
	  panic "ccache check failed"
	if [ "$verbose" -ge 3 ]; then
		eecho "ccache: $ccache"
	fi

	if [ "$ccache" -eq 0 -a -x "$new_program_path" ]; then
		new_program_canonpath="$(realpath "$new_program_path")" || \
		  new_program_canonpath=
		if [ -n "$new_program_canonpath" ]; then
			program_path="$new_program_path"
			program_canonpath="$new_program_canonpath"
		fi
	fi

fi

if [ "$verbose" -ge 3 ]; then
	eecho "program path: $program_path"
	eecho "program canonical path: $program_canonpath"
fi

# If no path for the program is known, attempt to determine it.
if [ -z "$program_path" ]; then

	IFS=":" read -a path <<< "$path_spec" || panic "cannot get search path"
	program_path=
	program_canonpath=
	for name in "${names[@]}"; do
	#for dir in "${path[@]}"; do

		#for name in "${names[@]}"; do
		for dir in "${path[@]}"; do

			# Skip empty strings resulting from empty entries in search path.
			if [ -z "$dir" ]; then
				continue
			fi
			if [ "$verbose" -ge 3 ]; then
				log "checking for directory $dir"
			fi

			ccache="$(is_ccache "$dir")" || \
			  panic "ccache check failed"
			if [ "$ccache" -ne 0 ]; then
				if [ "$verbose" -ge 2 ]; then
					log "skipping ccache directory $dir"
				fi
				continue
			fi

			if [ -n "$sde_top_dir" ]; then
				path="$(realpath "$dir")" || panic "realpath failed"
				if [ "$path" == "$sde_top_dir/bin" ]; then
					if [ "$verbose" -ge 2 ]; then
						log "skipping SDE directory $dir"
					fi
					continue
				fi
			fi

			new_program_path="$dir/$name"
			if [ "$verbose" -ge 3 ]; then
				log "checking for file $new_program_path"
			fi
			if [ -e "$new_program_path" ]; then
				if [ "$verbose" -ge 3 ]; then
					if [ -h "$new_program_path" ]; then
						log "file is symlink: $new_program_path [$(realpath "$new_program_path")]"
					fi
					if [ ! -x "$new_program_path" ]; then
						log "file is not executable: $new_program_path"
					fi
				fi
				if [ -x "$new_program_path" ]; then
					new_program_canonpath="$(realpath "$new_program_path")" || \
					  new_program_canonpath=
					# Only break if the canonical path can be resolved.
					if [ -n "$new_program_canonpath" ]; then
						program_path="$new_program_path"
						program_canonpath="$new_program_canonpath"
						break 2
					else
						if [ "$verbose" -ge 3 ]; then
							log "cannot resolve path $new_program_path"
						fi
					fi
				fi
			fi
		#done
		done

	#done
	done

fi

version=
major_version=
program_dir=
include_dir=

if [ -n "$program_path" ]; then

	version="$(get_clang_version "$program_path")" || \
	  panic "cannot get clang version"
	major_version="${version%%.*}"

	#program_dir="$(dirname "$program_path")" || panic "dirname failed"
	#program_dir="$(realpath "$program_dir")" || program_dir=

	program_dir="$(dirname "$program_canonpath")" || panic "dirname failed"
	#program_dir="$(realpath "$program_dir")" || program_dir=

	if [ -n "$program_dir" ]; then
		# Note: The order of the following list of directories is important.
		# Look for the directory names based on $clang_version before
		# the ones based on $clang_major_version.
		dir_list=(
			"$program_dir/../lib/clang/$version/include"
			"$program_dir/../lib64/clang/$version/include"
			"$program_dir/../lib/clang/$major_version/include"
			"$program_dir/../lib64/clang/$major_version/include"
			# The following are for Ubuntu but may not be needed:
			"/usr/include/clang/$version/include"
			"/usr/include/clang/$major_version/include"
			# The following are for MacOS and Brew:
			"/usr/local/opt/llvm@$major_version/lib/clang/$version/include"
			"/usr/local/opt/llvm@$major_version/lib/clang/$major_version/include"
		)
		for dir in "${dir_list[@]}"; do
			if [ "$verbose" -ge 2 ]; then
				log "checking for existence of directory $dir"
			fi
			if [ -d "$dir" ]; then
				new_include_dir="$dir"
				if [ "$verbose" -ge 3 ]; then
					log "found directory $dir"
				fi
				include_dir="$new_include_dir"
				break
			fi
		done
		if [ -n "$include_dir" ]; then
			include_dir="$(realpath "$include_dir")"
			if [ "$verbose" -ge 3 ]; then
				log "include directory after canonicalization $include_dir"
			fi
		fi
	fi

fi

for key in "${keys[@]}"; do

	case "$key" in
	program_path)
		[ -n "$program_path" ] || \
		  panic "could not determine value for key $key"
		echo "$program_path"
		;;
	program_dir)
		[ -n "$program_dir" ] || \
		  panic "could not determine value for key $key"
		echo "$program_dir"
		;;
	version)
		[ -n "$version" ] || \
		  panic "could not determine value for key $key"
		echo "$version"
		;;
	major_version)
		[ -n "$major_version" ] || \
		  panic "could not determine value for key $key"
		echo "$major_version"
		;;
	include_dir)
		[ -n "$include_dir" ] || \
		  panic "could not determine value for key $key"
		echo "$include_dir"
		;;
	*)
		panic "invalid key $key"
	esac

done

if [ "$verbose" -ge 1 ]; then
	ecat <<- EOF
	program path: $program_path
	program dir: $program_dir
	version: $version
	major version: $major_version
	include dir: $include_dir
	EOF
fi
