#!/bin/bash

#
# A script to rebuild packages as needed on gentoo linux.
# 
# Copyright (C) 2011 Emil Karlson <jekarlson@gmail.com>
# Freely distributable.
#

rebuild_all_majorversion=2
rebuild_all_minorversion=0
rebuild_all_patchversion="2.1"

if [[ -z "$rebuild_all_patchversion" ]]; then
	rebuild_all_version="${rebuild_all_majorversion}.${rebuild_all_majorversion}"
else
	rebuild_all_version="${rebuild_all_majorversion}.${rebuild_all_majorversion}.${rebuild_all_patchversion}"
fi

unset processes modules_files

#
# functions
#

isMemberOf() {
	local i target
	target="$1"
	shift 1
	while (($#>0)); do
		[[ "$target" == "$1" ]] && return 0
		shift 1;
	done;
	return 1
}

printHelpAndExit(){
	[[ -n "$*" ]] && echo $* && echo
cat << __EOF__
rebuild-all version ${rebuild_all_version}

usage: rebuild-all [options]
	-a action                Set the action or package manager.
	-A                       List available actions.
	-m module1:module2...    Set modules to check for broken packages, a colon separated list.
	-M                       List available modules.
	-V exact|best|slot       Set, which version of the broken package should be built.
	-p                       Use pretend option for package managers.

	-d		 				Debug using set -xv.
	-v                      Print version.
	-h                      Print help.
__EOF__

	exit
}

cleanup(){
	[[ -n "${processes[*]}" ]] && kill ${processes[*]}
	for ((i=0;i<${#modules_files[*]};++i)); do
		rm -f "${modules_files[${i}]}" "${modules_errorfiles[${i}]}"
	done
	unset modules_files processes
}

fail () {
	[[ -n "$*" ]] && echo $*
	cleanup
	exit 1
}

get_exact_version () {
	local i
	for i in $*; do
		echo $(echo $i | sed 's/^/=/')
	done
}

strip_version () {
	local i
	for i in $*; do
		echo $(echo $i | sed 's/-[0-9]\+\(\.[0-9]\+\)*[a-z]\?\(_\(\(alpha\)\|\(beta\)\|\(pre\)\|\(rc\)\|\(p\)\)[0-9]*\)*\(-r[0-9]*\)\?$//')
	done
}

get_slot () {
	local i
	for i in $*; do
			echo "$(strip_version $i)":$(sed 's#/.*$##' "/var/db/pkg/${i}/SLOT")
	done
}

run_for_all_modules () {
	cleanup
	local i action="$1" error_template="$2"

	for ((i=0;i<${#modules[*]};i++)) do
		modules_files[$i]="$(mktemp)"
		modules_errorfiles[$i]="$(mktemp)"
		"${modules_path}/${modules[$i]}" "$action" "${modules_files[$i]}" "${modules_errorfiles[$i]}" &
		processes=(${processes} $?)
	done

	wait
	unset processes

	#check for errors
	for ((i=0;i<${#modules[*]};i++)) do
		[[ -n "$(<${modules_errorfiles[$i]})" ]] && printf  "module ${modules[$i]}: ${error_template}\n" "$(<${modules_errorfiles[$i]})" && fail
	done
}

map_modules () {
	local i
	
	for ((i=0;i<${#modules[*]};i++)) do
		[[ -n "$(<${modules_files[$i]})" ]] && $* "${modules[$i]}" "$(<${modules_files[$i]})"
	done
}

check_api () {
	local module="$1" version=$(echo "$2" | sed 's/\..*$//') minor_version=$(echo "$2" | sed 's/^.*\.//')
	(( "$rebuild_all_majorversion" == "$version" )) || fail "Module ${module} uses incorrect api."
	(( "$rebuild_all_minorversion" >= "$minor_version" )) || fail "Module ${module}, api not yet supported."
}

check_required_actions () {
	local i module="$1"
	shift 1
	for i in ${required_actions[*]}; do
		isMemberOf "$i" $@ || fail "Module ${module} does not support action ${i}."
	done
}

strip_unwanted_packages () {
	for i in $*; do
		grep -qr $(strip_version $i) "$packages_exclude_dir" || echo $i
	done
}

trap "cleanup; exit" SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM

#
# settings
#

required_actions=(get_api list_actions)

#default modules, all present
modules_path="/etc/rebuild-all/modules/"
action_path="/etc/rebuild-all/actions/"
packages_exclude_dir="/etc/rebuild-all/exclude_packages/"

#TODO
action="portage"

#exact, best, slot TODO
rebuild_version="slot"

pretend=no

[[ -f /etc/rebuild-all/rebuild-allrc ]] && . /etc/rebuild-all/rebuild-allrc

available_rebuild_version=(exact best slot)
available_modules=($(ls "${modules_path}"))
available_action=($(ls "${action_path}"))
[[ -z "$modules" ]] && modules=(${available_modules[*]})

while getopts "Aa:Mm:V:pdvh" optionName; do
	case "$optionName" in
			a) action="$OPTARG";;
			A) echo "${available_action[*]}"; exit 0;;
			m) modules=($(echo "$OPTARG" | sed 's/:/ /g'));;
			M) echo "${available_modules[*]}"; exit 0;;
			V) rebuild_version="$OPTARG";;
			p) pretend=yes;;
#default stuff
			d) set -xv;;
			v) echo rebuild_all version "$reabuild_all_version" && exit;;
			h) printHelpAndExit;;
			[?]) printHelpAndExit bad_option "$badOptionHelp";;
	esac
done

#check module availability etc.
for i in ${modules[*]}; do
	isMemberOf $i ${available_modules[*]} || printHelpAndExit unknown_module $i
done
	
isMemberOf $rebuild_version ${available_rebuild_version[*]} ||
	printHelpAndExit unknown_rebuild_version $rebuild_version

isMemberOf $action ${available_action[*]} ||
	printHelpAndExit unknown_action $action

#
# functionality
#

. "${action_path}/${action}"

#boring checks
[[ 0 == ${#modules[*]} ]] && exit 0
run_for_all_modules list_actions "module internal error, list_actions failed with: %s"
map_modules check_required_actions
run_for_all_modules get_api "module internal error, get_api failed with: %s"
map_modules check_api


run_for_all_modules find_broken_packages "find_broken_package failed with: %s"
packagesToRebuild=$([[ -n "${modules_files[*]}" ]] && cat ${modules_files[*]})
cleanup
test -z "${packagesToRebuild[*]}" && exit 0

if [[ -d "$packages_exclude_dir" ]]; then
	packagesToRebuild=$(strip_unwanted_packages ${packagesToRebuild[*]})
fi

case "$rebuild_version" in
	exact) rebuild_targets=($(get_exact_version ${packagesToRebuild[*]}));;
	slot) rebuild_targets=($(get_slot ${packagesToRebuild[*]}));;
	best) rebuild_targets=($(strip_version ${packagesToRebuild[*]}));;
	*) fail internal error;;
esac

if [[ yes == "$pretend" ]]; then
	pretend_rebuild ${rebuild_targets[*]}
else
	rebuild ${rebuild_targets[*]}
fi

