#!/bin/sh
#    ______   ______  __  __   ______   
#   /\  __ \ /\  == \/\ \/ /  /\  ___\  
#   \ \  __ \\ \  _-/\ \  _"-.\ \ \__ \ 
#    \ \_\ \_\\ \_\   \ \_\ \_\\ \_____\
#     \/_/\/_/ \/_/    \/_/\/_/ \/_____/
#
# APKG - Alice Package Manager (C) 2023-2025 Emmett1
#

fetch_src() {
	[ "$source" ] || return 0
	for i in $source; do
		i=${i%::noextract}
		case $i in
			*::*) sn=${i%::*};;
			   *) sn=${i##*/};;
		esac
		case ${i#*::} in
			http://*|https://*|ftp://*)
				if [ ! -f $APKG_SOURCE_DIR/$sn ]; then
					[ -f $APKG_SOURCE_DIR/$sn.tmp ] && resume="-C -"
					msg "fetching ${i#*::}"
					curl $resume --fail --retry 3 --retry-delay 3 -L -o $APKG_SOURCE_DIR/$sn.tmp ${i#*::} || {
						msg "Failed downloading source '$sn'."
						exit 1
					}
					mv $APKG_SOURCE_DIR/$sn.tmp $APKG_SOURCE_DIR/$sn
				fi;;
		esac
	done
}

extract_src() {
	prepare_workdir
	[ "$source" ] || return 0
	checksource
	for i in $source; do
		case $i in
			*/*) S=$APKG_SOURCE_DIR;;
			  *) S=$HERE;;
		esac
		case ${i%::noextract} in
			*::*) sn=${i%%::*};;
			   *) sn=${i##*/}; sn=${sn%::noextract};;
		esac
		case $i in
			*::noextract)
				i=${i%::noextract}
				cp $S/$sn $SRC || {
					msg "Failed copy '$S/$sn' to '$SRC'."
					ret 1
				};;
			*.tar.bz2|*.tar.gz|*.tar.lz|*.tar.lzma|*.tar.lzo|*.tar.xz|*.tar.Z|*.tb2|*.tbz|*.tbz2|*.tz2|*.taz|*.tgz|*.tlz|*.txz|*.tZ|*.taZ)
				msg "Unpacking '$S/$sn'..."
				tar -p -o -C $SRC -xf $S/$sn || {
					msg "Failed unpacking source '${i##*/}'."
					ret 1
				};;
			*) 
				i=${i%::noextract}
				cp $S/$sn $SRC || {					
					msg "Failed copy '$S/$sn' to '$SRC'."
					ret 1
				};;
		esac
	done
}

prepare_workdir() {
	export SRC=$APKG_WORK_DIR/apkg-src-${PWD##*/}
	export PKG=$APKG_WORK_DIR/apkg-pkg-${PWD##*/}
	
	remove_workdir
	mkdir -p "$PKG" "$SRC"	
}

remove_workdir() {
	rm -rf "$PKG" "$SRC"
}

ret() {	
	[ "$APKG_KEEP_WORKDIR" ] || remove_workdir
	exit $1
}

detect_buildtype() {
	if [ -f meson.build ]; then
		build_type=meson_build
	elif [ -f configure ]; then
		build_type=configure_build
	elif [ -f CMakeLists.txt ]; then
		build_type=cmake_build
	elif [ -f setup.py ]; then
		build_type=python_build
	elif [ -f Makefile.PL ]; then
		build_type=perlmodule_build
	elif [ -f Makefile ] || [ -f makefile ] || [ -f GNUmakefile ]; then
		build_type=makefile_build
	else
		msg "failed to detect buildtype"
		ret 1
	fi
}

_makefile_build() {
	make \
		PREFIX=/usr \
		prefix=/usr \
		SYSCONFDIR=/etc \
		sysconfdir=/etc \
		MANDIR=/usr/share/man \
		mandir=/usr/share/man \
		LIBDIR=/usr/lib \
		PKGCONFIGDIR=/usr/lib/pkgconfig
	make \
		PREFIX=/usr \
		prefix=/usr \
		SYSCONFDIR=/etc \
		sysconfdir=/etc \
		MANDIR=/usr/share/man \
		mandir=/usr/share/man \
		LIBDIR=/usr/lib \
		PKGCONFIGDIR=/usr/lib/pkgconfig \
		DESTDIR=$PKG install
}

_perlmodule_build() {
	perl Makefile.PL
	make
	make DESTDIR=$PKG install
}

_cmake_build() {
	mkdir -p cmakebuild
	cd cmakebuild
	cmake \
		-DCMAKE_INSTALL_PREFIX=/usr \
		-DCMAKE_INSTALL_SYSCONFDIR=/etc \
		-DCMAKE_INSTALL_LIBDIR=lib \
		-DCMAKE_BUILD_TYPE=Release \
		-DFETCHCONTENT_FULLY_DISCONNECTED=ON \
		-DCMAKE_C_FLAGS_RELEASE="$CFLAGS" \
		-DCMAKE_CXX_FLAGS_RELEASE="$CXXFLAGS" \
		$build_opt \
		-B build ..
	if [ -f build.ninja ]; then
		ninja
		DESTDIR=$PKG ninja install
	else
		cmake --build build
		DESTDIR=$PKG cmake --install build
	fi
}

_python_build() {
	python3 setup.py build
	python3 setup.py install --prefix=/usr --root=$PKG --optimize=1 $build_opt
}

_configure_build() {
	./configure \
		--prefix=/usr \
		--sysconfdir=/etc \
		--localstatedir=/var \
		--libdir=/usr/lib \
		--infodir=/usr/share/info \
		--mandir=/usr/share/man \
		$build_opt
	make
	make DESTDIR=$PKG install
}

_meson_build() {
	meson setup \
		-Dprefix=/usr \
		-Dlibdir=/usr/lib \
		-Dincludedir=/usr/include \
		-Ddatadir=/usr/share \
		-Dmandir=/usr/share/man \
		-Dinfodir=/usr/share/info \
		-Dlocaledir=/usr/share/locale \
		-Dsysconfdir=/etc \
		-Dlocalstatedir=/var \
		-Dsharedstatedir=/var/lib \
		-Dbuildtype=plain \
		-Dauto_features=auto \
		-Dwrap_mode=nodownload \
		-Db_lto=true \
		-Db_pie=true \
		$build_opt \
		build
	ninja -C build
	DESTDIR=$PKG meson install -C build
}

apply_patch() {
	[ "$skip_patch" ] && {
		unset skip_patch # allowing manual patch in build() using apply_patch
		return 0
	}
	if [ "$source" ]; then
		for p in $source; do
			case ${p%::noextract} in
				*::*) pn=${p%::*};;
				   *) pn=${p##*/};;
			esac
			case $pn in
				*.patch|*.diff)
					msg "applying patch '$SRC/$pn'."
					patch ${patch_opt:--p1} -i $SRC/$pn || ret 1;;
			esac
		done
	fi
}

msg() {
	# Don't bother if we're not in a terminal
    if [ -t 1 ]; then
        ttysize=$(stty size 2>/dev/null | awk '{print $2}' || echo "80")
    fi
    printf "%s\n" "[${name:-...}] $@" | fold -sw ${ttysize:-80}
}

buildstatus() {
	[ "$?" = 0 ] || {
		msg "build package '$name-$version-$release' failed on '$1'."
		ret 1
	}
}

build_src() {
	cd $SRC
	
	if [ "$build_dir" ]; then
		if [ ! -d "$build_dir" ]; then
			msg "Source directory '$build_dir' not found."
			ret 1
		fi
	elif [ -d $name-$version ]; then
		build_dir=$name-$version
	elif [ -d "$(ls -1 --group-directories-first | head -n1)" ]; then
		build_dir=$(ls -1 --group-directories-first | head -n1)
	fi
	
	# cd into extracted source directory
	if [ "$build_dir" ]; then
		cd $build_dir
	fi
	
	apply_patch

	export CARGO_HOME=${CARGO_HOME:-$SRC/.cargo}
	export GOCACHE=${GOCACHE:-$SRC/.go}
	export DESTDIR=$PKG
	export DEST_DIR=$PKG     # p7zip
	export INSTALLROOT=$PKG  # syslinux
	export install_root=$PKG # glibc
	export INSTALL_ROOT=$PKG # qt5

	if [ ! "$source" ]; then
		# dummy pkg
		mkdir -p $PKG/usr
	elif [ "$(command -v build)" ]; then
		(set -e -x; build)
		buildstatus build
	else
		if [ "$(command -v prebuild)" ]; then
			(set -e -x; prebuild)
			buildstatus prebuild
		fi
		if [ ! "${build_type}" ]; then
			detect_buildtype
		fi
		(set -e -x; _${build_type})
		buildstatus ${build_type}
		if [ "$(command -v postbuild)" ]; then
			(set -e -x; postbuild)
			buildstatus postbuild
		fi
	fi
	
	msg "build $name-$version-$release success"
	
	# compress man pages
	if [ -d $PKG/usr/share/man ]; then
		# only keep man[1-8] man pages
		for m in $PKG/usr/share/man/*; do
			case ${m##*/} in
				man[1-8]) continue;;
			esac
			rm -rf $m
		done
		for i in $(find $PKG/usr/share/man -type f); do
			[ $i = ${i%%.gz} ] || continue
			gzip -9 $i
		done
		for i in $(find $PKG/usr/share/man -type l) ; do
			ln -s $(readlink $i).gz ${i%.gz}.gz
			rm $i
		done
	fi

	# compress info pages
	if [ -d $PKG/usr/share/info ]; then
		rm -f $PKG/usr/share/info/dir
		find $PKG/usr/share/info -type f -exec gzip -9 {} \;
	fi
	
	# possible conflicts
	find $PKG -name "fonts.dir" -exec rm {} \;
	find $PKG -name "fonts.scale" -exec rm {} \;
	find $PKG -name "perllocal.pod" -exec rm {} \;
	find $PKG -name "charset.alias" -exec rm {} \;
	
	if [ ! "$keep_static" ]; then
		find $PKG -name "*.a" -exec rm {} \;
	fi
	
	if [ ! "$keep_libtool" ]; then
		find $PKG -name "*.la" -exec rm {} \;
	fi
	
	if [ ! "$keep_locale" ]; then
		rm -rf $PKG/usr/share/locale
		rm -rf $PKG/usr/lib/locale
	fi
	
	if [ ! "$keep_doc" ]; then
		rm -rf $PKG/usr/share/doc
		rm -rf $PKG/usr/doc
	fi
	
	# strip binaries and libraries
	if [ ! "$no_strip" ]; then
		find $PKG | xargs file | grep "executable" | grep ELF | cut -f 1 -d : | xargs ${CROSS_COMPILE}strip --strip-all 2>/dev/null
		find $PKG | xargs file | grep "shared object" | grep ELF | cut -f 1 -d : | xargs ${CROSS_COMPILE}strip --strip-unneeded 2>/dev/null
		find $PKG | xargs file | grep "current ar archive" | cut -f 1 -d : | xargs ${CROSS_COMPILE}strip --strip-debug 2>/dev/null
	fi
	
	# usrmerge
	if [ "$APKG_BINMERGE" ]; then
		for b in bin sbin usr/sbin; do
			if [ -d $PKG/$b ]; then
				mkdir -p $PKG/usr/bin
				mv $PKG/$b/* $PKG/usr/bin
				rm -rf $PKG/$b
			fi
		done
	fi
		
	# runit service
	for s in $sv; do
		[ -f $SRC/$s ] || ret 1
		case $s in
				 run) install -Dm755 $SRC/$s $PKG/etc/sv/$name/run
					  ln -s ../../../run/runit/supervise.$name $PKG/etc/sv/$name/supervise;;
			  finish) install -Dm755 $SRC/$s $PKG/etc/sv/$name/finish;;
			   *.run) install -Dm755 $SRC/$s $PKG/etc/sv/${s%.*}/run
					  ln -s ../../../run/runit/supervise.${s%.*} $PKG/etc/sv/${s%.*}/supervise;;
			*.finish) install -Dm755 $SRC/$s $PKG/etc/sv/${s%.*}/finish;;
				 *.*) install -Dm644 $SRC/$s $PKG/etc/sv/${s%%.*}/${s#*.};;
				   *) install -Dm644 $SRC/$s $PKG/etc/sv/$name/$s;;
		esac
	done
		
	if [ ! "$(ls -1 $PKG)" ]; then
		msg "\$PKG is empty"
		ret 1
	fi
	
	# temporary: for backward compatibility
	if [ "$(grep '#MARKER' /usr/bin/spm)" ]; then
		cd $PKG
		spm -b $packagefile || {
			msg "Failed packaging $packagefile"
			ret 1
		}
	else
		cd $APKG_PACKAGE_DIR
		spm -b $PKG && {
			mv package.spm $packagefile
		} || {
			msg "Failed packaging $packagefile"
			ret 1
		}
	fi
	
	if [ ! -f "$HERE"/.files ] || [ "$HERE"/abuild -nt "$HERE"/.files ]; then
		pkg_updatefiles
	fi

	cd $HERE
	remove_workdir
}

pkg_updatefiles() {
	if [ ! -f "$packagefile" ]; then
		msg "Package '$packagefile' not found."
		ret 1
	fi
	msg "filelist updated."
	tar -tvf $packagefile | awk '{$3=$4=$5=""; print $0}' | sort -k 3 > "$HERE"/.files
}

pkg_path() {
	for p in $@; do
		for r in $APKG_REPO; do
			[ -f $r/$p/abuild ] && {
				printf "%s\n" $r/$p
				break
			}
		done
	done
}

die() {
	[ "$1" ] && printf "%s\n" "error: $1"
	ret 1
}

checkdep() {
	[ "$(pkg_path $1)" ] || return
	# track processed pkg to avoid cycle deps
	process="$process $1"
	for ii in $(pkg_depends $1); do
		# if already installed, skip
		if [ "$skip_installed" ]; then
			[ -s $SPM_PKGDB/$ii ] && continue
		fi
		# if deps already in process list, skip, cycle deps detected
		printf "%s\n" $process | tr ' ' '\n' | grep -qx $ii && continue
		# skip if itself in depends list
		[ "$ii" = "$1" ] && continue
		# skip if pkg already in deps list
		printf "%s\n" $DEPS | tr ' ' '\n' | grep -x $ii && continue
		# check deps
		checkdep $ii
	done
	# will go here if no deps anymore to check, add it to list deps
	DEPS="$DEPS $1"
}

pkg_deplist() {
	for i in $@; do
		# if already have in list deps, dont check again
		if [ ! $(printf "%s\n" $DEPS | tr ' ' '\n' | grep -x $i) ]; then
			checkdep $i
		fi
	done
	printf "%s\n" $DEPS | tr ' ' '\n'
}

pkg_fsearch() {
	for r in $APKG_REPO; do
		rr="$rr $r/*/.files"
	done
	grep $1 $rr 2>/dev/null | tr ':' ' ' | awk '{print $1,$4}' | sed 's,/.files,,g'
}

pkg_dependents() {
	for r in $APKG_REPO; do
		rr="$rr $r/*/depends"
	done
	grep -x $1 $rr 2>/dev/null | rev | cut -d / -f 2 | rev
}

pkg_depends() {
	_path=$(pkg_path $1) || return 1
	[ -s $_path/depends ] || return
	solve_alias $(grep -Ev ^'(#|$)' $_path/depends | awk '{print $1}')
}

solve_alias() {
	[ "$APKG_ALIAS" ] || {
		printf "%s\n" $@
		return
	}
	while [ "$1" ]; do
		d=$(printf "%s\n" $APKG_ALIAS | tr ' ' '\n' | grep ^$1:* | head -n1 | awk -F : '{print $2}')
		printf "%s\n" ${d:-$1}
		shift
	done
}

pkg_outdate() {
	verbose=1 pkg_allinstalled | while read -r n v; do
		if [ "$mask" ]; then
			[ "$APKG_MASK" ] && {
				printf "%s\n" "$APKG_MASK" | tr ' ' '\n' | grep -xq $n && continue
			}
		fi
		[ "$(pkg_path $n)" ] || continue
		nv="$(grep ^version= $(pkg_path $n)/abuild | tail -n1 | awk -F = '{print $2}')-$(grep ^release= $(pkg_path $n)/abuild | awk -F = '{print $2}')"
		[ "$nv" ] || continue
		[ "$v" = "$nv" ] || printf "%s\n" "$n $v -> $nv"
	done
}

pkg_remove() {
	needroot
	for i in $@; do
		SPM_ROOT=$APKG_ROOT spm -r $i
	done
}

pkg_sysup() {
	needroot
	msg "Checking for outdated packages..."
	od=$(mask=1 pkg_outdate | awk '{print $1}')
	if [ ! "$od" ]; then
		msg "No outdated packages."
		exit 0
	else
		msg "Solving dependencies..."
		for p in $(pkg_deplist $od); do
			[ -s $SPM_PKGDB/$p ] || ni="$ni $p"
			printf "%s\n" $od | tr ' ' '\n' | grep -qx $p && pu="$pu $p"
		done
		if [ "$ni" ]; then
			newpkg=$(printf "%s\n" $ni | tr ' ' '\n' | wc -l)
			msg "Installing $newpkg new package(s): $ni"
		fi
		totalpkg=$(printf "%s\n" $pu | tr ' ' '\n' | wc -l)
		msg "Upgrading $totalpkg package(s): $pu"
		prompt_user
		if [ "$ni" ]; then
			$APKG -i $ni #|| die
		fi
		$APKG -u $pu #|| die
	fi
}

pkg_search() {
	if [ ! "$APKG_REPO" ]; then
		msg "No repo configured."
		exit 1
	fi
	if [ "$pkg" ]; then
		for p in $pkg; do
			grep="$grep -e $p"
		done
		grep="grep $grep"
	else
		grep=cat
	fi
	[ "$verbose" ] && {
		find $APKG_REPO -type f -name abuild 2>/dev/null | sed 's|/abuild||' | rev | awk -F / '{print $1}' | rev | $grep | while read line; do printf "$line "; awk -F'=' '/^version/ {print $2}' $($APKG -p $line)/abuild ; done
	} || {
		find $APKG_REPO -type f -name abuild 2>/dev/null | sed 's|/abuild||' | rev | awk -F / '{print $1}' | rev | $grep
	}
}

pkg_depinstalll() {
	needroot
	for p in $@; do
		if [ -s $SPM_PKGDB/$p ]; then
			msg "Package '$p' is installed"
			continue
		fi
		if [ ! "$(pkg_path $p)" ]; then
			msg "Package '$p' not found"
			continue
		fi
		pkg="$pkg $p"
	done
	[ "$pkg" ] || exit 0
	set -- $pkg
	msg "Solving dependencies..."
	for p in $(skip_installed=1 pkg_deplist $@); do
		[ -s $SPM_PKGDB/$p ] && continue
		installthis="$installthis $p"
	done
	if [ ! "$installthis" ]; then
		msg "Nothing to install. Exiting..."
		exit 0
	fi
	totalpkg=$(printf "%s\n" $installthis | tr ' ' '\n' | wc -l)
	msg "Installing $totalpkg package(s): $installthis"
	prompt_user
	$APKG -i $installthis || die
}

_trigger_fccache() {
	command -v fc-cache >/dev/null || return 0
	spm -l $1 | grep -q ^usr/share/fonts/$ && {
		msg "Updating fontconfig cache..."
		fc-cache -sf
	}
}

_trigger_gdkpixbufcache() {
	command -v gdk-pixbuf-query-loaders >/dev/null || return 0
	spm -l $1 | grep -q ^usr/lib/gdk-pixbuf-2.0/2.10.0/loaders/$ && {
		msg "Probing GDK-Pixbuf loader modules..."
		gdk-pixbuf-query-loaders --update-cache
	}
}

_trigger_giomodules() {
	command -v gio-querymodules >/dev/null || return 0
	spm -l $1 | grep -q ^usr/lib/gio/modules/$ && {
		msg "Updating GIO module cache..."
		gio-querymodules /usr/lib/gio/modules
	}
}

_trigger_glibschema() {
	command -v glib-compile-schemas >/dev/null || return 0
	spm -l $1 | grep -q ^usr/share/glib-2.0/schemas/$ && {
		msg "Compiling GSettings XML schema files..."
		glib-compile-schemas /usr/share/glib-2.0/schemas
	}
}

_trigger_gtk2immodules() {
	command -v gtk-query-immodules-2.0 >/dev/null || return 0
	spm -l $1 | grep -q ^usr/lib/gtk-2.0/2.10.0/immodules/$ && {
		msg "Probing GTK2 input method modules..."
		gtk-query-immodules-2.0 --update-cache
	}
}

_trigger_gtk3immodules() {
	command -v gtk-query-immodules-3.0 >/dev/null || return 0
	spm -l $1 | grep -q ^usr/lib/gtk-3.0/3.0.0/immodules/$ && {
		msg "Probing GTK3 input method modules..."
		gtk-query-immodules-3.0 --update-cache
	}
}

_trigger_iconcache() {
	command -v gtk-update-icon-cache >/dev/null || return 0
	spm -l $1 | grep -q ^usr/share/icons/$ && {
		msg "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
	}
}

_trigger_udevdb() {
	command -v udevadm >/dev/null || return 0
	spm -l $1 | grep -q ^etc/udev/hwdb.d/$ && {
		msg "Updating hardware database..."
		udevadm hwdb --update
	}
}

_trigger_fontcache() {
	command -v mkfontdir >/dev/null || return 0
	command -v mkfontscale >/dev/null || return 0
	spm -l $1 | grep -q ^usr/share/fonts/$ && {
		msg "Updating X fontdir indices..."
		for dir in $(find /usr/share/fonts -maxdepth 1 -type d \( ! -path /usr/share/fonts \)); 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
	}
}

_trigger_desktopdb() {
	command -v update-desktop-database >/dev/null || return 0
	spm -l $1 | grep -q ^usr/share/applications/$ && {
		msg "Updating desktop file MIME type cache..."
		update-desktop-database --quiet
	}
}

_trigger_mimedb() {
	command -v update-mime-database >/dev/null || return 0
	spm -l $1 | grep -q ^usr/share/mime/$ && {
		msg "Updating the MIME type database..."
		update-mime-database /usr/share/mime
	}
}

pkg_trigger() {
	needroot
	[ "$@" ] || set -- $($APKG -a)
	for t in mimedb desktopdb fontcache fccache udevdb iconcache gtk3immodules gtk2immodules glibschema giomodules gdkpixbufcache; do
		for p in $@; do
			_trigger_$t $p && break
		done
	done		
}

pkg_allinstalled() {
	if [ "$pkg" ]; then
		for p in $pkg; do
			grep="$grep -e $p"
		done
		grep="grep $grep"
	else
		grep=cat
	fi
	[ "$verbose" ] && {
		ls -1 $SPM_PKGDB | $grep | while read line; do printf "$line "; head -n1 $SPM_PKGDB/$line ; done
	} || {
		ls -1 $SPM_PKGDB | $grep
	}
}

checksum_src() {	
	if [ ! -s "$checksum_file" ]; then
		gencsum
	else
		b3sumcheck || return 0
		printcsum > ${checksum_file}.tmp
		diff -U 0 ${checksum_file} ${checksum_file}.tmp | \
			sed '/---/d;/+++/d;/@@/d' | \
			sed 's/^+/ new    : /' | \
			sed 's/^-/ missing: /' > ${checksum_file}.diff
			[ -s "${checksum_file}.diff" ] && {
				cat ${checksum_file}.diff
				msg "Checksum failed."
				failed=1
			}
		rm -f ${checksum_file}.tmp ${checksum_file}.diff
		[ "$failed" ] && ret 1
	fi
}

printsource() {
	# print out source full path
	for printsource in $source; do
		printsource=${printsource%::noextract}
		case $printsource in
			*::*) sourcename=${printsource%::*};;
			   *) sourcename=${printsource##*/};;
		esac
		case ${printsource#*::} in
			*/*) sourcenamefullpath=${APKG_SOURCE_DIR}/$sourcename;;
			  *) sourcenamefullpath=${HERE}/$sourcename;;
		esac
		printf "%s\n" $sourcenamefullpath
	done
}

checksource() {
	# check for source existence, exit non-zero if missing
	for checksource in $(printsource); do
		[ -f "$checksource" ] || {
			msg "Source file '$checksource' not found."
			failed=1
		}
	done
	[ "$failed" ] && ret 1
}

b3sumcheck() {
	command -v b3sum >/dev/null || { msg "'b3sum' not installed."; return 1; }
}

gencsum() {
	checksource
	b3sumcheck || return 0
	rm -f .checksum
	printcsum > .checksum
	msg "Checksum file updated."
}

printcsum() {
	for printcsum in $(printsource); do
		[ -f "$printcsum" ] || continue
		b3sum $printcsum | sed "s,$APKG_SOURCE_DIR/,,;s,$HERE/,,"
	done | sort -k 2
}

prompt_user() {
	if [ ! "$APKG_NOPROMPT" ]; then
		msg "Press ENTER to continue operation."
		msg "Press Ctrl + C to abort."
		read -r null
	fi
}

runscript() {
	if [ -x ./${1}install ]; then
		msg "Running ${1}install script..."
		if [ "$APKG_ROOT" ]; then
			cat ./${1}install > "$APKG_ROOT"/.runscript
			chmod +x "$APKG_ROOT"/.runscript
			chroot "$APKG_ROOT" /.runscript
			rm -f "$APKG_ROOT"/.runscript
		else
			./${1}install
		fi
	fi
}

needroot() {
	[ $(id -u) = 0 ] || {
		msg 'This operation need root access'
		exit 1
	}
}

parsesubopt() {
	# loop opts until found '-*'
	shift; while [ "$1" ]; do
		case $1 in
			-*) break;;
			*) printf "%s\n" "$1";;
		esac
		shift
	done
}

updateopts() {
	while [ "$1" ]; do
		case $1 in
			-*) newopt="$newopt $1";;
		esac
		shift
	done
	printf "%s\n" "$newopt"
}

parseopts() {
	while [ "$1" ]; do
		case $1 in
			-I) pkg_depinstalll "$(parsesubopt "$@")"; exit 0;;
			-D) pkg_deplist "$(parsesubopt "$@")"; exit 0;;
			-r) pkg_remove "$(parsesubopt "$@")"; exit 0;;
			-p) pkg_path "$(parsesubopt "$@")"; exit 0;;
			-t) pkg_trigger "$(parsesubopt "$@")"; exit 0;;
			-U) pkg_sysup; exit 0;;
			-d) pkg_depends "$2"; exit 0;;
			-s) search=1;;
			-S) pkg_fsearch "$2"; exit 0;;
			-j) pkg_dependents "$2"; exit 0;;
			-a) allinstalled=1;;
			-h) apkg_help; exit 0;;
			-l) pkg_outdate; exit 0;;
			-g) updatecsum=1;;
			-k) updatefiles=1;;
			-f) forcerebuild=1;;
			-i) install=1;;
			-u) upgrade=1;;
			-o) downloadonly=1;;
			-v) verbose=1;;
			-*) msg "invalid option '$1'"; exit 1;;
			*) pkg="$pkg $1";;
		esac
		shift
	done
}

apkg_help() {
	cat << EOF
usage: ${0##*/} <option> <arg(s)>

options:
  -i <pkg(s)>  install package(s)
  -I <pkg(s)>  install packages(s) with dependencies
  -d <pkg>     list <pkg> dependencies
  -D <pkg(s)>  list all dependencies
  -j <pkg>     list all dependents
  -u <pkg(s)>  upgrade package(s)
  -r <pkg(s)>  remove package(s)
  -t [pkg(s)]  trigger system cache/db updates
  -U           update system
  -f           force rebuild
  -o <pkg(s)>  download source
  -p <pkg>     print package path
  -g <pkg>     update package checksum
  -k <pkg>     update package files
  -l           list outdated packages
  -s <pattern> search packages
  -S <pattern> search files
  -v           be verbose
  -h           print this help message
  
environment variables:
  APKG_ROOT          override default root (${APKG_ROOT:-/}) location
  APKG_CONF          override default $APKG_CONF location
  APKG_NOPROMPT      set the variable for non-interactive use
  APKG_LOG           set the variable for logging the output to a file
  
EOF
exit 0
}

main() {
	parseopts "$@"
	set -- $(updateopts "$@")
	
	if [ "$search" ]; then
		pkg_search $@
		exit 0
	fi
	
	if [ "$allinstalled" ]; then
		pkg_allinstalled $@
		exit 0
	fi
	
	for d in $APKG_PACKAGE_DIR $APKG_SOURCE_DIR $APKG_WORK_DIR $APKG_LOG_DIR; do
		[ -d "$d" ] || { msg "Directory '$d' not exist"; exit 1; }
		[ -w "$d" ] || { msg "Directory '$d' dont have write access"; exit 1; }
	done
	
	if [ "$pkg" ]; then
		for p in $pkg; do
			[ "$(pkg_path "$p")" ] || {
				msg "Package '$p' not found"
				continue
			}
			(cd "$(pkg_path "$p")" && $APKG "$@") || exit 1
		done
		exit 0
	fi

	if [ ! -f ./abuild ]; then
		msg "'abuild' not found."
		exit 1
	fi
	
	. ./abuild
	[ "$name" ]    || die "name is empty"
	[ "$version" ] || die "version is empty"
	[ "$release" ] || die "release is empty"
	[ "$name" = "${PWD##*/}" ] || {
		msg "'name' should be same as package directory name (${PWD##*/})."
		exit 1
	}
	
	logfile="$APKG_LOG_DIR"/$name.log
	packagefile="$APKG_PACKAGE_DIR"/$name#$version-$release.spm

	if [ "$install" ] && [ -s "$SPM_PKGDB/${PWD##*/}" ]; then
		msg "Package '${PWD##*/}' already installed."
		exit 0
	fi
	
	if [ "$upgrade" ] && [ ! -s "$SPM_PKGDB/${PWD##*/}" ]; then
		msg "Package '${PWD##*/}' not installed."
		exit 0
	fi
	
	if [ "$updatecsum" ]; then
		gencsum
		exit 0
	fi
	
	if [ "$downloadonly" ]; then
		fetch_src
		exit 0
	fi
	
	if [ "$updatefiles" ]; then
		pkg_updatefiles
		exit 0
	fi
	
	if [ "$(id -u)" != 0 ]; then
		msg "Packages need to build as root. Aborted."
		exit 1
	fi
	
	if [ -f "$packagefile" ] && [ ! "$forcerebuild" ]; then
		if [ ! "$install" ] && [ ! "$upgrade" ]; then
			msg "Package '$packagefile' found."
		fi
	else	
		if [ "$install" ] || [ "$upgrade" ]; then
			# run preinstall script before build
			runscript pre
		fi
		fetch_src
		checksum_src
		extract_src
		[ "$APKG_LOG" ] && {
			build_src 2>&1 | tee -- "$logfile"
		} || {
			build_src
		}
	fi	
	
	if [ "$install" ]; then
		SPM_ROOT=${APKG_ROOT%/} spm -i "$packagefile" || exit $?
	elif [ "$upgrade" ]; then
		SPM_ROOT=${APKG_ROOT%/} spm -u "$packagefile" || exit $?
	fi
	
	if [ "$install" ] || [ "$upgrade" ]; then
		# run postinstall script after installed/upgraded
		runscript post
		[ "$APKG_ROOT" ] || pkg_trigger "$name"
	fi
}

umask 022

export HERE="$PWD"

APKG=$0
SPM_PKGDB="${APKG_ROOT%/}/var/lib/spm/db"

# for apkg config, either override or default
APKG_CONF="${APKG_CONF:-/etc/apkg.conf}"

## NEED BETTER WAY TO DO THIS?
# environment override
O_APKG_REPO=$APKG_REPO
O_APKG_PACKAGE_DIR=$APKG_PACKAGE_DIR
O_APKG_SOURCE_DIR=$APKG_SOURCE_DIR
O_APKG_WORK_DIR=$APKG_WORK_DIR
O_APKG_LOG_DIR=$APKG_LOG_DIR
O_APKG_MASK=$APKG_MASK

# default value
APKG_REPO="$PWD"
APKG_PACKAGE_DIR="$PWD"
APKG_SOURCE_DIR="$PWD"
APKG_WORK_DIR="$PWD"
APKG_LOG_DIR="$PWD"

checksum_file="$HERE/.checksum"

# source config
if [ -f "$APKG_CONF" ]; then
	. "$APKG_CONF"
fi

# reset value, use override first, else either from config or use default
APKG_REPO=${O_APKG_REPO:-$APKG_REPO}
APKG_PACKAGE_DIR=${O_APKG_PACKAGE_DIR:-$APKG_PACKAGE_DIR}
APKG_SOURCE_DIR=${O_APKG_SOURCE_DIR:-$APKG_SOURCE_DIR}
APKG_WORK_DIR=${O_APKG_WORK_DIR:-$APKG_WORK_DIR}
APKG_LOG_DIR=${O_APKG_LOG_DIR:-$APKG_LOG_DIR}
APKG_MASK=${O_APKG_MASK:-$APKG_MASK}

main "$@"

exit 0
