#!/bin/sh
set -eu

quiet=
name="fetchdarcs"
repository=
tag=
context=
darcs_hash=
exp_hash=

usage() {
	echo "Usage: nix-prefetch-darcs [options] [REPOSITORY] [FILENAME [EXPECTED-HASH]]"
	echo
	echo "Options:"
	echo "	--quiet               Suppress most error messages."
	echo "	--name <NAME>         Symbolic store path name to use for the result."
	echo "	--repo <REPOSITORY>   URL for the Darcs repository."
	echo "	--tag <REGEXP>        Clone specified by tag matching a regular expression."
	echo "	--context <FILENAME>  Clone specified by context file."
	echo "	--darcs-hash <HASH>   Clone specified by hash. WARN: hash order is fickle by design."
	echo "	--hash <HASH>         Expected hash."
	echo "	--help                Show this help message."
}

# Argument parsing
while [ $# -gt 0 ]; do
	case "$1" in
		--quiet)
		  quiet=1; shift 1 ;;
		--name)
			name="$2"; shift 2 ;;
		--repository)
			repository="$2"; shift 2 ;;
		--tag)
			tag="$2"; shift 2 ;;
		--context)
			context="$2"; shift 2 ;;
		--darcs-hash)
			darcs_hash="$2"; shift 2 ;;
		--hash)
			exp_hash="$2"; shift 2 ;;
		--help)
			usage; exit 0 ;;
		*)
			# Positional arguments
			if [ -z "$repository" ]; then
				repository="$1"
				shift
			elif [ -z "$context" ]; then
				context="$1"
				shift
			elif [ -z "$exp_hash" ]; then
				exp_hash="$1"
				shift
			else
				echo "Error: Too many arguments" >&2
				usage
				exit 1
			fi
			;;
	esac
done

if [ -z "$repository" ]; then
	echo "Error: URL for repository is required." >&2
	echo >&2
	usage
	exit 1
fi

state_flag_count=0
[ -n "$tag" ] && state_flag_count=$(( state_flag_count + 1 ))
[ -n "$context" ] && state_flag_count=$(( state_flag_count + 1 ))
[ -n "$darcs_hash" ] && state_flag_count=$(( state_flag_count + 1 ))

if [ "$state_flag_count" -gt 1 ]; then
	echo "Error: no more than 1 of --tag, --context, --darcs-hash flags can be set. $state_flag_count were set." >&2
	echo >&2
	usage
	exit 1
elif [ -n "$context" ]; then
	if [ ! -s "$context" ]; then
		echo "Error: context file must be readable & non-empty @ “$context”" >&2
		echo >&2
		usage
		exit 1
	else
		context="$(realpath "$context")"
	fi
fi

weak_hash=
hash=
hash_algo="${NIX_HASH_ALGO:-"sha256"}"
hash_format="${hashFormat:-"--base32"}"
final_path=
final_context=

# If the hash was given, a file with that hash may already be in the
# store.
if [ -n "$exp_hash" ]; then
	final_path=$(nix-store --print-fixed-path --recursive "$hash_algo" "$exp_hash" "$name")
	if ! nix-store --check-validity "$final_path" 2> /dev/null; then
		final_path=""
	fi
	hash="$exp_hash"
fi

# If we don’t know the hash or a path with that hash doesn’t exist,
# download the file and add it to the store.
if [ -z "$final_path" ]; then
	tmp_clone="$(realpath ${quiet:+--quiet} "$(mktemp ${quiet:+--quiet} -d --tmpdir darcs-clone-tmp-XXXXXXXX)")"
	trap "rm -rf \"$tmp_clone\"" EXIT

	clone_args="--lazy"
	if [ -n "$quiet" ]; then
		clone_args="$clone_args --quiet"
	fi
	if [ -n "$tag" ]; then
		clone_args="$clone_args --tag=$tag"
	elif [ -n "$context" ]; then
		clone_args="$clone_args --context=$context"
	elif [ -n "$darcs_hash" ]; then
		clone_args="$clone_args --to-hash=$darcs_hash"
	fi

	cd "$tmp_clone"
	# Do not print Darcs progress to stdout (else stdout isn’t parsable JSON)
	if [ -t 1 ]; then
		darcs clone $clone_args "$repository" "$name" >/dev/tty
	else
		darcs clone $clone_args "$repository" "$name" >/dev/null
	fi
	cd "$tmp_clone/$name"
	# Will put the current Darcs context into the store.
	new_context="$tmp_clone/${name}-context.txt"
	darcs log --context > "$new_context"
	final_context="$(nix-store --add-fixed "$hash_algo" "$new_context")"
	# Darcs has a weak hash using the XOR of the patch hashes which is useful
	# for other scripts
	# https://darcs.net/Internals/Hashes
	weak_hash="$(darcs show repo | grep '^ *Weak Hash:' | cut -d: -f2- | tr -d "[:space:]")"
	cd - >/dev/null
	rm -rf "$tmp_clone/$name/_darcs"

	hash="$(nix-hash --type "$hash_algo" "$hash_format" "$tmp_clone/$name")"
	final_path=$(nix-store --add-fixed --recursive "$hash_algo" "$tmp_clone/$name")

	if [ -n "$exp_hash" ] && [ "$exp_hash" != "$hash" ]; then
		echo "Hash mismatch for “$repository”" >&2
		echo "Expected: $exp_hash" >&2
		echo "Got:      $hash" >&2
		exit 1
	fi
fi

json_escape() {
	printf '%s' "$1" | jq -Rs .
}

cat <<EOF
{
	"repository": $(json_escape "$repository"),
EOF
if [ -n "$tag" ]; then cat <<EOF
	"tag": "$(json_escape "$tag")",
EOF
elif [ -n "$darcs_hash" ]; then cat <<EOF
	"darcs-hash": $(json_escape "$darcs_hash"),
EOF
fi; if [ -n "$weak_hash" ]; then cat <<EOF
	"weak-hash": $(json_escape "$weak_hash"),
EOF
fi; if [ -s "$final_context" ]; then cat <<EOF
	"context": $(json_escape "$final_context"),
EOF
fi; cat <<EOF
	"path": "$final_path",
	$(json_escape "$hash_algo"): $(json_escape "$hash"),
	"hash": "$(nix-hash --to-sri --type "$hash_algo" "$hash")"
}
EOF
# vim: noet ci pi sts=0
