#!/bin/sh
#
#  scratchpkg
#
#  Copyright (c) 2018 by Emmett1  (emmett1.2miligrams@gmail.com)
#
#  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 <https://www.gnu.org/licenses/>.
#

RED='\e[0;31m'
GREEN='\e[0;32m'
YELLOW='\e[0;33m'
CYAN='\e[0;36m'
PURPLE='\e[0;35m'
CRESET='\e[0m'

nocolor() {
	RED=
	GREEN=
	YELLOW=
	CYAN=
	PURPLE=
	CRESET=
}

msg() {
	printf "${GREEN}==>${CRESET} %s\n" "$1"
}

msginst() {
	printf "[${GREEN}i${CRESET}] %s\n" "$1"
}

msgmiss() {
	printf "[${YELLOW}m${CRESET}] %s\n" "$1"
}

msgnoinst() {
	printf "[-] %s\n" "$1"
}

msgerr() {
	printf "${RED}==> ERROR:${CRESET} %s\n" "$1" >&2
}

msgwarn() {
	printf "${YELLOW}==> WARNING:${CRESET} %s\n" "$1" >&2
}

needroot() {	
	if [ "$(id -u)" != 0 ]; then
		if [ "$#" -eq 0 ]; then
			needroot "This operation"
		else
			msgerr "$* need root access!"
		fi
		exit 1
	fi	
}

getportpath() {	
	for repo in $PORT_REPO; do
		if [ -f "$repo/$1/$BUILD_SCRIPT" ]; then
			dirname "$repo/$1/$BUILD_SCRIPT"
			return 0
		fi
	done
	return 1	
}

vercomp() {	
	if [ "$1" = "$2" ]; then
		return 0 # same version
	elif [ "$1" = "$(echo "$1\n$2" | sort -V | head -n1)" ]; then
		return 1 # $1 lower than $2
	else
		return 2 # $1 higher than $2
	fi
}

installed_pkg_info() {	
	if isinstalled $2; then
		grep ^$1 $PKGDB_DIR/$2/.pkginfo 2>/dev/null | cut -d " " -f3-
	fi
}

allinstalled() {	
	grep ^name "$PKGDB_DIR"/*/.pkginfo 2>/dev/null | awk '{print $3}'
}

deps_alias() {
	[ -f "$ALIAS_FILE" ] || {
		echo $@
		return
	}
	while [ "$1" ]; do
		if [ "$(grep -w ^$1 $ALIAS_FILE)" ]; then
			getalias=$(grep -w ^$1 $ALIAS_FILE | awk '{print $2}')
			[ "$getalias" ] && echo "$getalias"
		else
			echo "$1"
		fi
		shift
		unset getalias
	done
}

get_depends() {
	ppath=$(getportpath $1) || return 0
	deps=$(grep "^# depends[[:blank:]]*:" $ppath/$BUILD_SCRIPT \
	| sed 's/^# depends[[:blank:]]*:[[:blank:]]*//' \
	| tr ' ' '\n' \
	| awk '!a[$0]++' \
	| sed 's/,//')
	deps_alias $deps
}

confirm() {
	printf "$1 (Y/n) "
	read -r response
	case "$response" in
		[Nn][Oo]|[Nn]) echo "$2"; return 2 ;;
		*) : ;;
	esac
	return 0
}

checktool() {	
	if ! command -v $1 >/dev/null; then
		msgerr "'$1' not exist in your system!"
		exit 1
	fi	
}

needarg() {
	[ "$*" ] || {
		msgerr "This operation required an arguments!"
		exit 1
	}
}

isinstalled() {
	if [ -s "$PKGDB_DIR/$1/.pkginfo" ] && [ "$(grep $1 $PKGDB_DIR/$1/.pkginfo)" ]; then
		return 0
	else
		return 1
	fi
}

settermtitle() {
	printf "\033]0;$*\a"
}

scratch_integrity() {
	if [ "$1" ]; then
		cd /
			if [ -f $PKGDB_DIR/$1/.files ]; then
				cat $PKGDB_DIR/$1/.files | while read -r line; do
					if [ ! -e "$line" ]; then
						if [ -L "$line" ]; then
							printf "${YELLOW}broken symlink${CRESET} $1: /$line"
						else
							printf "${RED}file missing${CRESET} $1: /$line"
						fi
					fi
				done
			else
				echo "Package '$1' not installed."
				exit 1
			fi
		cd - >/dev/null
	else
		cd /
			for pkg in $(allinstalled); do
				cat $PKGDB_DIR/$pkg/.files | while read -r line; do
					if [ ! -e "$line" ]; then
						if [ -L "$line" ]; then
							echo "broken symlink $pkg: /$line"
						else
							echo "missing file $pkg: /$line"
						fi
					fi
				done
			done
		cd - >/dev/null
	fi
}

scratch_lock() {
	needroot "Locking package"	
	for pkg in "$@"; do
		if ! isinstalled $pkg; then
			msgerr "Package '$pkg' is not installed."
		elif [ -f $PKGDB_DIR/$pkg/.lock ]; then
			msgerr "Package '$pkg' already locked."
		else
			touch $PKGDB_DIR/$pkg/.lock && msg "Successfully locked package '$pkg'."
		fi
	done	
}

scratch_locate() {
	needarg $@
	for repo in $PORT_REPO; do
		grep -R $@ $repo/*/.pkgfiles 2>/dev/null | sed 's/:/ /;s/\/\.pkgfiles//' | awk '{print $1,$4}' | column -t
	done
}

scratch_unlock() {
	needroot "Unlocking package"	
	for pkg in "$@"; do
		if ! isinstalled $pkg; then
			msgerr "Package '$pkg' is not installed."
		elif [ ! -f $PKGDB_DIR/$pkg/.lock ]; then
			msgerr "Package '$pkg' is not locked."
		else
			rm -f $PKGDB_DIR/$pkg/.lock && msg "Successfully unlocked package '$pkg'."
		fi
	done	
}

scratch_isorphan() {	
	needarg $@	
	for pkg in $(allinstalled); do
		if depend=$(get_depends $pkg); then
			for dep in $depend; do
				if [ $dep = $1 ]; then
					return 1
				fi
			done
		fi
		unset depend dep
	done
	return 0
}

scratch_sync() {	
	checktool httpup	
	needroot "Updating ports"
	
	if [ ! -e "$REPO_FILE" ]; then
		msgerr "Repo file not found! ($REPO_FILE)"
		exit 1
	fi

	grep -Ev '^(#|$)' "$REPO_FILE" | awk '{print $1,$2}' | while read -r repodir repourl; do
		if [ "$repodir" ] && [ "$repourl" ]; then
			httpup sync $repourl $repodir || {
				msgerr "Failed sync from $repourl"
				exit 1
			}
		fi
	done
}

scratch_trigger() {
	needroot "Run trigger"
	if [ -z "$*" ]; then
		for i in $(seq 12); do
			eval trig_$i=1
		done
	else
		pre_triggers $@
	fi
	post_triggers
}

post_triggers() {
	if [ "$trig_12" = 1 ]; then
		echo "trigger: Running mkdirs..."
		for mkd in $PKGDB_DIR/*/.pkgmkdirs; do
			[ -s $mkd ] || continue
			grep -Ev '^(#|$)' $mkd | while read -r dir mode uid gid junk; do
				if [ -e "$dir" ]; then
					if [ "$uid" != '-' ]; then
						getent passwd $uid >/dev/null && chown "$uid" "$dir"
					fi
					if [ "$gid" != '-' ]; then
						getent group $gid >/dev/null && chgrp "$gid" "$dir"
					fi
					if [ "$mode" != '-' ]; then
						chmod "$mode" "$dir"
					fi
				fi
			done
		done
	fi
	
	if [ "$trig_11" = 1 ] && [ $(command -v fc-cache) ]; then
		echo "trigger: Updating fontconfig cache..."
		fc-cache -s
	fi
	
	if [ "$trig_10" = 1 ] && [ $(command -v gdk-pixbuf-query-loaders) ]; then
		echo "trigger: Probing GDK-Pixbuf loader modules..."
		gdk-pixbuf-query-loaders --update-cache
	fi
	
	if [ "$trig_9" = 1 ] && [ $(command -v gio-querymodules) ]; then
		echo "trigger: Updating GIO module cache..."
		gio-querymodules /usr/lib/gio/modules
	fi
	
	if [ "$trig_8" = 1 ] && [ $(command -v glib-compile-schemas) ]; then
		echo "trigger: Compiling GSettings XML schema files..."
		glib-compile-schemas /usr/share/glib-2.0/schemas
	fi
	
	if [ "$trig_7" = 1 ] && [ $(command -v gtk-query-immodules-2.0) ]; then
		echo "trigger: Probing GTK2 input method modules..."
		gtk-query-immodules-2.0 --update-cache
	fi
	
	if [ "$trig_6" = 1 ] && [ $(command -v gtk-query-immodules-3.0) ]; then
		echo "trigger: Probing GTK3 input method modules..."
		gtk-query-immodules-3.0 --update-cache
	fi
	
	if [ "$trig_5" = 1 ] && [ $(command -v gtk-update-icon-cache) ]; then
		echo "trigger: Updating icon theme caches..."
		for dir in /usr/share/icons/* ; do
			if [ -e $dir/index.theme ]; then
				gtk-update-icon-cache -q $dir 2>/dev/null
			else
				rm -f $dir/icon-theme.cache
				rmdir --ignore-fail-on-non-empty $dir
			fi
		done
	fi
	
	if [ "$trig_4" = 1 ] && [ $(command -v udevadm) ]; then
		echo "trigger: Updating hardware database..."
		udevadm hwdb --update
	fi
	
	if [ "$trig_3" = 1 ] && [ $(command -v mkfontdir) ] && [ $(command -v mkfontscale) ]; then
		echo "trigger: Updating X fontdir indices..."
		for dir in $(find /usr/share/fonts -maxdepth 1 -type d \( ! -path /usr/share/fonts -a ! -name X11 \)) /usr/share/fonts/X11/*; do
			rm -f $dir/fonts.scale $dir/fonts.dir $dir/.uuid
			rmdir --ignore-fail-on-non-empty $dir
			[ -d "$dir" ] || continue
			mkfontdir $dir
			mkfontscale $dir
		done
	fi
	
	if [ "$trig_2" = 1 ] && [ $(command -v update-desktop-database) ]; then
		echo "trigger: Updating desktop file MIME type cache..."
		update-desktop-database --quiet
	fi
	
	if [ "$trig_1" = 1 ] && [ $(command -v update-mime-database) ]; then
		echo "trigger: Updating the MIME type database..."
		update-mime-database /usr/share/mime
	fi
}

pre_triggers() {	
	# mime db
	if [ "$trig_1" != "1" ]; then
		for pkg in $@; do
			if [ -s "$PKGDB_DIR/$pkg/.files" ] && [ "$(grep ^usr/share/mime/$ $PKGDB_DIR/$pkg/.files)" ]; then
				trig_1=1
				break
			fi
		done
	fi
	
	# desktop db
	if [ "$trig_2" != "1" ]; then
		for pkg in $@; do
			if [ -s "$PKGDB_DIR/$pkg/.files" ] && [ "$(grep ^usr/share/applications/$ $PKGDB_DIR/$pkg/.files)" ]; then
				trig_2=1
				break
			fi
		done
	fi
	
	# mkfontdir
	if [ "$trig_3" != "1" ]; then
		for pkg in $@; do
			if [ -s "$PKGDB_DIR/$pkg/.files" ] && [ "$(grep ^usr/share/fonts/$ $PKGDB_DIR/$pkg/.files)" ]; then
				trig_3=1
				break
			fi
		done
	fi
	
	# hwdb
	if [ "$trig_4" != "1" ]; then
		for pkg in $@; do
			if [ -s "$PKGDB_DIR/$pkg/.files" ] && [ "$(grep ^etc/udev/hwdb.d/$ $PKGDB_DIR/$pkg/.files)" ]; then
				trig_4=1
				break
			fi
		done
	fi
	
	# icon caches
	if [ "$trig_5" != "1" ]; then
		for pkg in $@; do
			if [ -s "$PKGDB_DIR/$pkg/.files" ] && [ "$(grep ^usr/share/icons/$ $PKGDB_DIR/$pkg/.files)" ]; then
				trig_5=1
				break
			fi
		done
	fi
	
	# gtk3 immodules
	if [ "$trig_6" != "1" ]; then
		for pkg in $@; do
			if [ -s "$PKGDB_DIR/$pkg/.files" ] && [ "$(grep ^usr/lib/gtk-3.0/3.0.0/immodules/.*.so $PKGDB_DIR/$pkg/.files)" ]; then
				trig_6=1
				break
			fi
		done
	fi
	
	# gtk2 immodules
	if [ "$trig_7" != "1" ]; then
		for pkg in $@; do
			if [ -s "$PKGDB_DIR/$pkg/.files" ] && [ "$(grep ^usr/lib/gtk-2.0/2.10.0/immodules/.*.so $PKGDB_DIR/$pkg/.files)" ]; then
				trig_7=1
				break
			fi
		done
	fi
	
	# gsettings schema
	if [ "$trig_8" != "1" ]; then
		for pkg in $@; do
			if [ -s "$PKGDB_DIR/$pkg/.files" ] && [ "$(grep ^usr/share/glib-2.0/schemas/$ $PKGDB_DIR/$pkg/.files)" ]; then
				trig_8=1
				break
			fi
		done
	fi
	
	# gio modules
	if [ "$trig_9" != "1" ]; then
		for pkg in $@; do
			if [ -s "$PKGDB_DIR/$pkg/.files" ] && [ "$(grep ^usr/lib/gio/modules/.*.so $PKGDB_DIR/$pkg/.files)" ]; then
				trig_9=1
				break
			fi
		done
	fi
	
	# gdk-pixbuf
	if [ "$trig_10" != "1" ]; then
		for pkg in $@; do
			if [ -s "$PKGDB_DIR/$pkg/.files" ] && [ "$(grep ^usr/lib/gdk-pixbuf-2.0/2.10.0/loaders/.*.so $PKGDB_DIR/$pkg/.files)" ]; then
				trig_10=1
				break
			fi
		done
	fi
	
	# font caches
	if [ "$trig_11" != "1" ]; then
		for pkg in $@; do
			if [ -s "$PKGDB_DIR/$pkg/.files" ] && [ "$(grep ^usr/share/fonts/$ $PKGDB_DIR/$pkg/.files)" ]; then
				trig_11=1
				break
			fi
		done
	fi
	
	# makedirs
	if [ "$trig_12" != "1" ]; then
		for pkg in $@; do
			if [ -s "$PKGDB_DIR/$pkg/.pkgmkdirs" ]; then
				trig_12=1
				break
			fi
		done
	fi
}

scratch_build() {
	while [ "$1" ]; do
		case $1 in
			-i|-u|-r|-g|-p) ;;
			--log) LOG=1;;
			-*) OPTS="$OPTS $1";;
			 *) PKGNAME="$PKGNAME $1";;
		esac
		shift
	done
	[ "$PKGNAME" ] || {
		echo "Please specify package(s) to build."
		return 1
	}
	for pkg in $PKGNAME; do
		ppath=$(getportpath $pkg) || {
			echo "Package '$pkg' not found."
			return 1
		}
		cd $ppath
		settermtitle "Building $pkg..."
		if [ "$LOG" ]; then
			pkgbuild $OPTS | tee /var/log/pkgbuild.log || {
				settermtitle "Building $pkg failed."
				return 1
			}
		else
			pkgbuild $OPTS || {
				settermtitle "Building $pkg failed."
				return 1
			}
		fi
		settermtitle "Building $pkg done."
		cd - >/dev/null
	done
}

scratch_install() {
	needroot "Installing package"
	while [ "$1" ]; do
		case $1 in
			-i|-u) ;;
			-r|--reinstall) REINSTALL=1;;
			-y|--yes) NOCONFIRM=1;;
			-n|--no-dep) NO_DEP=1;;
			--exclude=*) EXOPT=$1;;
			-*) OPTS="$OPTS $1";;
			*) PKGNAME="$PKGNAME $1";;
		esac
		shift
	done	
	[ "$PKGNAME" ] || {
		echo "Please specify package(s) to install."
		return 1
	}
	# if reinstall, dont calculate dep, just reinstall it then exit
	if [ "$REINSTALL" = 1 ]; then
		error=0
		for ii in $PKGNAME; do
			if [ ! $(getportpath $ii) ]; then
				echo "Package '$ii' not found."
			elif ! isinstalled $ii; then
				echo "Package '$ii' not installed."
			else
				cd $(getportpath $ii)
					settermtitle "Reinstalling $ii..."
					pkgbuild $OPTS -r || {
						error=1
						break
					}
					done_pkg="$done_pkg $ii"
				cd - >/dev/null
			fi
		done
		settermtitle "Triggering install hook..."
		[ "$done_pkg" ] && scratch_trigger $done_pkg
		settermtitle "Reinstalling done."
		return "$error"
	fi
	if [ "$NO_DEP" = 1 ]; then
		error=0
		for ii in $PKGNAME; do
			if [ ! $(getportpath $ii) ]; then
				echo "Package '$ii' not found."
			elif isinstalled $ii; then
				echo "Package '$ii' already installed."
				continue
			else
				cd $(getportpath $ii)
					settermtitle "Installing $ii..."
					pkgbuild -i $OPTS || {
						error=1
						break
					}
					done_pkg="$done_pkg $ii"
				cd - >/dev/null
			fi
		done
		settermtitle "Triggering install hook..."
		[ "$done_pkg" ] && scratch_trigger $done_pkg
		settermtitle "Installing done."
		return "$error"
	fi
	for i in $PKGNAME; do
		if [ ! $(getportpath $i) ]; then
			echo "Package '$i' not found."
		elif isinstalled $i; then
			echo "Package '$i' already installed."
		else
			IPKG="$IPKG $i"
		fi
	done
	[ "$IPKG" ] || return 0
	echo "Resolving dependencies..."
	INST="$(scratch_deplist -q $IPKG $EXOPT)"
	if [ "$INST" ]; then
		echo
		pkgcount=0
		for pkg in $INST; do
			pkgcount=$(( pkgcount + 1 ))
			printf "[${GREEN}i${CRESET}] $pkg  "
		done
		echo; echo
		echo "( $pkgcount install )"
		echo
		if [ ! "$NOCONFIRM" ]; then
			confirm "Continue install package(s)?" "Package installation cancelled." || exit $?
			echo
		fi
		error=0
		count=0
		total=$(echo $INST | wc -w)
		for int in $INST; do
			count=$(( count + 1 ))
			if portpathh=$(getportpath $int); then
				cd $portpathh
					settermtitle "[ $count/$total ] installing $int..."
					pkgbuild -i $OPTS || {
						error=1
						count=$(( count - 1 ))
						break
					}
					done_pkg="$done_pkg $int"
				cd - >/dev/null
			else
				msgwarn "Skipping missing package: $int"
			fi
			unset portpathh
		done
		settermtitle "Triggering install hook..."
		[ "$done_pkg" ] && scratch_trigger $done_pkg
		settermtitle "$count/$total package(s) installed."
		return "$error"
	fi
}

scratch_remove() {
	needroot "Removing package"
	while [ "$1" ]; do
		case $1 in
			-y|--yes) NOCONFIRM=1;;
			-*) OPTS="$OPTS $1";;
			*) PKGNAME="$PKGNAME $1";;
		esac
		shift
	done
	[ "$PKGNAME" ] || {
		echo "Please specify package(s) to remove."
		return 1
	}
	for i in $PKGNAME; do
		if ! isinstalled $i; then
			echo "Package '$i' not installed."
		else
			IPKG="$IPKG $i"
		fi
	done
	[ "$IPKG" ] || return 0
	echo "Removing packages..."
	echo
	pkgcount=0
	count=0
	for pkg in $IPKG; do
		pkgcount=$(( pkgcount + 1 ))
		printf "[${RED}x${CRESET}] $pkg  "
	done
	echo; echo
	echo "( $pkgcount remove )"
	echo
	[ "$NOCONFIRM" ] || {
		confirm "Continue remove package(s)?" "Package removing cancelled." || exit $?
		echo
	}
	for pkg in $IPKG; do
		count=$(( count + 1 ))
		pre_triggers $pkg
		settermtitle "[ $count/$pkgcount ] Removing $pkg..."
		pkgdel $pkg $OPTS || {
			error=1
			break
		}
	done
	settermtitle "Triggering remove hook..."
	post_triggers
	settermtitle "$pkgcount package(s) removed."
}

outdatepkg() {
	for pkg in $(allinstalled); do
		if [ -f "$MASK_FILE" ] && [ $(grep -Ev '^(#|$| )' $MASK_FILE | grep -w $pkg) ]; then
			continue
		fi
		[ -e "$PKGDB_DIR/$pkg/.lock" ] && continue
		getportpath $pkg >/dev/null || continue
		. $(getportpath $pkg)/$BUILD_SCRIPT
		if [ -z "$name" ] || [ -z "$version" ]; then
			continue
		fi
		iversion=$(installed_pkg_info version $pkg)
		irelease=$(installed_pkg_info release $pkg)
		if [ "$release" != "$irelease" ] || [ "$version" != "$iversion" ]; then
			echo $name
		fi
		unset iversion irelease version release
	done
}

scratch_sysup() {
	needroot "Upgrading package"	
	while [ "$1" ]; do
		case $1 in
			-i|-u|-r) ;;
			-y|--yes) NOCONFIRM=1;;
			-n|--no-dep) NODEP=1;;
			--exclude=*) EXOPT=$1;;
			-*) OPTS="$OPTS $1";;
		esac
		shift
	done
	echo "Checking for outdated packages..."
	PKGOUTDATE=$(outdatepkg)
	[ "$PKGOUTDATE" ] || {
		echo "All packages are up to date."
		return 0
	}
	[ "$(echo $PKGOUTDATE | tr ' ' '\n' | grep -x scratchpkg)" ] && {
		echo
		msgwarn "Please upgrade 'scratchpkg' first.'"
		return 1
	}
	UPGPKG=0
	NEWPKG=0	
	if [ "$NODEP" != 1 ]; then
		echo "Resolving dependencies..."
		DEP=$(scratch_deplist $PKGOUTDATE $EXOPT | awk '{print $2}')
		echo
		for d in $DEP; do
			if [ "$(echo $PKGOUTDATE | tr ' ' '\n' | grep -x $d)" = "$d" ]; then
				printf "[${GREEN}u${CRESET}] $d  "
				WILLINSTALL="$WILLINSTALL $d"
				UPGPKG=$(( UPGPKG + 1 ))
			elif ! isinstalled $d && [ "$(getportpath "$d")" ]; then
				printf "[${CYAN}n${CRESET}] $d  "
				WILLINSTALL="$WILLINSTALL $d"
				NEWPKG=$(( NEWPKG + 1 ))
			fi
		done
	else
		echo
		for dd in $PKGOUTDATE; do
			printf "[${GREEN}u${CRESET}] $dd  "
			WILLINSTALL="$WILLINSTALL $dd"
			UPGPKG=$(( UPGPKG + 1 ))
		done
	fi
	echo; echo
	echo "( $UPGPKG upgrade, $NEWPKG new install )"
	echo
	[ "$NOCONFIRM" ] || {
		confirm "Continue upgrade/install these package(s)?" "Package upgrade cancelled." || exit $?
		echo
	}
	error=0
	count=0
	total=$(echo $WILLINSTALL | wc -w)
	for inst in $WILLINSTALL; do # install all required dependencies and target packages itself
		count=$(( count + 1 ))
		cd $(getportpath $inst)
		if ! isinstalled $inst; then
			settermtitle "[ $count/$total ] Installing $inst..."
			pkgbuild -i $OPTS || {
				error=1
				count=$(( count - 1 ))
				break
			}
		else
			settermtitle "[ $count/$total ] Upgrading $inst..."
			pkgbuild -u $OPTS || {
				error=1
				count=$(( count - 1 ))
				break
			}
		fi
		cd - >/dev/null
		done_pkg="$done_pkg $inst"
	done
	settermtitle "Triggering install hook."
	[ "$done_pkg" ] && scratch_trigger $done_pkg
	settermtitle "$count/$total package(s) upgraded."
	return "$error"
}

scratch_upgrade() {
	needroot "Upgrading package"	
	while [ "$1" ]; do
		case $1 in
			-i|-r) ;;
			-y|--yes) NOCONFIRM=1;;
			-d|--no-dep) NO_DEP=1;;
			--exclude=*) EXOPT=$1;;
			-*) OPTS="$OPTS $1";;
			*) PKGNAME="$PKGNAME $1";;
		esac
		shift
	done	
	[ "$PKGNAME" ] || {
		echo "Please specify package(s) to upgrade."
		return 1
	}
	for pkg in $PKGNAME; do
		if ! isinstalled $pkg; then
			echo "Package '$pkg' not installed."
			continue
		elif [ ! $(getportpath $pkg) ]; then
			echo "Package '$pkg' not exist."
			continue
		else
			. $(getportpath $pkg)/$BUILD_SCRIPT
			if [ "$(installed_pkg_info version $pkg)-$(installed_pkg_info release $pkg)" = "$version-$release" ]; then
				echo "Package '$pkg' is up to date."
				continue
			fi
		fi
		upkg="$upkg $pkg"
	done
	[ "$upkg" ] || return 0
	
	UPGPKG=0
	NEWPKG=0	
	if [ "$NODEP" != 1 ]; then
		echo "Resolving dependencies..."
		DEP=$(scratch_deplist $upkg $EXOPT | awk '{print $2}')
		echo
		for d in $DEP; do
			if [ "$(echo $upkg | tr ' ' '\n' | grep -x $d)" = "$d" ]; then
				printf "[${GREEN}u${CRESET}] $d  "
				WILLINSTALL="$WILLINSTALL $d"
				UPGPKG=$(( UPGPKG + 1 ))
			elif ! isinstalled $d && [ "$(getportpath "$d")" ]; then
				printf "[${CYAN}n${CRESET}] $d  "
				WILLINSTALL="$WILLINSTALL $d"
				NEWPKG=$(( NEWPKG + 1 ))
			fi
		done
	else
		echo
		for dd in $upkg; do
			printf "[${GREEN}u${CRESET}] $dd  "
			WILLINSTALL="$WILLINSTALL $dd"
			UPGPKG=$(( UPGPKG + 1 ))
		done
	fi
	echo; echo
	echo "( $UPGPKG upgrade, $NEWPKG new install )"
	echo
	[ "$NOCONFIRM" ] || {
		confirm "Continue upgrade/install these package(s)?" "Package upgrade cancelled." || exit $?
		echo
	}
	error=0
	count=0
	total=$(echo $WILLINSTALL | wc -w)
	for inst in $WILLINSTALL; do # install all required dependencies and target packages itself
		count=$(( count + 1 ))
		cd $(getportpath $inst)
		if ! isinstalled $inst; then
			settermtitle "[ $count/$total ] Installing $inst..."
			pkgbuild -i $OPTS || {
				error=1
				count=$(( count - 1 ))
				break
			}
		else
			settermtitle "[ $count/$total ] Upgrading $inst..."
			pkgbuild -u $OPTS || {
				error=1
				count=$(( count - 1 ))
				break
			}
		fi
		cd - >/dev/null
		done_pkg="$done_pkg $inst"
	done
	settermtitle "Triggering install hook."
	[ "$done_pkg" ] && scratch_trigger $done_pkg
	settermtitle "$count/$total package(s) upgraded."
	return "$error"
}

scratch_outdate() {
	for pkg in $(allinstalled); do
		if [ "$(getportpath $pkg)" ]; then
			. $(getportpath $pkg)/$BUILD_SCRIPT
			if [ -z "$name" ] || [ -z "$version" ]; then
				continue
			fi
			iversion=$(installed_pkg_info version $pkg)
			irelease=$(installed_pkg_info release $pkg)
			[ -f "$PKGDB_DIR/$pkg/.lock" ] && ITSLOCK="[masked]"
			if [ -f "$MASK_FILE" ] && [ $(grep -Ev '^(#|$| )' $MASK_FILE | grep $pkg) ]; then
				ITSLOCK="[masked]"
			fi
			outdatemsg="$name $iversion-$irelease => $version-$release $ITSLOCK"
			newerinstmsg="$name $iversion-$irelease => $version-$release [newer installed] $ITSLOCK"
			if [ "$version" !=  "$iversion" ]; then
				vercomp $version $iversion
				if [ $? = 2 ]; then
					echo "$outdatemsg"
					OUTDATE=yes
				elif [ $? = 1 ]; then
					echo "$newerinstmsg"
					OUTDATE=yes
				fi
			elif [ "$release" !=  "$irelease" ]; then
				vercomp $release $irelease
				if [ $? = 2 ]; then
					echo "$outdatemsg"
					OUTDATE=yes
				elif [ $? = 1 ]; then
					echo "$newerinstmsg"
					OUTDATE=yes
				fi
			fi
			unset ITSLOCK name version
		fi
	done
	
	[ ! "$OUTDATE" ] && msg "All packages are up to date."
}

scratch_search() {
	needarg $@
	arg=$*
	for repo in $PORT_REPO; do
		[ -d $repo ] || continue
		out=$(grep -R "# description" $repo/*/$BUILD_SCRIPT | grep "$arg" | awk -F : '{print $1}' | sort)
		[ "$out" ] || continue
		found=1
		for line in $out; do
			repo=$(echo $line | rev | awk -F / '{print $3}' | rev)
			desc=$(grep "^# description[[:blank:]]*:" $line | sed 's/^# description[[:blank:]]*:[[:blank:]]*//')
			. $line
			if isinstalled $name; then
				ins="[${GREEN}*${CRESET}]"
			else
				ins="[ ]"
			fi
			printf "$ins ${PURPLE}($repo)${CRESET} $name ${CYAN}$version-$release${CRESET}: $desc\n"
			unset repo desc name version release build
		done
		unset out
	done
	if [ ! "$found" ]; then
		msg "No matching package found."
	fi
}

scratch_cache() {
	needroot "Clear old caches"
	
	allcachepkg=/tmp/.allcachepkg.$$
	allcachesrc=/tmp/.allcachesrc.$$
	keepcachepkg=/tmp/.keepcachepkg.$$
	keepcachesrc=/tmp/.keepcachesrc.$$
	diffcachepkg=/tmp/.diffcachepkg.$$
	diffcachesrc=/tmp/.diffcachesrc.$$
	
	[ -f /etc/scratchpkg.conf ] && . /etc/scratchpkg.conf
	
	touch \
		$allcachepkg \
		$allcachesrc \
		$keepcachepkg \
		$keepcachesrc \
		$diffcachepkg \
		$diffcachesrc
	
	if [ "$(find $PACKAGE_DIR -mindepth 1 -print -quit 2>/dev/null)" ]; then
		for list in "$PACKAGE_DIR"/*; do
			basename $list >> "$allcachepkg"
		done
	fi
	
	if [ "$(find $SOURCE_DIR -mindepth 1 -print -quit 2>/dev/null)" ]; then
		for list in "$SOURCE_DIR"/*; do
			basename $list >> "$allcachesrc"
		done
	fi
	
	for repo in $PORT_REPO; do
		if [ "$(find $repo/*/ -mindepth 1 -print -quit 2>/dev/null)" ]; then # check directory if its not empty
			for port in $repo/*/$BUILD_SCRIPT; do
				. $port
				echo "$name-$version-$release.spkg.tar.$COMPRESSION_MODE" >> "$keepcachepkg"
				if [ "$source" ]; then
					for src in $source; do
						if echo $src | grep -Eq "(ftp|http|https)://"; then
							if echo $src | grep -Eq "::(ftp|http|https)://"; then
								sourcename="$(echo $src | awk -F '::' '{print $1}')"
							else
								sourcename="$(echo $src | rev | cut -d / -f 1 | rev)"
							fi
							echo $sourcename >> "$keepcachesrc"
						fi
					done
				fi
			done
		fi
	done
	grep -Fxv -f "$keepcachepkg" "$allcachepkg" > "$diffcachepkg"
	grep -Fxv -f "$keepcachesrc" "$allcachesrc" > "$diffcachesrc"
	
	cat $diffcachepkg
	cat $diffcachesrc
	
	if [ -s "$diffcachepkg" ]; then
		cd "$PACKAGE_DIR"
		sizepkg=$(du -ch $(cat $diffcachepkg) | grep total | awk '{print $1}')
		cd - >/dev/null
	else
		sizepkg=0M
	fi
	
	if [ -s "$diffcachesrc" ]; then
		cd "$SOURCE_DIR"
		sizesrc=$(du -ch $(cat $diffcachesrc) | grep total | awk '{print $1}')
		cd - >/dev/null
	else
		sizesrc=0M
	fi
	
	echo "Total package cache size: $sizepkg"
	echo "Total source cache size : $sizesrc"
	
	if [ -s "$diffcachepkg" ] || [ -s "$diffcachesrc" ]; then
		echo
		confirm "Clear old caches?" "Old caches is kept." && {
			for i in $(cat $diffcachepkg); do
				[ -e "$PACKAGE_DIR/$i" ] && rm -v "$PACKAGE_DIR/$i"
			done
			for i in $(cat $diffcachesrc); do
				[ -e "$SOURCE_DIR/$i" ] && rm -v "$SOURCE_DIR/$i"
			done
		}
	fi
	
	rm -f \
		"$allcachepkg" \
		"$allcachesrc" \
		"$keepcachepkg" \
		"$keepcachesrc" \
		"$diffcachepkg" \
		"$diffcachesrc"
}

scratch_deplist() {
	OLDIFS=$IFS
	IFS=,
	while [ "$1" ]; do
		case $1 in
			-q|--quick) quick=1;;
			--exclude=*) for i in ${1#*=}; do exclude="$exclude $i"; done;;
			-*) ;;
			*) PKG="$PKG $1";;
		esac
		shift
	done
	IFS=$OLDIFS
	[ "$PKG" ] || {
		echo "Please specify package(s) to list dependencies."
		return 1
	}
	for p in $PKG; do
		if [ "$(getportpath $p)" ]; then
			PPKG="$PPKG $p"
		else
			[ "$quick" = 1 ] || msgerr "Package '$p' not exist."
		fi
	done
	
	for p in $PPKG; do
		deplist $p
	done
	
	[ "$DEP" ] || return 0
	
	if [ "$quick" = 1 ]; then
		echo $DEP | tr ' ' '\n'
	else
		for p in $DEP; do
			if isinstalled $p; then
				echo "[*] $p"
			else
				echo "[-] $p"
			fi
		done
		if [ "$MISSINGDEP" ]; then
			for m in $MISSINGDEP; do
				echo "Missing deps: $m" | sed 's/(/ (/'
			done
		fi
	fi
}

deplist() {
	# skip excluded dependencies
	if echo $exclude | tr " " "\n" | grep -qx $1; then
		return 0
	fi
	
	# check currently process for circular dependencies
	# for circular dependencies, found first will take precedence
	[ "$CHECK" ] && {
		if echo $CHECK | tr " " "\n" | grep -qx $1; then
			return 0
		fi
	}
	
	# add package to currently process
	CHECK="$CHECK $1"
	
	# check dependencies
	for i in $(get_depends $1); do
		if [ "$quick" = 1 ] && isinstalled $i; then
			continue
		else
			if ! echo $DEP | tr " " "\n" | grep -qx $i; then
				if ! getportpath $i >/dev/null; then
					MISSINGDEP="$MISSINGDEP $i($1)"
				else
					deplist $i
				fi
			fi
		fi
	done
	
	# add dependency to list checked dep
	if ! echo $DEP | tr " " "\n" | grep -qx $1; then
		if [ "$quick" = 1 ]; then
			isinstalled $1 || DEP="$DEP $1"
		else
			DEP="$DEP $1"
		fi
	fi
	
	# delete item from loop process
	CHECK=$(echo $CHECK | sed "s/$1//")
}

scratch_cat() {	
	needarg $@
	if PPATH=$(getportpath "$1"); then
		cat "$PPATH/$BUILD_SCRIPT"
	else
		msgerr "Port '$1' not exist."
		return 1
	fi
}

scratch_dependent() {
	needarg $@
	if [ "$(getportpath $1)" ]; then
		grep -R "# depends[[:blank:]]*:" $PORT_REPO \
		| sed "s,:# depends[[:blank:]]*:[[:blank:]]*,#|,;s, ,|,g;s,$,|,g" \
		| grep "|$1|" \
		| awk -F "#" '{print $1}' \
		| rev \
		| awk -F / '{print $2}' \
		| rev
	else
		msgerr "Port '$1' not exist."
		return 1
	fi
}

scratch_depends() {
	needarg $@	
	if getportpath "$1" >/dev/null; then
		depends=$(get_depends $1)
	else
		msgerr "Port '$1' not exist."
		return 1
	fi
	
	for dep in $depends; do
		if isinstalled $dep; then
			msginst "$dep"
		elif getportpath $dep >/dev/null; then
			msgnoinst "$dep"
		else
			msgmiss "$dep"
		fi
	done
}

scratch_dup() {	
	dup=$(find $PORT_REPO -type d -print | grep -Exv "($(echo $PORT_REPO | tr ' ' '|'))" | \
		rev | cut -d '/' -f1 | rev | sort | uniq -d)
	
	if [ "$dup" ]; then
		for dp in $dup; do
			for repo in $PORT_REPO; do
				[ -d $repo/$dp ] && echo "$repo/$dp"
			done
		done
	else
		msg "No duplicate ports found."
	fi	
}

scratch_foreign() {
	for pkg in $(allinstalled); do
		if ! getportpath $pkg >/dev/null; then
			iname=$(installed_pkg_info name $pkg)
			iversion=$(installed_pkg_info version $pkg)
			irelease=$(installed_pkg_info release $pkg)
			echo "$iname $iversion-$irelease"
		fi
		unset iname iversion irelease
	done	
}

scratch_info() {
	needarg $@
	ppath=$(getportpath $1) || return 1
	
	. $ppath/$BUILD_SCRIPT
	desc=$(grep "^# description[[:blank:]]*:" $ppath/$BUILD_SCRIPT | sed 's/^# description[[:blank:]]*:[[:blank:]]*//')
	maint=$(grep "^# maintainer[[:blank:]]*:" $ppath/$BUILD_SCRIPT | sed 's/^# maintainer[[:blank:]]*:[[:blank:]]*//')
	deps=$(get_depends $1 | tr '\n' ' ')
	
	echo "Name:         $1"
	echo "Path:         $ppath"
	echo "Version:      $version"
	echo "Release:      $release"
	echo "Description:  $desc"
	echo "Maintainer:   $maint"
	echo "Dependencies: $deps"
}

scratch_installed() {
	for all in $(allinstalled); do		
		printf "%s" "$all "
		grep -e ^version -e ^release $PKGDB_DIR/$all/.pkginfo | awk '{print $3}' | tr '\n' '-' | sed 's:\-$::'
		echo
	done
}

scratch_missingdep() {
	for pkg in $(allinstalled); do
		if getportpath "$pkg" >/dev/null; then
			depends=$(get_depends $pkg)
		fi
		if [ "$depends" ]; then
			for d in $depends; do
				if ! isinstalled $d; then
					if [ -z "$msd" ]; then
						msd="$d"
					else
						msd="$msd $d"
					fi
				fi
			done
		fi
		[ "$msd" ] && echo "$pkg: $msd"
		unset depends msd
	done
}

scratch_locked() {
	for pkg in $(allinstalled); do
		[ -f "$PKGDB_DIR/$pkg/.lock" ] && echo "$pkg"
	done
}

scratch_orphan() {
	tmpallpkg="/tmp/.pkgquery_allpkg.$$"
	tmpalldep="/tmp/.pkgquery_alldep.$$"
	for pkg in $(allinstalled); do
		echo $pkg >> $tmpallpkg
		dep="$dep $(get_depends $pkg)"
	done
	echo $dep | tr ' ' '\n' | sort | uniq > "$tmpalldep"
	grep -xvF -f "$tmpalldep" "$tmpallpkg"
	rm "$tmpalldep" "$tmpallpkg"
}

scratch_path() {
	needarg $@
	if PPATH=$(getportpath "$1"); then
		echo "$PPATH"
	else
		msgerr "Port '$1' not exist."
		return 1
	fi
}

scratch_provide() {
	needarg $@
	arg=$(echo $1 | sed "s/^\///")
	grep -R "$arg" $PKGDB_DIR/*/.files \
	| sed "s:$PKGDB_DIR/::" \
	| sed "s:/.files::" \
	| tr : " " \
	| column -t
}

scratch_readme() {
	needarg $@	
	if PPATH=$(getportpath "$1"); then
		if [ -f "$PPATH/readme" ]; then
			cat "$PPATH/readme"
		else
			msgerr "Port '$1' does not have readme."
		fi
	else
		msgerr "Port '$1' not exist."
		exit 1
	fi
}

scratch_files() {	
	needarg $@
	if isinstalled $1; then
		cat "$PKGDB_DIR/$1/.files"
	else
		msg "Package '$1' not installed."
	fi	
}

scratch_help() {
	cat << EOF
Usage:
    $(basename $0) <options> [<arg>]
    
Options:
    install <ports> <arg>    install ports (use pkgbuild arg, except '-i' & '-u')
                             -r|--reinstall   reinstall
                             -n|--no-dep      skip dependencies
                             -y|--yes         skip ask user permission
                             --exclude=*      exclude dependencies, comma separated

    upgrade <ports> <arg>    upgrade ports (use pkgbuild arg, except '-i' & '-r')
                             -n|--no-dep      skip dependencies
                             -y|--yes         skip ask user permission
                             --exclude=*      exclude dependencies, comma separated

    remove  <ports> <arg>    remove installed ports (use pkgdel arg)
                             -y|--yes         skip ask user permission

    sysup           <arg>    full system upgrade (use pkgbuild arg, except '-i', '-r'  & '-u')
                             -n|--no-dep      skip dependencies
                             -y|--yes         skip ask user permission
                             --exclude=*      exclude dependencies, comma separated

    deplist <ports>          print all dependencies for ports
                             -q|--quick       skip installed ports
                             --exclude=*      exclude dependencies, comma separated

    build   <ports> <arg>    build ports (use pkgbuild arg, except '-i', '-u', '-r', '-g', & '-p')
                             --log            log build process (/var/log/pkgbuild.log)

    lock      <ports>        locking ports prevent upgrade
    unlock    <ports>        unlock locked ports
    trigger   [ports]        run system trigger
    search    <pattern>      find ports in repo
    cat       <port>         print spkgbuild
    depends   <port>         print dependencies
    dependent <port>         print dependent
    path      <port>         print path in repo
    provide   <files>        print port's provided files
    readme    <port>         print readme file, if exist
    files     <port>         print files installed
    info      <port>         print information
    locate    <files>        print location of files in ports repo
    sync                     update ports database
    outdate                  print outdated ports
    cache                    print and clear old pkg and src caches
    integrity                check installed port integrity
    dup                      print duplicate ports in repo
    installed                print all installed ports
    locked                   print loacked ports
    missingdep               print missing dependencies
    orphan                   print orphan installed ports
    foreign                  print foreign ports
    help                     print this help msg

Global options:
    --append-repo=<repo path>       append custom repo path
    --prepend-repo=<repo path>      prepend custom repo path
    --repo-file=<repo file>         use custom repo file (default: $REPO_FILE)
    --nocolor                       disable colour for output

EOF
}

print_runhelp_msg() {
	echo "Run '$(basename $0) help' to see available options."
	exit 2
}

# check for 'pkgadd', required for package database path
command -v pkgadd >/dev/null 2>&1 || {
	echo "'pkgadd' not found in \$PATH!"
	exit 1
}

mode=$1

[ "$mode" ] || {
	print_runhelp_msg
}

shift

for opt in $@; do
	case $opt in
	     --nocolor) nocolor;;
	      --repo=*) PORT_REPO="$PORT_REPO ${opt#*=}";;
	 --repo-file=*) REPO_FILE="${opt#*=}";;
	--alias-file=*) ALIAS_FILE="${opt#*=}";;
	           --*) MAINOPTS="$MAINOPTS $opt";;
	            -*) char=${#opt}; count=1
	                while [ "$count" != "$char" ]; do
	                   count=$((count+1))
	                   MAINOPTS="$MAINOPTS -$(printf '%s' $opt | cut -c $count)"
	                done;;
	             *) MAINOPTS="$MAINOPTS $opt";;
	esac
	shift
done

BUILD_SCRIPT="spkgbuild"
PKGDB_DIR="$(pkgadd --print-dbdir)"
REPO_FILE="${REPO_FILE:-/etc/scratchpkg.repo}"
ALIAS_FILE="${ALIAS_FILE:-/etc/scratchpkg.alias}"
MASK_FILE="${MASK_FILE:-/etc/scratchpkg.mask}"

# default value from pkgbuild
SOURCE_DIR="/var/cache/scratchpkg/sources"
PACKAGE_DIR="/var/cache/scratchpkg/packages"
COMPRESSION_MODE="xz"

if [ -f "$REPO_FILE" ]; then
	for repodir in $(grep -Ev '^(#|$)' "$REPO_FILE" | awk '{print $1}'); do
		PORT_REPO="$PORT_REPO $repodir"
	done
fi

if [ "$(command -v scratch_$mode)" ]; then
	scratch_$mode $MAINOPTS
else
	print_runhelp_msg
fi

exit $?
