#!/bin/sh
#   ______   ______  __    __
#  /\  ___\ /\  == \/\ "-./  \
#  \ \___  \\ \  _-/\ \ \-./\ \
#   \/\_____\\ \_\   \ \_\ \ \_\
#    \/_____/ \/_/    \/_/  \/_/
#
# SPM - Simple Package manager (C) 2023-2026 Emmett1
#

msg()        { echo "[${name:-...}] $*"; }
msgerr()     { echo "[${name:-...}] ERROR: $*"; exit 1; }
needarg()    { [ "$1" ]      || msgerr "This operation need argument(s). Aborted."; }
checkdbdir() { [ -d $PKGDB ] || msgerr "Database directory '$PKGDB' not exist. Aborted"; }

checkpkgname() {
	name=${1%#*}
	version=${1#*#}; version=${version%%-*}
	release=${1##*-}; release=${release%.spm}
	
	[ "$name#$version-$release.spm" = "$1" ] || \
		msgerr "Valid 'spm' package format is '<name>#<version>-<release>.spm'"
}

spm_build() {
	needarg "$1"
	case $1 in
		*/*) [ -d "${1%/*}" ] || msgerr "'${1%/*}' directory not exist.";;
	esac
	
	checkpkgname ${1##*/}

	if [ -f "/tmp/spmpackage.lock" ]; then
		msg "Theres existing packaging process running..."
		msg "remove '/tmp/spmpackage.lock' if theres no packaging process running."
	fi
	while [ -f "/tmp/spmpackage.lock" ]; do
		count=$((count+1))
		printf "[${name:-...}] wait for existing packaging process complete ($count)...\033[0K\r"
		sleep 1
	done
	printf "\033[0K"
	
	touch "/tmp/spmpackage.lock"
	
	rm -f $1
	
	# move all files in etc/ to .new suffix
	if [ -d etc ]; then
		for d in $(find etc -type f); do
			[ -f $d ] || continue
			case $d in *.new) continue;; esac
			mv $d $d.new
		done
	fi
	
	SPM_COMPRESSION=${SPM_COMPRESSION:-gzip}
	case $SPM_COMPRESSION in
		bzip2) sc=-j;;
		   xz) sc=-J;;
		    *) sc=-z;;
	esac
	
	msg "Packaging $PWD."
	tar -c $sc -f $1 * || {
		msgerr "Failed packaging $PWD."
	}
	rm -f "/tmp/spmpackage.lock"
	
	tar -tvf $1
	msg "Package created: $1."
	
	exit 0
}

spm_install() {
	checkdbdir
	needarg $1
	if [ "$(id -u)" != 0 ] || [ "$FAKEROOTKEY" ]; then
		msgerr "root required to install package into system."
	fi
		
	[ -f "$1" ] || msgerr "Package '$1' not exist."
	
	checkpkgname ${1##*/}
	
	# if upgrade but not already installed, exit
	if [ "$upgrade" ] && [ ! -s $PKGDB/$name ]; then
		msgerr "Package '$name' not installed, use '-i' to install."
	fi
	
	# if already installed and not an upgrade, exit
	if [ -s $PKGDB/$name ] && [ ! "$upgrade" ]; then
		msgerr "Package '$name' is installed, use '-u' to reinstall/upgrade."
	fi
	
	if [ -f "/tmp/spminstall.lock" ]; then
		msg "Theres existing install process running..."
		msg "remove '/tmp/spminstall.lock' if theres no install process running."
	fi
	while [ -f "/tmp/spminstall.lock" ]; do
		count=$((count+1))
		printf "[${name:-...}] wait for existing install process complete ($count)...\033[0K\r"
		sleep 1
	done
	printf "\033[0K"
	
	touch "/tmp/spminstall.lock"	
	rm -f "$SPMDIR"/*.list
	
	# check if package is corrupt
	msg "Verify package..."
	tar -tvf "$1" > $SPMDIR/files.list 2>/dev/null || {
		msg "Package '$1' is corrupted. Aborted."
		rm -f "$SPMDIR"/*.list "/tmp/spminstall.lock"
		exit 1
	}
	
	# check conflicting files
	msg "Checking for conflicts..."
	if [ "$upgrade" ]; then
		tail -n+2 $PKGDB/$name > $SPMDIR/installed.list
		awk '{print $6}' $SPMDIR/files.list | sed 's/\.new$//g' > $SPMDIR/files2.list
		grep -Fxv -f $SPMDIR/installed.list $SPMDIR/files2.list > $SPMDIR/newfiles.list
	else
		awk '{print $6}' $SPMDIR/files.list | sed 's/\.new$//g' > $SPMDIR/newfiles.list
	fi
	cat $SPMDIR/newfiles.list | while read -r line; do
		[ "$line" = "${line%.new}.new" ] && line=${line%.new}
		# instant conflict if cross, mean file to directory and vice versa
		case $line in
			*/[|*/[[) continue;; # TODO: fix detected '[' and '[[' as conflicted
			*/) if [ -e "${SPM_ROOT%/}"/"$line" ] && [ ! -d "${SPM_ROOT%/}"/"$line" ]; then
					echo "$line" >> $SPMDIR/conflict.list; continue
				fi;;
			*) 	if [ -e "${SPM_ROOT%/}"/"$line" ] && [ -d "${SPM_ROOT%/}"/"$line" ] && [ ! -L "${SPM_ROOT%/}"/"$line" ]; then
					echo "$line" >> $SPMDIR/conflict.list; continue
				fi;;
		esac
		if [ -e "${SPM_ROOT%/}"/"$line" ]; then
			case $line in */) continue;; esac # if directory not conflict
			echo "$line" >> $SPMDIR/conflict.list
		fi
	done
	
	if [ -f "$SPMDIR/conflict.list" ] && [ ! "$SPM_FORCEINSTALL" ]; then
		cat "$SPMDIR/conflict.list"
		msg "File conflict found."
		rm -f "$SPMDIR"/*.list "/tmp/spminstall.lock"
		exit 1
	fi
	
	msg "$([ $upgrade ] && echo Upgrading || echo Installing) package..."
	tar -xvhpf "$1" -C "${SPM_ROOT%/}/" > "$SPMDIR/extract.list" 2>/dev/null || {
		msg "Package extraction failed. Aborted."
		rm -f "/tmp/spminstall.lock" "$SPMDIR"/*.list
		exit 1
	}

	while read -r line; do
		if [ "$line" = "${line%.new}.new" ]; then
			line=${line%.new}
			# if file not exist or same file, just move it
			if [ ! -e "$SPM_ROOT/$line" ] || diff "$SPM_ROOT/$line".new "$SPM_ROOT/$line" >/dev/null; then
				mv -f "$SPM_ROOT/$line".new "$SPM_ROOT/$line"
			fi
		fi
		[ "$SPM_VERBOSE" ] && echo "installed '$line'"
		echo "$line"
	done < "$SPMDIR/extract.list" > "$SPMDIR/install.list"

	if [ "$upgrade" ]; then
		cd $PKGDB
		# remove files from old package thats not in new package
		tail -n+2 $name > $SPMDIR/old.list
		grep -Fxv -f $SPMDIR/install.list $SPMDIR/old.list | tac | while read -r line; do
			case $line in
				*/) grep -x "$line" * | grep -v $name: >/dev/null || rmdir $VERBOSE "${SPM_ROOT%/}"/"$line";;
				*) rm $VERBOSE "${SPM_ROOT%/}"/"$line";;
			esac
		done
		cd - >/dev/null
	fi		
		
	# register package into db
	mkdir -p $PKGDB
	echo "$version-$release" > $PKGDB/$name
	cat $SPMDIR/install.list >> $PKGDB/$name
	
	# permission
	mkdir -p $PERMDIR $OWNDIR
	rm -f "$PERMDIR/$name" "$OWNDIR/$name"
	grep ^d $SPMDIR/files.list | awk '{print $1,$2,$6}' | while read -r perms owner dir; do
		if [ "$perms" != "drwxr-xr-x" ] && [ "$perms" != "drwxrwxr-x" ]; then
			echo "$(cvperms $perms) $dir" >> $PERMDIR/$name
		fi
		[ "$owner" = "root/root" ]  || echo "$owner $dir" | sed 's,/,:,' >> $OWNDIR/$name
	done
	
	# correct dir permission
	[ "$(ls -1 $PERMDIR)" ] && {
		cat $PERMDIR/* | while read -r perms dir; do
			[ -d "${SPM_ROOT%/}"/"$dir" ] || continue
			chmod "$perms" "${SPM_ROOT%/}"/"$dir"
		done
	}
	# correct dir ownership
	[ "$(ls -1 $OWNDIR)" ] && {
		cat $OWNDIR/* | while read -r owner dir; do
			[ -d "${SPM_ROOT%/}"/"$dir" ] || continue
			chown "$owner" "${SPM_ROOT%/}"/"$dir"
		done
	}
	
	[ -x /sbin/ldconfig ] && /sbin/ldconfig -r "${SPM_ROOT:-/}"
	
	msg "Package '$name#$version-$release' $([ $upgrade ] && echo upgraded. || echo installed.)"
	
	# remove lock file & temporary list files
	rm -f "/tmp/spminstall.lock" "$SPMDIR"/*.list
	
	exit 0
}

cvperms() {
	# converts symbolic to numeric permissions
	# required an input (symbolic, eg: drwxr-xr-x)
	s=0; n=0; count=0
	for i in $(echo "$1" | sed -e 's/\(.\)/\1\n/g'); do
		count=$((count+1))
		case $i in
			d) ;;
			r) n=$((n+4));;
			w) n=$((n+2));;
			x) n=$((n+1));;
			s) [ $count = 4 ] && s=$((s+4)) || s=$((s+2)); n=$((n+1));;
			t) s=$((s+1));n=$((n+1));;
			S) s=$((s+2));;
			T) s=$((s+1));;
		esac
		[ "$count" = 4 ] && {
			user=$n; n=0
		}
		[ "$count" = 7 ] && {
			group=$n; n=0
		}
		[ "$count" = 10 ] && {
			other=$n; n=0
		}
	done
	echo "$s$user$group$other"
}


spm_remove() {
	[ "$(id -u)" = 0 ] || msgerr "root required to remove package from system."
	checkdbdir
	needarg $1
	if [ ! -s $PKGDB/$1 ]; then
		msgerr "Package '$1' not installed."
	fi
	
	cd "${SPM_ROOT:-/}"
	
	# handle filenames with spaces individually (rm/rmdir), then bulk-filter them from db
	grep ' ' "$PKGDB/$1" | while read -r line; do
		case $line in
			*/) rmdir $VERBOSE "$line";;
			*)  rm $VERBOSE "$line";;
		esac
	done
	grep -v ' ' "$PKGDB/$1" > "$SPMDIR/remove.tmp" && mv "$SPMDIR/remove.tmp" "$PKGDB/$1"

	# remove remaining files (no spaces, safe to bulk-expand)
	rm $VERBOSE $(tail -n+2 "$PKGDB/$1" | tac | grep -v /$)
	
	# list all dirs except for target pkg
	grep /$ $PKGDB/* | grep -v /$1: | awk -F : '{print $2}' | sort | uniq > $SPMDIR/remove.all
	grep /$ $PKGDB/* | grep /$1:    | awk -F : '{print $2}' | sort | uniq > $SPMDIR/remove.target
	grep -Fxv -f $SPMDIR/remove.all $SPMDIR/remove.target | sort | tac > $SPMDIR/remove.rmlist
	[ -s $SPMDIR/remove.rmlist ] && rmdir $VERBOSE $(sort $SPMDIR/remove.rmlist | tac)
	
	rm -f $PKGDB/$1 $PERMDIR/$1 $OWNDIR/$1 $SPMDIR/remove.*
	msg "Package '$1' removed."
	
	exit 0
}

spm_allinstalled() {
	checkdbdir
	(cd $PKGDB && echo * | tr ' ' '\n')
	exit 0
}

spm_listfiles() {
	checkdbdir
	needarg $1
	[ -s $PKGDB/$1 ] || msgerr "Package '$1' not installed."
	tail -n+2 $PKGDB/$1
	exit 0
}

spm_owner() {
	checkdbdir
	needarg "$1"
	f=${1#/}
	(cd "$PKGDB" && grep -F "$f" * | tr ':' ' ')
	exit 0
}


spm_help() {
	cat << EOF
usage: ${0##*/} -[a|b|h|i|l|r|o|u] <arg(s)>

options:
  -a         print all installed packages
  -b <pkg>   path to package name
  -h         print this help message
  -i <file>  install <file> package into system
  -l <pkg>   list files installed by <pkg>
  -r <name>  remove installed <name> from system
  -o <file>  print owner of <file>
  -u <pkg>   reinstall/upgrade <pkg>
  
environments:
  SPM_ROOT          override default root (/) location
  SPM_FORCEINSTALL  force package installation
  SPM_VERBOSE       be verbose
  SPM_COMPRESSION   packaging compression; gzip (default) / bzip2 / gzip
  
EOF
exit 0
}

if [ "$SPM_ROOT" ]; then
	[ -d "$SPM_ROOT" ] || msgerr "'$SPM_ROOT' directory not exist."
	SPM_ROOT=$(realpath $SPM_ROOT)
fi

if [ "$SPM_VERBOSE" ]; then
	VERBOSE=-v
else
	VERBOSE=
fi

HERE=$PWD
SPMDIR=$SPM_ROOT/var/lib/spm
PKGDB=$SPMDIR/db
PERMDIR=$SPMDIR/perms
OWNDIR=$SPMDIR/owner

if [ "$1" ]; then
	case $1 in
		-b) spm_build $2;;
		-i) spm_install $2;;
		-u) upgrade=1; spm_install $2;;
		-r) spm_remove $2;;
		-a) spm_allinstalled;;
		-l) spm_listfiles $2;;
		-o) spm_owner $2;;
		-h) spm_help;;
	esac
else
	echo "run '${0##*/} -h' to see help message."
	exit 0
fi

exit 0
