#!/bin/busybox ash
# shellcheck shell=ash
export LC_ALL=C

# List of $repository/$packages that should not be used
deprecated_packages="
main/db
$(echo "$CUSTOM_DEPRECATED_PACKAGES" | tr ' ' '\n')
"

# Define find_repo using 'apk' which is slow and costly when the
# user wants it.
#
# Times go up from 0.02 for community/gcc6/APKBUILD to 3.26
if [ "$EXPENSIVE_FIND_REPO" ]; then

find_repo() {
	[ -z "$1" ] || [ -z "$2" ] && return 0

	local pkgname="$1" 
	# Repo the package we are linting currently is. We want it
	# for avoiding checks on repos we don't want
	local targetrepo="$2"

	# Unmaintained is the top of the ladder, it can depend on any
	# of the steps below
	if [ "$targetrepo" = "unmaintained" ]; then
		return 0
	fi

	# Search for the --origin of a package, matching the given name --exactly
	pkgname="$(apk search --origin --exact "$pkgname" \
			 | rev \
			 | cut -d -f3- 2>/dev/null \
			 | rev)"

	# If we found nothing just ignore it
	[ -z "$pkgname" ] && return 0

	check_in_repo() { test -f "$1"/"$2"/APKBUILD && echo "$1" ; }

	case "$targetrepo" in
		testing) 
			check_in_repo unmaintained "$pkgname"
			;;
		community)
			check_in_repo unmaintained "$pkgname"
			check_in_repo testing "$pkgname"
			;;
		main)
			check_in_repo unmaintained "$pkgname"
			check_in_repo testing "$pkgname"
			check_in_repo community "$pkgname"
			;;
	esac
}
else

# Finds from which repo a package comes from
# it can return multiple values if it finds multiple matches
find_repo() {
	[ -z "$1" ] || [ -z "$2" ] && return 0

	local pkgname="$1"

	# ninja was deprecated and moved to unmaintained in favour of
	# samurai, which is in main/, but most package still call ninja
	# correctly because samurai provides it, but aports-lint is not
	# smart enough to deal with replaces= and provides=, we just check
	# directories.
	#
	# This leads to false positives where packages that depend on ninja
	# and rightfully get samurai are considered broken because ninja is in
	# unmaintained.
	if [ "$pkgname" = ninja ]; then
		return 0
	fi

	# Repo the package we are linting currently is. We want it
	# for avoiding checks on repos we don't want
	local targetrepo="$2"

	# Unmaintained is the top of the ladder, it can depend on any
	# of the steps below
	if [ "$targetrepo" = "unmaintained" ]; then
		return 0
	fi

	# Perform some transformations that can be done easily and cheaply
	# and are common.
	#
	# This is a hack until apk has something like xpkg -m or aports adopt
	# the xbps-src symlinks
	pkgname="${pkgname%-dev}"
	pkgname="${pkgname%-doc}"
	pkgname="${pkgname%-openrc}"
	pkgname="${pkgname%-bash-completion}"
	pkgname="${pkgname%-zsh-completion}"
	pkgname="${pkgname%-fish-completion}"
	# Disabled because it can cause conflicts with -dev packages, there is glade and libglade
	# which are separate packages but end up causing false-postiives
	# pkgname="${pkgname#lib}"
	pkgname="${pkgname%-static}"
	pkgname="${pkgname%-lang}"

	check_in_repo() { test -f "$1"/"$2"/APKBUILD && echo "$1" ; }

	case "$targetrepo" in
		testing) 
			check_in_repo unmaintained "$pkgname"
			;;
		community)
			check_in_repo unmaintained "$pkgname"
			check_in_repo testing "$pkgname"
			;;
		main)
			check_in_repo unmaintained "$pkgname"
			check_in_repo testing "$pkgname"
			check_in_repo community "$pkgname"
			;;
	esac
}
fi

find_dupe() {
	local pkgname="$1" repo="$2" r=

	check_in_repo() { test -f "$1"/"$2"/APKBUILD && echo "$1" ; }

	for r in unmaintained testing community main; do
		[ "$r" = "$repo" ] && continue
		check_in_repo "$r" "$pkgname"
	done
}

upper_repo_depends() {
	[ "$SKIP_UPPER_REPO_DEPENDS" ] && return 0
	[ "$SKIP_AL16" ] && return 0
	printf "%s\n" "$depends" | tr " " "\n" | tr -d "\t" | sort -u | while read -r pkg; do
		for p in $(find_repo "$pkg" "$_repo"); do
			printf "SC:[AL17]:%s::depends '%s' is in upper repo '%s'\n" \
				"$apkbuild" "$pkg" "$p"
		done
	done
}

duplicate_depends() {
	[ "$SKIP_DUPLICATE_DEPENDS" ] && return 0
	[ "$SKIP_AL17" ] && return 0
	printf "%s\n" "$depends" | tr " " "\n" | tr -d "\t" | sort | uniq -d | while read -r dup; do
		[ -z "$dup" ] && continue
		printf "MC:[AL17]:%s::duplicate '%s' in depends\n" "$apkbuild" "$dup"
	done
}

upper_repo_makedepends() {
	[ "$SKIP_UPPER_REPO_MAKEDEPENDS" ] && return 0
	[ "$SKIP_AL18" ] && return 0
	printf "%s\n" "$makedepends" | tr " " "\n" | tr -d "\t" | sort -u | while read -r pkg; do
		for p in $(find_repo "$pkg" "$_repo"); do
			printf "SC:[AL18]:%s::makedepends '%s' is in upper repo '%s'\n" \
				"$apkbuild" "$pkg" "$p"
		done
	done
}

duplicate_makedepends() {
	[ "$SKIP_DUPLICATE_MAKEDEPENDS" ] && return 0
	[ "$SKIP_AL19" ] && return 0
	printf "%s\n" "$makedepends" | tr " " "\n" | tr -d "\t" | sort | uniq -d | while read -r dup; do
		[ -z "$dup" ] && continue
		printf "MC:[AL19]:%s::duplicate '%s' in makedepends\n" "$apkbuild" "$dup"
	done
}

upper_repo_checkdepends() {
	[ "$SKIP_UPPER_REPO_CHECKDEPENDS" ] && return 0
	[ "$SKIP_AL20" ] && return 0
	printf "%s\n" "$checkdepends" | tr " " "\n" | tr -d "\t" | sort -u | while read -r pkg; do
		for p in $(find_repo "$pkg" "$_repo"); do
			printf "SC:[AL20]:%s::checkdepends '%s' is in upper repo '%s'\n" \
				"$apkbuild" "$pkg" "$p"
		done
	done
}

duplicate_checkdepends() {
	[ "$SKIP_DUPLICATE_CHECKDEPENDS" ] && return 0
	[ "$SKIP_AL21" ] && return 0
	printf "%s\n" "$checkdepends" | tr " " "\n" | tr -d "\t" | sort | uniq -d | while read -r dup; do
		[ -z "$dup" ] && continue
		printf "MC:[AL21]:%s::duplicate '%s' in checkdepends\n" "$apkbuild" "$dup"
	done
}

duplicate_package() {
	[ "$SKIP_DUPLICATE_PACKAGE" ] && return 0
	[ "$SKIP_AL22" ] && return 0
	for _r in $(find_dupe "$pkgname" "$_repo"); do
		printf "SC:[AL22]:%s::package is already present in %s\n" "$apkbuild" "$_r"
	done
}

pkgname_dirname_mismatch() {
	[ "$SKIP_PKGNAME_DIRNAME_MISMATCH" ] && return 0
	[ "$SKIP_AL23" ] && return 0
	local _dirname=
	case "${apkbuild%%/*}" in
		main|community|testing|unmaintained|non-free)
			_dirname="${apkbuild%/*}"
			_dirname="${_dirname##*/}"
			;;
		*) return 0 ;;
	esac
	if [ "$pkgname" != "$_dirname" ]; then
		printf "IC:[AL23]:%s::pkgname is '%s' but is in directory '%s'\n" \
			"$apkbuild" "$pkgname" "$_dirname"
	fi
}

depends_makedepends_checkdepends_overlap() {
	[ "$SKIP_DEPENDS_MAKEDEPENDS_CHECKDEPENDS_OVERLAP" ] && return 0
	[ "$SKIP_AL24" ] && return 0
	local _mkdeps _ckdeps d
	# aports rules guarantee there are no aports that have space
	# or unfunny shell characters in their name so we can assume
	# they are not quoted here so printf is simpler
	# shellcheck disable=SC2086
	_mkdeps="$(printf "%s\\n" $makedepends | sort -u)"
	# shellcheck disable=SC2086
	_ckdeps="$(printf "%s\\n" $checkdepends | sort -u)"
	[ -z "$_mkdeps" ] && [ -z "$_ckdeps" ] && return 0
	for d in $depends; do
		if printf "%s\\n" "$_mkdeps" | grep -q -x "$d"; then
			printf "IC:[AL24]:%s::dependency '%s' is in depends and makedepends\n" "$apkbuild" "$d"
		fi

		# Don't check against checkdepends if it is empty
		[ -z "$checkdepends" ] && continue
		if printf "%s\\n" "$_ckdeps" | grep -q -x "$d"; then
			printf "IC:[AL24]:%s::dependency '%s' is in depends and checkdepends\n" "$apkbuild" "$d"
		fi
	done

	# Don't check against checkdepends if it is empty, no need to check for
	# makedepends because the loop just won't run if it is empty.
	[ -z "$checkdepends" ] && return 0
	for d in $makedepends; do
		if printf "%s\\n" "$_ckdeps" | grep -q -x "$d"; then
			printf "IC:[AL24]:%s::dependency '%s' is in makedepends and checkdepends\n" "$apkbuild" "$d"
		fi
	done
}

deprecated_packages() {
	[ "$SKIP_DEPRECATED_PACKAGES" ] && return 0
	[ "$SKIP_AL58" ] && return 0
	local _depends _makedepends _checkdepends

	# shellcheck disable=SC2086
	_depends="$(printf "%s\\n" $depends | sort -u)"
	# shellcheck disable=SC2086
	_makedepends="$(printf "%s\\n" $makedepends | sort -u)"
	# shellcheck disable=SC2086
	_checkdepends="$(printf "%s\\n" $checkdepends | sort -u)"
	
	for val in $deprecated_packages; do
		local _apkbuild
		if [ -f "$PWD"/$val/APKBUILD ]; then
			_apkbuild="$PWD"/$val/APKBUILD
		elif [ -f "$PWD"../$val/APKBUILD ]; then
			_apkbuild="$PWD"/$val/APKBUILD
		elif [ -f "$PWD"/../../$val/APKBUILD ]; then
			_apkbuild="$PWD"/../../$val/APKBUILD
		else
			echo "Failed to find APKBUILD for '$val'" ;
			continue
		fi
		. "$_apkbuild" || {
			echo "Failed to source APKBUILD in '$val'" ;
			continue
		}
		local invalids="$pkgname $subpackages"
		for invalid in $invalids; do
			if [ "$_depends" ]; then
				echo "$_depends" \
					| grep -q -x "$invalid" && \
					printf "SC:[AL58]:%s::Package '%s' in depends is deprecated\n" \
						"$apkbuild" "$invalid"
			fi
			if [ "$_makedepends" ]; then
				echo "$_makedepends" \
					| grep -q -x "$invalid" && \
					printf "SC:[AL58]:%s::Package '%s' in makedepends is deprecated\n" \
						"$apkbuild" "$invalid"
			fi
			if [ "$_checkdepends" ]; then
				echo "$_checkdepends" \
					| grep -q -x "$invalid" && \
					printf "SC:[AL58]:%s::Package '%s' in checkdepends is deprecated\n" \
						"$apkbuild" "$invalid"
			fi
		done
	done
}

missing_patch_description() {
	[ "$SKIP_MISSING_PATCH_DESCRIPTION" ] && return 0
	[ "$SKIP_AL56" ] && return 0
	
	local ret=0 patchname

	local i; for i in $source; do
		case ${i#*::} in
			http://*|ftp://*|https://*) ;;
			*.patch)
				if patchname="$(awk '
				function desc_check() {
				  if (NR == 1) {
				    printf("%s\n",FILENAME)
				    exit(1)
				  } else {
				    exit(0)
			          }
				}

				/^--- .*$/  { desc_check() }
				/^diff .*$/ { desc_check() }
				' "$(dirname "${apkbuild}")/$i")"; then
					continue
				else
					ret=$((ret + 1))
				fi
				patchname="${patchname##*/}"
				cat -n "$apkbuild" | sed -n '/source="/,/"/p' | grep -F -- "$patchname" | while read -r line _; do
					printf "MP:[AL56]:%s:%s:Patch file %s is missing a description\\n" \
					"$apkbuild" \
					"$line" \
					"$patchname"
				done
				;;
		esac
	done

	return $ret
}

ret=0
for apkbuild; do
	if [ -f "$apkbuild" ]; then

    case "$apkbuild" in
        /*) _repo="$apkbuild" ;;
        ./*) _repo="$PWD"/"${apkbuild#./}" ;;
        *) 
            apkbuild=./"$apkbuild"
            _repo="$PWD"/"$apkbuild"
            ;;
    esac

    # Check the directory where the directory of the APKBUILD is located
    # the alpine structure is expected to be (APORTS/REPOSITORY/PACKAGE/APKBUILD)
	_repo="${_repo%/*}"
	_repo="${_repo%/*}"
	_repo="${_repo##*/}"

	# Source apkbuild, we need some nice values
	srcdir="" . "$apkbuild" || {
		echo "Failed to source APKBUILD in '$apkbuild'" ;
		continue
	}

	if [ "$depends" ]; then
		upper_repo_depends &
		duplicate_depends &
	fi
	
	# This can be run if either 2 of the 3 are not empty
	# depends makedepends checkdepends
	depends_makedepends_checkdepends_overlap &

	if [ "$makedepends" ]; then
		upper_repo_makedepends &
		duplicate_makedepends &
	fi

	if [ "$checkdepends" ]; then
		if [ -z "$options" ] || [ "${options##*!check*}" ]; then
			upper_repo_checkdepends &
		fi
		duplicate_checkdepends &
	fi

	duplicate_package &

	pkgname_dirname_mismatch &
	
	deprecated_packages &

	missing_patch_description &

	wait

	else
	echo no such apkbuild "$apkbuild" 1>&2
	fi | sort -t: -n -k2 | grep . && ret=1
done
exit $ret
