#!/bin/sh
# Simple GUI driver for build-kernel.sh Fatdog64 kernel builder
# Copyright (C) James Budiono 2026
# License: GNU GPL Version 3.0 or later
#
# Version 1: June 2026: Initial version
#

### config
MINSPACE=${MINSPACE:-6} # in GB, minimum freespace in the directory

### constants
VERSION=1.1
COMPAT_VERSIONS="2.5 2.6" # space separated
APPTITLE="Fatdog64 Kernel Builder"
STATE_DIR=0state # share the same state-dir as build-kernel.sh

### variables
KVER= USE_AUFS= AUFS= AUFS_UTIL=
CONFIG_SOURCE= DOTCONFIG_PATH=
PAUSE_AT_PACKAGE=
FORCE_REDOWNLOAD=

########## UI helpers #########

# $1-info
msgbox() {
	if [ $DISPLAY ]; then
		Xdialog --title "$APPTITLE" --msgbox "$1" 0 0 10000
	else
		dialog --backtitle "$APPTITLE" --msgbox "$1" 0 0 > /dev/stderr
	fi
}

# $1-info
infobox() {
	if [ $DISPLAY ]; then
		Xdialog --title "$APPTITLE" --infobox "$1" 0 0 10000
	else
		dialog --backtitle "$APPTITLE" --infobox "$1" 5 50
	fi
}

# $1-text, $2-choices, output: stdout
choices() {
	if [ $DISPLAY ]; then
		eval Xdialog --title \"$APPTITLE\" --stdout --no-tags --menubox \""$1"\" 20 100 5 $2
	else 
		eval dialog --backtitle \"$APPTITLE\" --stdout --no-tags --menu \""$1"\" 0 0 0 $2
	fi
}

# $1-text
yesno() {
	if [ $DISPLAY ]; then
		Xdialog --title "$APPTITLE" --yesno "$1" 0 0
	else
		dialog --title "$APPTITLE" --yesno "$1" 0 0
	fi
}

# $1-text
die() {
	infobox "$1"
	exit
}

# $1-prompt, $2-current value
inputbox() {
	if [ $DISPLAY ]; then
		Xdialog --title "$APPTITLE" --stdout --inputbox "$1" 0 0 "$2"
	else
		dialog --title "$APPTITLE" --stdout --inputbox "$1" 0 0 "$2"		
	fi
}

# $1-prompt
fselect() {
	if [ $DISPLAY ]; then
		Xdialog --title "$APPTITLE" --backtitle "$1" --stdout --no-buttons --fselect "$PWD" 0 0
	else
		dialog --title "$APPTITLE" --backtitle "$1" --stdout --fselect "$PWD"/ 10 70
	fi
}

# $1-prompt $2-show-cancel if not empty
tailbox() {
	local tmpfile=/tmp/gui-build-kernel.$$
	local NOCANCEL="--no-cancel"
	[ "$2" ] && NOCANCEL=

	if [ $DISPLAY ]; then
		Xdialog --title "$APPTITLE" $NOCANCEL --backtitle "$1" --tailbox - 30 80
	else
		cat > $tmpfile
		dialog --title "$APPTITLE" --backtitle "$1" --textbox $tmpfile 0 0
		rm $tmpfile
	fi
}


########## helpers #########

# $1-major version, $2-force redownload
get_kernel_list_major() {
	local KMAJOR=$1 FORCE=$2 HTML=kernel${1}.html TXT=kernel${1}.txt ALLVER p
	
	if ! [ -e $STATE_DIR/$TXT ] || [ "$FORCE" ]; then
		wget "https://www.kernel.org/pub/linux/kernel/v${KMAJOR}.x/" -O $STATE_DIR/$HTML
		sed '/linux-.*.tar.xz/!d; s/.*href="//;s/".*//;s/.tar.xz//;s/linux-//' $STATE_DIR/$HTML |
		sort -V > $STATE_DIR/$TXT	
	fi

	ALLVER=$(cat $STATE_DIR/$TXT)
	for p in $(seq 0 30); do echo "$ALLVER" | grep -F $KMAJOR.$p. | tail -n1; done
}

#$1-force
get_kernel_list_all() {
	local p
	for p in $(seq 4 7); do
		get_kernel_list_major $p $1
	done | sort -rV
}

# $1-kernel version, $2-force redownload
get_compat_aufs_version() {
	local KVER=$1 FORCE=$2
	
	OIFS="$IFS"
	IFS=.
	set -- $1
	IFS="$OIFS"

	local KMAJOR=$1 KMINOR=$2 KPATCH=$3
	local JSON= TXT=

	case $KMAJOR in
		4) JSON=aufs${KMAJOR}.json TXT=aufs${KMAJOR}.txt ;;
		*) JSON=aufs.json TXT=aufs.txt ;;
	esac
	
	if ! [ -e $STATE_DIR/$JSON ] || [ "$FORCE" ]; then
		case $KMAJOR in
			4) wget "https://api.github.com/repos/sfjro/aufs4-standalone/branches?per_page=100" -O $STATE_DIR/$JSON ;;
			*) wget "https://api.github.com/repos/sfjro/aufs-standalone/branches?per_page=100" -O $STATE_DIR/$JSON ;;
		esac
		sed '/name/!d; s/.*name": "//; s/".*//' $STATE_DIR/$JSON | sort -rV > $STATE_DIR/$TXT
	fi
	grep "^aufs$KMAJOR\\.$KMINOR" $STATE_DIR/$TXT
}

#$1-kernel ver $2-force redownload
get_compat_aufsutil_version() {
	local KVER=$1 FORCE=$2
	
	OIFS="$IFS"
	IFS=.
	set -- $1
	IFS="$OIFS"

	local KMAJOR=$1 KMINOR=$2 KPATCH=$3
	local HTML=aufsutil.html TXT=aufsutil.txt ALLVER p
	
	if ! [ -e $STATE_DIR/$HTML ] || [ "$FORCE" ]; then
		wget "https://sourceforge.net/p/aufs/aufs-util/ref/master/branches/" -O $STATE_DIR/$HTML
		sed '/tree/!d; s/.*nofollow">//; s/<.*//' $STATE_DIR/$HTML | sort -rV > $STATE_DIR/$TXT
	fi
	grep "^aufs$KMAJOR\\.$KMINOR" $STATE_DIR/$TXT || grep "^aufs$KMAJOR\\.0" $STATE_DIR/$TXT
}


################## build steps #####################
### they all implicitly uses the variables declared at the top of this file.

#########################
read_choices() {
	# 1. kernel version
	KVER=$(choices "Choose kernel version to build" "other 'Other versions' $(get_kernel_list_all $FORCE_REDOWNLOAD | sed 's/.*/& "Linux &"/')") ||
	die "Cancelled"

	if [ $KVER = "other" ]; then
		KVER=$(inputbox "Type the kernel version you want to build" "") ||
		die "Cancelled"
	fi

	# 2. get aufs option
	if yesno "Do you want to use aufs?"; then
		USE_AUFS=yes

		AUFS=$(choices "Choose the closest aufs branch to $KVER" "$(get_compat_aufs_version $KVER $FORCE_REDOWNLOAD | sed 's/.*/& "&"/') other 'Others'")
		if [ $AUFS = "other" ]; then
			AUFS=$(inputbox "Type the aufs branch you want to use" "")
		fi || die "Cancelled"
		
		AUFSUTIL=$(choices "Choose aufs-util branch to $KVER" "$(get_compat_aufsutil_version $KVER $FORCE_REDOWNLOAD | sed 's/.*/& "&"/') other 'Others'")
		if [ $AUFSUTIL = "other" ]; then
			AUFSUTIL=$(inputbox "Type the aufs-util branch you want to use" "")
		fi || die "Cancelled"
	fi



	# 3. get config
	local CHOICES="
DOTconfig 'Use an existing DOTconfig' \
DOTconfig-custom 'Use an existing DOTconfig and customise later' \
defconfig 'Use default configuration' \
defconfig-custom 'Use default configuration and customise later' \
"
	if [ -e /proc/config.gz ]; then
		CHOICES="
proc-config 'Use /proc/config.gz' \
proc-config-custom 'Use proc/config.gz and customise later' \
$CHOICES"
	fi

	CONFIG_SOURCE=$(choices "Choose kernel configuration mode" "$CHOICES") || die "Cancelled"

	case $CONFIG_SOURCE in
		proc-config*) DOTCONFIG_PATH=/proc/config.gz ;;
		DOTconfig*) DOTCONFIG_PATH=$(fselect "Choose an existing kernel config file (DOTconfig)") ;;
		*) DOTCONFIG_PATH="N/A"
	esac  || die "Cancelled"

	# 4. whether to pause at packaging

	if yesno "
Do you want to pause at packaging time so you can inspect/add/remove
modules and firmware before it is packaged into kernel-modules.sfs?
"; then
		PAUSE_AT_PACKAGE=yes
	fi

	# 5. start building
	rm -f $STATE_DIR/KVER # clean start
	echo $KVER | sh build-kernel.sh start ||
	die "Cannot find kernel $KVER"
}

#########################
show_summary() {
	{
	echo "
Build summary
=============

Kernel version: $KVER
"
	if [ "$USE_AUFS" ]; then
		echo Using aufs: $USE_AUFS
		echo aufs branch: $AUFS
		echo aufs-util branch: $AUFSUTIL
		echo
	fi

	echo "Kernel config source: ${CONFIG_SOURCE%%-custom}
Kernel DOTconfig path: $DOTCONFIG_PATH"
	case $CONFIG_SOURCE in
		*custom) echo Customise kernel config before building: Yes ;;
	esac
	echo

	if [ "$PAUSE_AT_PACKAGE" ]; then
		echo Pause at packaging: $PAUSE_AT_PACKAGE
		echo
	fi
	echo "Click 'OK' to proceed to build the kernel.
The build will run non-stop other than at the pause points you have chosen above."
	} | tailbox "Summary" show-cancel || die "Cancelled."
}

#########################
apply_patches() {
	if [ "$USE_AUFS" ]; then
		yes "" | sh build-kernel.sh aufs-option &&
		yes "" | sh build-kernel.sh download-aufs &&
		cd aufs-standalone &&
		git checkout $AUFS &&
		cd ../aufs-util &&
		git checkout $AUFSUTIL &&
		cd .. &&
		yes "" | sh build-kernel.sh checkout-aufs &&
		yes "" | sh build-kernel.sh download-extract-kernel &&
		if ! [ -e $STATE_DIR/apply-aufs ]; then # skip if it's already done
			yes "" | sh build-kernel.sh apply-aufs
		fi
	else
		echo "N" | sh build-kernel.sh aufs-option &&
		yes "" | sh build-kernel.sh download-extract-kernel
	fi || die "Something's wrong, please fix."
	yes "" | sh build-kernel.sh apply-patches
}

#########################
build_kernel() {
	# configuration
	case $CONFIG_SOURCE in
		proc-config*)
			gunzip -v -c /proc/config.gz > $KDIR/.config ;;
		defconfig*)
			( cd $KDIR; make defconfig ) ;;
		DOTconfig*)
			case "$(file -b --mime-type $DOTCONFIG_PATH)" in
				*gzip*) gunzip -v -c $DOTCONFIG_PATH > $KDIR/.config ;;
				*) cp -v $DOTCONFIG_PATH $KDIR/.config ;;
			esac ;;
	esac &&

	case $CONFIG_SOURCE in
		*custom)
			msgbox "
Next, a terminal will be opened to allow you to customise the kernel
configuration. Once you exit it, the build will continue.

Click OK to continue.
"
			defaultterm -e sh -c "cd $KDIR; make menuconfig"
			;;
	esac &&

	yes "" | sh build-kernel.sh kernel-config &&
	echo "Y" | sh build-kernel.sh build-kernel
}

#########################
package_kernel() {
	yes "" | sh build-kernel.sh package-kernel ||
	die "Something's wrong, please fix."

	if [ $PAUSE_AT_PACKAGE ]; then
		rox $KDIR/modules/lib
		msgbox "
Pausing to allow you time to change at modules/firmware,
which is locatedin $KDIR/modules/lib.

Please click 'OK' to continue when done.
"
	fi

	yes "" | sh build-kernel.sh package-kernel && # need to for the second time to complete
	echo "Y" | sh build-kernel.sh package-source ||
	die "Something's wrong, please fix."
}

###########################
clean() {
	rm -r $STATE_DIR
	rm -rf linux* aufs*
	echo "All previous builds removed."
	exit 0
}

show_help() {
	cat << EOF
Fatdog64 Kernel Builder GUI version $VERSION

Usage: $0 [-r|-c]
  -r : re-download all files
  -c : clear all data and files from previous build
  -v : show version

EOF
	exit 0
}

sanity_check() {
	local VER p found=
	! yesno "
If you are not running this program from the terminal, you should.

While this program should run successfully without a terminal,
you also won't be able to see its progress, as the progress is only
shown in the terminal.

You will also miss the diagnostic output in case it encounters problem.

Continue?
"	&& exit
	
	[ $(id -u) -ne 0 ] && die "Must be root to run this program."
	! type gcc > /dev/null 2>&1 && die "Can't find gcc."
	! type make > /dev/null 2>&1 && die "Can't find make."
	! type git > /dev/null 2>&1 && die "Can't find git."
	! [ -e build-kernel.sh ] && die "Can't find build-kernel.sh in this directory."
	[ $(df -k . | awk 'NR==2 {print $4}') -le $((MINSPACE*1024*1024)) ] &&
	die "Freespace on this directory too small. You need at least $MINSPACE GB free."
	
	# check compatible version
	VER=$(sh build-kernel.sh --version)
	for p in $COMPAT_VERSIONS; do
		[ $p = $VER ] && found=yes
	done
	! [ "$found" ] && die "Not compatible with build-kernel.sh version $VER"

	# all good
	return 0
}


### main

case $1 in
	"") true ;; # continue
	-h|--help)    show_help ;;
	-r|--reload)  FORCE_REDOWNLOAD=yes ;;
	-c|--clean)   clean ;;
	-v|--version) echo $VERSION; exit 0 ;;
	*) echo "Wrong options."; exit 0 ;;
esac

mkdir -p $STATE_DIR
sanity_check      &&
read_choices      &&
. $STATE_DIR/KVER &&
show_summary      &&
apply_patches     &&
build_kernel      &&
package_kernel    &&
msgbox "All done!" || infobox "Something is wrong, please fix."

