#!/usr/bin/env bash
name="Libredbdiff"

# Copyright (C) 2014 Esteban Carnevale <alfplayer@mailoo.org>
# Copyright (C) 2014 Luke Shumaker <lukeshu@sbcglobal.net>
#
# License: GNU GPLv3+
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

baseconfpath="/etc/libredbdiff"
basedbpath="/var/lib/libredbdiff"

conffile="$baseconfpath/pacman.conf.parabola"
conffilearch="$baseconfpath/pacman.conf.archlinux"

dbpath="$basedbpath/pacman.parabola"
dbpatharch="$basedbpath/pacman.archlinux"

mirrorlist="$baseconfpath/mirrorlist.parabola"
mirrorlistarch="$baseconfpath/mirrorlist.archlinux"

mirror='http://repo.parabola.nu/$repo/os/$arch'
mirrorarch='http://mirrors.kernel.org/archlinux/$repo/os/$arch'

repos="libre pcr libre-multilib nonprism"

field_pkgname_parabola=30
field_pkgname_arch=30


. libremessages

cmd="${0##*/}"

arch_packages_tmp="/tmp/${cmd}.arch-packages"
parabola_packages_tmp="/tmp/${cmd}.parabola-packages"

field_pkgname_total="$((${field_pkgname_parabola} + ${field_pkgname_arch}))"
printf_format="%s  %-${field_pkgname_parabola}s%-${field_pkgname_arch}s   %s | %s\n"
printf_format_noarch="%s  %-${field_pkgname_total}s   %s\n"

downloadfile() {
	local outfile=$1
	local url=$2
	local mesg=("${@:3}")
	if [[ ! -e $outfile ]] ; then
		msg "${mesg[@]}"
		if wget -q "$url" -O "$outfile"; then
			return 255
		else
			die "Failed to download %q.  Exiting." "$outfile"
		fi
	elif [[ $init ]]; then
		warning "%q already exists.  Skipping." "$outfile"
	fi
}

enablerepo() {
	repo="$1"
	conffile_arg="$2"
	msg2 "Enabling repo %q in %q" "$repo" "${conffile_arg}"
	sed -i "s/\#\[$repo\]/[$repo]/" "${conffile_arg}"
	sed -i "\/\[$repo\]/,+1 s/#Include/Include/" "${conffile_arg}"
}

createdir() {
	local dir=$1
	if [[ ! -e $dir ]] ; then
		msg "Creating directory %q" "$dir"
		mkdir -- "$1" || die "Failed to create directory %q.  Exiting." "$dir"
	elif [[ $init ]]; then
		warning "%q already exists.  Skipping." "$dir"
	fi
}

setmirror() {
	local distro="$1"
	local mirror="$2"
	local mirrorlist="$3"
	if [[ $init ]] && [[ $mirror ]]; then
		mirrorescaped="${mirror//./\\.}"
		mirrorescaped="${mirrorescaped//\$/\\$}"
		msg2 "Setting %s as the only enabled %s mirror." "${mirror}" "${distro}"
		sed -i 's|^#\(Server = '"${mirrorescaped}"'\)$|\1|' "${mirrorlist}"
	fi
}

filenotfound() {
	local file=$1
	if [[ ! -r $1 ]]; then
		error "Could not read %q." "$file"
		die "Nothing done.  It may be necessary to run %q without \
arguments as root to initialize %s." "$cmd" "$name"
	fi
}

initialize() {
	createdir "$baseconfpath"
	createdir "$basedbpath"

	downloadfile \
		"${conffile}" \
		"https://projects.parabola.nu/abslibre.git/plain/libre/pacman/pacman.conf.x86_64" \
		"Downloading Parabola %q" \
		pacman.conf
	if [[ $? == 255 ]] ; then
		msg2 "Setting DBPath in %q" "${conffile}"
		sed -i "s|^#DBPath .*|DBPath = ${dbpath}|" "${conffile}"
		enablerepo nonprism "${conffile}"
		enablerepo pcr "${conffile}"
		enablerepo libre-multilib "${conffile}"
		enablerepo multilib "${conffile}"
	fi

	downloadfile \
		"${conffilearch}" \
		"https://projects.archlinux.org/svntogit/packages.git/plain/pacman/trunk/pacman.conf.x86_64" \
		"Downloading Arch %q" \
		pacman.conf
	if [[ $? == 255 ]] ; then
		msg2 "Setting DBPath in %q" "${conffilearch}"
		sed -i "s|^#DBPath .*|DBPath = ${dbpatharch}|" "${conffilearch}"
		msg2 "Setting Arch mirrorlist file in %q" "${conffilearch}"
		sed -i "s|/etc/pacman\.d/mirrorlist$|$baseconfpath/mirrorlist.archlinux|" \
			"${conffilearch}"
		enablerepo multilib "${conffilearch}"
	fi

	downloadfile \
		"${mirrorlist}" \
		"https://repo.parabola.nu/mirrorlist.txt" \
		"Downloading Parabola %q" \
		mirrorlist
	if [[ $? == 255 ]] ; then
		sed -i 's|^Server|#Server|' "${mirrorlist}"
		setmirror "Parabola" "$mirror" "$mirrorlist"
	fi

	downloadfile \
		"${mirrorlistarch}" \
		"https://projects.archlinux.org/svntogit/packages.git/plain/pacman-mirrorlist/trunk/mirrorlist" \
		"Downloading Arch %q" \
		mirrorlist
	if [[ $? == 255 ]] ; then
		setmirror "Arch" "$mirrorarch" "$mirrorlistarch"
	fi
}

repo_test() {
	for repo in ${repos} ; do
		if [[ $repo == $1 ]] ; then
			found=1
			return 0
		fi
	done
	if [[ $found != 1 ]] ; then
		die "The specified Parabola repo \"%s\" cannot be compared.  It's not in the list of repos in the configuration variable \"repos\"." "$1"
	fi
}

compare_pkgs() {
	local cmp
	if [[ ${verarch[$pkgname]} ]] ; then
		cmp=$(vercmp "${ver[$pkgname]}" "${verarch[$pkgname]}")
		if [[ $cmp -lt 0 ]]; then
			printf "${printf_format}" \
				'=' \
				"${pkgname}" \
				"" \
				"${ver[$pkgname]}" \
				"${verarch[$pkgname]}"
		fi
	elif [[ ${provides[$pkgname]} ]]; then
		for provide in "${provides[$pkgname]}"; do
			if [[ ${verarch["$provide"]} ]]; then
				cmp=$(vercmp "${ver[$pkgname]}" "${verarch[$provide]}")
				if [[ $cmp -lt 0 ]]; then
					printf "${printf_format}" \
						'p' \
						"${pkgname}" \
						"${provide}" \
						"${ver[$pkgname]}" \
						"${verarch[$provide]}"
				fi
			fi
		done
	else
		printf "${printf_format_noarch}" \
			'o' \
			"${pkgname}" \
			"${ver[$pkgname]}"
	fi
}

print_cmp() {
	local repo="$1"
	awk -F/ -v repo="$repo" \
		'$1 == repo {print $2}' \
		${parabola_packages_tmp} | \
			while read -a line ; do
				ver["${line[0]}"]="${line[1]}"
				provides[${line[0]}]="${line[@]:2}"
				pkgname=${line[0]}
				compare_pkgs
			done
}

usage() {
	print "Usage: %q [-n|-h]" "$cmd"
	print 'Show packages that need to be updated from Arch repositories.'
	echo
	prose "Compares packages in Parabola repositories.  Packages from
	       all configured Parabola repositories are compared.  A Parabola
	       repository name can be specified as argument to compare only
	       packages in that repository."
	echo
	prose "The default mode of operation is to download/update all necessary
	       files for comparison, but not compare them.  Specify the \`-n\`
	       flag to not download anything, but to compare already downloaded
	       files."
	echo
	print 'Options:'
	flag '-n' "Don't update anything, just compare already downloaded files."
	flag '-h' 'Show this message'
	echo
	print "Output format:"
	print "type_character  parabola_pkgname  (arch_pkgname)   parabola_pkgver - (arch_pkgver)"
	echo
	print "Type characters:"
	flag '=' "Arch package with the same pkgname and greater pkgver was found"
	flag 'p' "Arch package with pkgname equal to a provide and greater
	          pkgver was found In this case arch_pkgname is a provide of
	          parabola_pkgname"
	flag 'o' "No Arch package with the same pkgname or with pkgname equal to
	          a provide was found"
}

main() {
	local UPDATE=1
	local arg
	local repo_arg

	for arg in "$@"; do
		case "$arg" in
			-n|--noupdate)
				UPDATE=0
				;;
			-h|--help)
				usage
				return 0
				;;
			*)
				repo_test "$arg"
				repo_arg="$arg"
				break
				;;
		esac
	done

	if (( $UPDATE )) ; then
		if [[ $EUID != 0 ]]; then
			die "To initialize %s or update %s pacman databases, %s must be run as root (without arguments).  Nothing done." \
				"$name" \
				"$name" \
				"$cmd"
		fi

		if ! [[ -e "${conffile}" && \
			-e "${conffilearch}" && \
			-e "${mirrorlist}" && \
			-e "${mirrorlist}" ]]; then
			warning "At least one %s configuration file is missing." \
				"${name}"
			msg "Downloading and preparing missing configuration files."
			init=1
		fi

		createdir "$baseconfpath"
		createdir "$basedbpath"

		initialize

		if ! [[ -d "$dbpath" && \
			-d "$dbpatharch" ]]; then
			warning "At least one %s pacman DB directory is missing.  Synchronizing %s DB files." \
				"${name}" "${name}"
		fi

		createdir "$dbpath"
		msg "Synchronizing %s pacman databases for Parabola" "$name"
		pacman --config "${conffile}" -Sy ||
			die "Failed to synchronize pacman database for Parabola.  Exiting."

		createdir "$dbpatharch"
		msg "Synchronizing %s pacman databases for Arch" "$name"
		pacman --config "${conffilearch}" -b "${dbpatharch}" -Sy ||
			die "Failed to synchronize pacman database for Arch.  Exiting."

		msg "%s pacman databases are updated.  %s is ready.  Run %q -n to print results." \
		    "$name" "$name" "$cmd"
		return 0
	else
		filenotfound "${dbpath}"
		filenotfound "${dbpatharch}"
		filenotfound "${conffile}"
		filenotfound "${conffilearch}"
		filenotfound "${mirrorlist}"
		filenotfound "${mirrorlistarch}"

		unset provides ver verarch
		declare -gA provides ver verarch

		if ! [[ -d "$dbpath" && \
			-d "$dbpatharch" ]]; then
			die "At least one %s pacman DB directory is missing.  To update %s pacman databases, %s must be run as root.  Nothing done." \
				"$name" \
				"$name" \
				"$cmd"
		fi

		pacman --config "${conffilearch}" -Ss | \
				grep -v '^ ' | \
				awk -F/ '{print $2}' \
			> ${arch_packages_tmp} || \
			die "pacman command to get Arch package data has failed.  Exiting."
		chmod 777 ${arch_packages_tmp}

		while read -a line; do
			verarch["${line[0]}"]="${line[1]}"
		done < ${arch_packages_tmp}

		expac --config "${conffile}" -S '%r/%n %v %S' \
			> ${parabola_packages_tmp} || \
			die "expac command to get Parabola package data has failed.  Exiting."
		chmod 777 ${parabola_packages_tmp}

		if [[ ${repo_arg} ]] ; then
			print_cmp "${repo_arg}"
		else
			for repo in ${repos} ; do
				echo "[$repo]"
				print_cmp "$repo"
			done
		fi
	fi
}

main "$@"