#!/usr/bin/env bash

# Copyright (C) 2012-2013 Nicolás Reynolds <fauno@parabola.nu>
# Copyright (C) 2013 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 Affero 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/>.

source libremessages
source $(librelib conf.sh)

load_files libretools
check_vars libretools FULLBUILDCMD || exit 1
# The following variables are actually optional
#check_vars libretools HOOKPREBUILD HOOKLOCALRELEASE || exit 1
load_files makepkg

term_title "${0##*/}"

# End inmediately but print an useful message
trap_exit() {
	term_title "${0##*/}: $(_ 'Error!')"
	error "%s: %s (leftovers on %s" "${0##*/}" "$*" "${BUILDDIR}"
	exit 1
}

# Trap signals from makepkg
set -E
trap 'trap_exit "TERM signal caught. Exiting..."' TERM HUP QUIT
trap 'trap_exit "Aborted by user! Exiting..."' INT
trap 'trap_exit "An unknown error has occurred. Exiting..."' ERR

# Add line to build order cache in CSV format
# *must* be run from the PKGBUILD path
# status;depth;pkgbase;[epoch:]pkgver-pkgrel;path;repo
# $1 status
# $2 pkgname
add_order() {
	echo "${1};${DEPTH};${2:-${pkgbase}};$(get_full_version ${2});${PWD};$(guess_repo "$PWD")" >> "${BUILDORDER}"
	${VERBOSE} && msg2 "%${DEPTH}s${2:-${pkgbase}} [${1}]" || true
}

# Bury a package deeper in the tree
# $1 pkgbase
# $2 nextdepth
bury() {
	# Bury only if we are going to build the dep
	# Get it's current depth and dir name
	local current_depth=$(grep "build;[0-9]\+;${1};" "${BUILDORDER}" | cut -d ';' -f 2)
	local current_name="$(printf "%03d" ${current_depth})_${1}"

	# If there's a depth or the package is not the root of the build tree (which
	# can lead to funny chicken-and-egg problems), update the depth to the current
	# package next-depth and rename the dir too
	if [[ -z "${current_depth}" ]]; then return; fi
	if [[ -z "${current_name}" ]]; then return; fi
	if [[ ${current_depth} -eq 0 ]]; then return; fi
	if [[ ${current_depth} -ge $2 ]]; then return; fi

	${VERBOSE} && msg "Burying ${1} from ${current_depth} to ${2}"

	{
		sed -i "s|^\(build;\)\([0-9]\+\)\(;${1};.*\)$|\1${2}\3|" "${BUILDORDER}" && \
			mv "${BUILDDIR}/${current_name}" "${BUILDDIR}/$(printf "%03d" ${2})_${1}"
	} || return 1
}

# Guess the repo from the pkgbase path
# $1 path, pwd or toru-where
guess_repo() {
	basename "$(dirname "${1}")"
}

if [[ ! -f PKGBUILD ]]; then
	error "Missing PKGBUILD (%s)" "$PWD"
	exit 1
fi

if ! load_PKGBUILD ; then
	error "Can't source PKGBUILD"
	exit 1
fi

# Save resources
unset pkgdesc arch license groups backup install md5sums sha1sums \
	sha256sums source options >/dev/null 2>&1

unset build package >/dev/null 2>&1

for _pkg in "${pkgname[@]}"; do
	unset package_${_pkg} >/dev/null 2>&1 || true
done
##

# Get useful values
pkgbase="${pkgbase:-${pkgname[0]}}"

# Get or set the work dir
BUILDDIR="${1:-$(mktemp --tmpdir -d ${pkgbase}-treepkg.XXXXXXXXXX)}"
BUILDORDER="${BUILDDIR}/BUILDORDER"
DEPTH=${2:-0}
NEXTDEPTH=$((${DEPTH} + 1))
# This can be set as env vars (ie: $ V=false B=false treepkg)
# TODO Turn into flags?
VERBOSE=${V:-true}
BUILD=${B:-true}
CLEANUP=${C:-true}
# Skip BUILDORDER creation and build anything on BUILDDIR
BUILDNOW=${N:-false}
IGNORE=(${I})

if [[ ! -z $1 ]] && [[ $DEPTH -eq 0 ]]; then
	BUILDNOW=true
fi

if ! ${BUILDNOW}; then
	# ensure it exists
	touch "${BUILDORDER}"

	# If this package is already built quit silently
	for _pkg in "${pkgname[@]}"; do

		if in_array "${_pkg}" "${IGNORE[@]}"; then
			add_order "ignore"
			exit 0
		fi

		if is_built "${_pkg}" "$(get_full_version ${_pkg})"; then
			add_order "ignore"
			exit 0
		fi
	done

	# Ignore if already in build order
	egrep -q ";${pkgbase};" "${BUILDORDER}" && exit 0

	# Add pkgbase to build order
	add_order "build"

	# Copy the directory to the build dir
	# TODO run makepkg --source to avoid moving garbage around?
	cp -r "${PWD}" "${BUILDDIR}/$(printf "%03d" ${DEPTH})_${pkgbase}"

	# Cleanup dep versioning
	deps=($(echo "${depends[@]} ${makedepends[@]}" | \
		sed "s/[=<>]\+[^ ]\+//g" | \
		tr ' ' "\n" | \
		sort -u))

	# NOTE: getting depends from package() is a PITA
	for _dep in "${deps[@]}"; do
		# Move deps deeper in the tree if
		# pkgbase - dep1
		#         \ dep2 - dep1
		# dep1 should be depth + 1
		egrep -q ";${_dep};" "${BUILDORDER}" && bury "${_dep}" ${NEXTDEPTH}

		# Ask toru where's a PKGBUILD
		depdir="$(toru-where ${_dep})"

		if [[ -z ${depdir} ]] || [[ ! -d ${depdir} ]]; then
			# We specify the pkgname because we can't source the dep PKGBUILD
			# Normally 'any' packages are missing from our work ABS
			add_order "missing" "${_dep}"
			continue
		fi

		pushd "${depdir}" >/dev/null

		# Run itself over dependencies
		$0 "${BUILDDIR}" ${NEXTDEPTH}

	done
	# End BUILD now
fi

# Only build at the end
if [[ ${DEPTH} -eq 0 ]]; then
	${VERBOSE} && msg "Starting build" || true

	if ${BUILD}; then
		${VERBOSE} && msg "Build tree stored in %s" "${BUILDORDER}" || true

		# Build everything sorting the build dir
		# The reverse order ensures we start by the deepest packages
		for _pkg in $(ls -r "${BUILDDIR}"); do
			# Ignore if there's no PKGBUILD
			if [[ ! -f "${BUILDDIR}/${_pkg}/PKGBUILD" ]]; then continue; fi
			# Skip if already built (faster than calling is_build again)
			if [[ -f "${BUILDDIR}/${_pkg}/built_ok" ]]; then continue; fi

			${VERBOSE} && msg "Building %s" "${_pkg/_/ }" || true

			# Remove leading zeros and space if any
			term_title "$(echo ${_pkg/_/ } | sed "s/^0\+ \?//")"

			# Run build command
			pushd "${BUILDDIR}/${_pkg}" >/dev/null
			sudo -E pacman -Syu --noconfirm

			if [[ -n "${HOOKPREBUILD:-}" ]]; then
				${HOOKPREBUILD}
			fi

			${FULLBUILDCMD}

			# Run local release hook with $1 = $repo
			if [[ -n "${HOOKLOCALRELEASE:-}" ]]; then
				${HOOKLOCALRELEASE} $(egrep ";${_pkg#*_};" "${BUILDORDER}" | cut -d';' -f6)
			fi

			touch built_ok
			popd >/dev/null
		done

	else
		# Just print the working dir
		${VERBOSE} || echo "${BUILDORDER}" || true
	fi

	if ${CLEANUP} ; then
		msg2 "Removing %s" "${BUILDDIR}"
		rm -rf "${BUILDDIR}"
	fi

fi

term_title "$(_ Done)"
exit $?