#!/bin/bash

# # # # # # # # # # # # # # # # # # # # # # # --- VARIABLES --- # # # # # # # # # # # # # # # # # # # # # # #

### --- CONFIGURABLE VARIABLES --- ###

# Partition variables
DISK="!/dev/sda !/dev/nvme0n1"	# If you don't want the script to automatically set this, then choose /dev/sda (SATA devices such as HDDs) or /dev/nvme0n1 (NVMe devices such as SSDs)!
FIRMWARE_TYPE="!BIOS !UEFI"		# If you don't want the script to automatically set this, then choose BIOS or UEFI here!

# Name variables
LUKS_BOOT="cryptboot"			# name of encrypted boot partition
LUKS_LVM="cryptlvm"				# name of encrypted LVM container
HOSTNAME="archTest"				# hostname; can be set here

# Password/-phrase variables
LUKS_PASSPHRASE="luks"			# LUKS encryption key; should be set here

### --- MISCELLANEOUS GLOBAL VARIABLES (do not edit) --- ###

#EFI_PT="!1 !p1"				# (p)1 if firmware is UEFI type (p* for NVMe devices); is set automatically
#BOOT_PT="!2 !p2"				# (p)1 (or 2) = Arch Linux only, no dual boot with Windows (p* for NVMe devices); is set automatically
#ROOT_PT="!3 !p3"				# (p)2 (or 3) = Arch Linux only, no dual boot with Windows (p* for NVMe devices); is set automatically
#EFI_PT_PARTTOOL=""				# for partitioning tools such as fdisk or gdisk; 1 if firmware is UEFI type; is set automatically
#BOOT_PT_PARTTOOL=""			# for partitioning tools such as fdisk or gdisk; 1 (or 2) = Arch Linux only, no dual boot with Windows; is set automatically
#ROOT_PT_PARTTOOL=""			# for partitioning tools such as fdisk or gdisk; 2 (or 3) = Arch Linux only, no dual boot with Windows; is set automatically
#PT_TABLE_TYPE=""				# partition table type (MBR/GPT) for fdisk/gdisk
ENCRYPTED_BOOT_PT=false			# default=false; whether the boot partition should be encrypted; currently only works for BIOS/MBR systems
DEVICE_TYPE_IS_SATA=false		# default=false; is automatically set by script
DEVICE_TYPE_IS_NVME=false		# default=false; is automatically set by script
DUAL_BOOT_WITH_WIN=false		# default=false; is set to 'true' if 'dual boot' is selected in the select_install_setup function

# Messages
INVALID_1="Invalid reply..."
INVALID_2="Invalid input..."

### --- COLOR VARIABLES (do not edit) --- ###

# Color codes (ANSI escape sequences)
# These color escape sequences are composed of the escape character (<Esc>) followed by some other characters: <Esc> + [ + format code + m.
# In Bash the <Esc> character can be obtained with "\033" (octal syntax) or just "\e".
BRIGHT_RED="\e[91m"
BRIGHT_GREEN="\e[92m"
BRIGHT_YELLOW="\e[93m"
BRIGHT_BLUE="\e[94m"
BRIGHT_CYAN="\e[96m"
BRIGHT_WHITE="\e[97m"
CLEAR="\e[0m"

# # # # # # # # # # # # # # # # # # # # # # # --- FUNCTIONS --- # # # # # # # # # # # # # # # # # # # # # # #

function sanitize_variables () {
	DISK=$(sanitize_variable "$DISK")
	FIRMWARE_TYPE=$(sanitize_variable "$FIRMWARE_TYPE")
}

function sanitize_variable () {
	VARIABLE=$1
	# Removing disabled values (!); regex matches a ! followed by every character that is not blank space ([^ ]), this character occurs zero or more times (*)
	VARIABLE=$(echo $VARIABLE | sed "s/![^ ]*//g")
	# Substituting unnecessary whitespace (  ) with just one blank space ( ); regex matches blank space that occurs at least two times ({2,})
	VARIABLE=$(echo $VARIABLE | sed "s/ {2,}/ /g")
	# Trimming leading whitespace; regex matches any kind of whitespace ([[:space:]]) that occurs zero or more times (*) at the beginning of the string (^)
	VARIABLE=$(echo $VARIABLE | sed "s/^[[:space:]]*//")
	# Trimming trailing whitespace; regex matches any kind of whitespace ([[:space:]]) that occurs zero or more times (*) at the end of the string ($)
	VARIABLE=$(echo $VARIABLE | sed "s/[[:space:]]*$//")
	echo "$VARIABLE"
}

## Overwrite text

function overwrite () {
	local input=$1
	for i in {1..${#input}}; do
		echo -n -e ""
	done
}

## Animations for waiting till a process is finished

function wait_animation () {
	local animation=$2
	if [[ $animation = "spinner" ]]; then
		spinner=('|' '/' '-' '\')
		# Run while the given process is not yet finished and dump any errors into /dev/null
		while kill -0 $1 2> /dev/null; do
			for i in ${spinner[@]}; do
				# Move one space back (or simply backspace), then print one character of the spinner without printing a new line at the end (-n)
				echo -n -e "\b$i"
				sleep 0.1
			done
		done
		echo -n -e "\b\b...done\n"
	elif [[ $animation = "dot" ]]; then
		while kill -0 $1 2> /dev/null; do
			echo -n -e "."
			sleep 1
		done
	fi
}

## Initial warning

function warning () {
	# For stylized prompts
	local x=0
	local y_1=2
	local y_2=0
	# For output review prompt specifically
	local y_3=0
	local y_4=0
	# Messages
	local line_1="Welcome to the Arch Linux Laptop installation script!"
	local line_2="Warning!"
	local line_3="If you're opting for an Arch-only installation, this script will delete any partitions formerly set up."
	local line_4="All your data will be lost."
	local prompt="Do you want to continue? [y/N] "
	local placeholder="#####"
	clear
	echo -e "\e[93;104m\e[${y_1};$(( ($(tput cols) - ${#line_1}) / 2 ))f$line_1$CLEAR"
	((y_1+=2))
	echo -e "\n$BRIGHT_RED\e[${y_1};$(( ($(tput cols) - ${#line_2}) / 2 ))f$line_2$CLEAR"
	((y_1++))
	echo -e "$BRIGHT_RED\e[${y_1};$(( ($(tput cols) - ${#line_3}) / 2 ))f$line_3$CLEAR"
	((y_1++))
	echo -e "$BRIGHT_RED\e[${y_1};$(( ($(tput cols) - ${#line_4}) / 2 ))f$line_4$CLEAR\n"
	((y_1+=2))
	while true; do
		echo -e "\e[${y_1};$(( ($(tput cols) - ${#prompt}) / 2 ))f$BRIGHT_WHITE$prompt"
		((y_1++))
		((y_2=y_1))
		echo -n -e "\e[${y_1};$(( ($(tput cols) - ${#placeholder}) / 2 ))f${BRIGHT_WHITE}> "
		((y_1+=2))
		((y_3=y_1))
		read reply
		if [[ $reply =~ (^[yY][eE][sS]$)|^[yY]$ ]]; then
			echo -n -e "$CLEAR"
			break
		elif [[ $reply =~ ^[nN][oO]{0,1}$ ]]; then
			echo -n -e "$CLEAR"
			exit
			break
		else
			echo -n -e "\e[${y_2};$(( ($(tput cols) - ${#INVALID_1}) / 2 ))f$BRIGHT_RED$INVALID_1$CLEAR"
			read -n 1 -s
			echo -n -e "\e[${y_2};$(( ($(tput cols) - ${#INVALID_1}) / 2 ))f$BRIGHT_RED$INVALID_1$CLEAR\r\033[K$(overwrite "$INVALID_1")"
			((y_1-=3))
			((y_2--))
		fi
	done
	echo ""
}

## Encryption options

function encryption_options () {
	local options=("Full Disk Encryption" "Full Disk Encryption including boot partition")
	echo -e "\n${BRIGHT_YELLOW}Select how you want your system to be encrypted$CLEAR"
	select option in "${options[@]}"; do
		case $option in
			"Full Disk Encryption")
				ENCRYPTED_BOOT_PT=false
				break
				;;
			"Full Disk Encryption including boot partition")
				ENCRYPTED_BOOT_PT=true
				break
				;;
		esac
	done
}

function partitioning_setup () {
	## Preparation

	# Check firmware type
	function firmware_type () {
		if [[ -d /sys/firmware/efi ]]; then
			echo -e "\n${BRIGHT_CYAN}Firmware type ${BRIGHT_GREEN}UEFI$BRIGHT_CYAN detected. The system will boot in ${BRIGHT_GREEN}UEFI$BRIGHT_CYAN mode.$CLEAR"
			FIRMWARE_TYPE="UEFI"
		else
			echo -e "\n${BRIGHT_CYAN}Firmware type ${BRIGHT_GREEN}BIOS$BRIGHT_CYAN detected. The system will boot in ${BRIGHT_GREEN}Legacy BIOS$BRIGHT_CYAN mode.$CLEAR"
			FIRMWARE_TYPE="BIOS"
		fi
	}

	# Check device type
	function device_type () {
		if [[ -n $(echo "$DISK" | grep "sd[a-z]") ]]; then
			DEVICE_TYPE_IS_SATA=true
		elif [[ -n $(echo "$DISK" | grep "nvme") ]]; then
			DEVICE_TYPE_IS_NVME=true
		fi
	}

	# Automatically set variables for partitioning
	function auto_partitioning_setup () {
		# Because partx prints out an error if there're no partitions to read, there needs to be a hack to mute the error message.
		# If an error occurs, this (usually) means there're no partitions to read which in turn means that the partition count is 0.
		if [[ $PARTX_EXIT -ne 0 ]]; then
			local partition_count=0
		else
			partition_count=$(partx --noheadings --show $DISK | wc --lines)
		fi
		# For Arch-only installs
		function with_both_ESP_and_boot_pt () {
			EFI_PT_PARTTOOL=$(( $partition_count + 1 ))
			BOOT_PT_PARTTOOL=$(( $partition_count + 2 ))
			ROOT_PT_PARTTOOL=$(( $partition_count + 3 ))
			if [[ $DEVICE_TYPE_IS_SATA = true ]]; then
				EFI_PT=$(( $partition_count + 1))
				BOOT_PT=$(( $partition_count + 2 ))
				ROOT_PT=$(( $partition_count + 3 ))
			elif [[ $DEVICE_TYPE_IS_NVME = true ]]; then
				EFI_PT="p$(( $partition_count + 1 ))"
				BOOT_PT="p$(( $partition_count + 2 ))"
				ROOT_PT="p$(( $partition_count + 3 ))"
			fi
		}
		# For dual boot with Windows installs
		function with_boot_pt () {
			BOOT_PT_PARTTOOL=$(( $partition_count + 1 ))
			ROOT_PT_PARTTOOL=$(( $partition_count + 2 ))
			if [[ $DEVICE_TYPE_IS_SATA = true ]]; then
				BOOT_PT=$(( $partition_count + 1 ))
				ROOT_PT=$(( $partition_count + 2 ))
			elif [[ $DEVICE_TYPE_IS_NVME = true ]]; then
				BOOT_PT="p$(( $partition_count + 1 ))"
				ROOT_PT="p$(( $partition_count + 2 ))"
			fi
		}
		if [[ $1 = "arch_only" ]]; then
			if [[ $FIRMWARE_TYPE = "BIOS" ]]; then
				BOOT_PT_PARTTOOL="1"
				ROOT_PT_PARTTOOL="2"
			elif [[ $FIRMWARE_TYPE = "UEFI" ]]; then
				EFI_PT_PARTTOOL="1"
				BOOT_PT_PARTTOOL="2"
				ROOT_PT_PARTTOOL="3"
			fi
			if [[ $DEVICE_TYPE_IS_SATA = true ]]; then
				if [[ $FIRMWARE_TYPE = "BIOS" ]]; then
					BOOT_PT="1"
					ROOT_PT="2"
				elif [[ $FIRMWARE_TYPE = "UEFI" ]]; then
					EFI_PT="1"
					BOOT_PT="2"
					ROOT_PT="3"
				fi
			elif [[ $DEVICE_TYPE_IS_NVME = true ]]; then
				if [[ $FIRMWARE_TYPE = "BIOS" ]]; then
					BOOT_PT="p1"
					ROOT_PT="p2"
				elif [[ $FIRMWARE_TYPE = "UEFI" ]]; then
					EFI_PT="p1"
					BOOT_PT="p2"
					ROOT_PT="p3"
				fi
			fi
		else
			[[ $FIRMWARE_TYPE = "UEFI" && $DUAL_BOOT_WITH_WIN = false ]] && with_both_ESP_and_boot_pt || with_boot_pt
		fi
	}

	# Display partition table changes, cryptsetup results or display attributes of physical volumes
	function check_results () {
		local prompt_type=$1
		local what_to_check=$2
		local prompt_for_individual_check="Would you like to check the results? [y/N] > "
		local prompt_for_menu_check="Would you like to check the results of the previous steps? [y/N] > "

		function press_enter () {
			local input=$1
			if [[ $input = "menu" ]]; then
				local prompt="Press [ENTER] to return to menu"
			else
				local prompt="Press [ENTER] to continue"
			fi
			echo -e "$BRIGHT_WHITE"
			read -p "$prompt"
			echo -n -e "$CLEAR"
		}
		function check_results_partitions () {
			partx --show $DISK
		}
		function check_results_parttool () {
			lsblk $DISK
			echo ""
			fdisk --list $DISK
		}
		function check_results_luks () {
			if [[ $ENCRYPTED_BOOT_PT = true ]]; then
				cryptsetup luksDump $DISK$BOOT_PT
				press_enter
			fi
			cryptsetup luksDump $DISK$ROOT_PT
		}
		function check_results_lvm () {
			pvdisplay --maps --verbose
		}
		function individual_check () {
			[[ $what_to_check = "partitions" ]] && check_results_partitions
			[[ $what_to_check = "parttool" ]] && check_results_parttool
			[[ $what_to_check = "cryptsetup" ]] && check_results_luks
			[[ $what_to_check = "lvm" ]] && check_results_lvm
			press_enter
		}
		function menu_check () {
			echo -n -e "
${BRIGHT_WHITE}Partitioning setup results menu$CLEAR"
			if [[ $FIRMWARE_TYPE = "BIOS" ]]; then
				echo -n -e "
\n${BRIGHT_GREEN}1) ${BRIGHT_WHITE}Partitions set up with fdisk$CLEAR"
			elif [[ $FIRMWARE_TYPE = "UEFI" ]]; then
				echo -n -e "
\n${BRIGHT_GREEN}1) ${BRIGHT_WHITE}Partitions set up with gdisk$CLEAR"
			fi
			echo -n -e "
${BRIGHT_GREEN}2) ${BRIGHT_WHITE}Encryption results
${BRIGHT_GREEN}3) ${BRIGHT_WHITE}LVM results
${BRIGHT_RED}0) Exit menu\n
${BRIGHT_YELLOW}Choose an option: $CLEAR"
			read option
			case $option in
				1) check_results_parttool; press_enter "menu"; menu_check ;;
				2) check_results_luks; press_enter "menu"; menu_check ;;
				3) check_results_lvm; press_enter "menu"; menu_check ;;
				0) ;;
				*) echo -n -e "$BRIGHT_RED$INVALID_1$CLEAR"; read -n 1 -s; echo ""; menu_check ;;
			esac
		}
		while true; do
			if [[ $prompt_type = "individual" ]]; then
				echo -n -e "$BRIGHT_YELLOW$prompt_for_individual_check$CLEAR"
			elif [[ $prompt_type = "menu" ]]; then
				echo -n -e "\n$BRIGHT_YELLOW$prompt_for_menu_check$CLEAR"
			fi
			read -n 1 reply
			if [[ $reply =~ [yY] ]]; then
				if [[ $prompt_type = "individual" ]]; then
					echo -e "\n"
					individual_check
				elif [[ $prompt_type = "menu" ]]; then
					echo ""
					menu_check
				fi
				break
			elif [[ $reply =~ [nN] ]]; then
				echo ""
				break
			else
				echo -n -e "\b$BRIGHT_RED$INVALID_1$CLEAR"
				read -n 1 -s
				echo -n -e "$BRIGHT_RED$INVALID_1$CLEAR\r\033[K$(overwrite "$INVALID_1")"
			fi
		done
	}

	# Display all available disk devices
	function available_disk_devices () {
		local prompt="Do you want to see a list of all available disk devices? [y/N] > "
		echo ""
		while true; do
			echo -n -e "$BRIGHT_YELLOW$prompt$CLEAR"
			read -n 1 reply
			if [[ $reply =~ [yY] ]]; then
				echo ""
				lsblk --all
				break
			elif [[ $reply =~ [nN] ]]; then
				echo ""
				break
			else
				echo -n -e "\b$BRIGHT_RED$INVALID_1$CLEAR"
				read -n 1 -s
				echo -n -e "$BRIGHT_RED$INVALID_1$CLEAR\r\033[K$(overwrite "$INVALID_1")"
			fi
		done
		echo ""
	}

	# Prompt for manual input of disk device
	function manual_disk_input () {
		while true; do
			echo -n -e "${BRIGHT_WHITE}Type in which disk to install to (e.g. sda or nvme0n1): $CLEAR"
			read dest_disk
			if [[ -n $dest_disk ]]; then
				# Checking if disk exists
				if [[ -d $DIR/$dest_disk ]]; then
					# Checking criterias for disk
					if [[ $(cat $DIR/$dest_disk/removable) -eq 0 ]]; then
						# Converting Block size to GB
						if [[ $(( $(cat $DIR/$dest_disk/size) / 1953125 )) -ge $MINSIZE ]]; then
							check_for_partitions "manual" "$dest_disk"
							break
						else
							echo -e "\n${BRIGHT_RED}The disk should not have less than $MINSIZE GB of free space!$CLEAR\n"
							local prompt="Do you want to use this disk nonetheless? [y/N] > "
							while true; do
								echo -n -e "$BRIGHT_YELLOW$prompt$CLEAR"
								read reply
								if [[ $reply =~ (^[yY][eE][sS]$)|^[yY]$ ]]; then
									check_for_partitions "manual" "$dest_disk"
									break
								elif [[ $reply =~ ^[nN][oO]{0,1}$ ]]; then
									manual_disk_input
									break
								else
									echo -n -e "\b$BRIGHT_RED$INVALID_1$CLEAR"
									read -n 1 -s
									echo -n -e "$BRIGHT_RED$INVALID_1$CLEAR\r\033[K$(overwrite "$INVALID_1")"
								fi
							done
							break
						fi
					else
						echo -e "${BRIGHT_RED}The disk device must not be removable!$CLEAR\n"
					fi
				else
					echo -e "${BRIGHT_RED}Disk doesn't exist.$CLEAR\n"
				fi
			else
				echo -e "$BRIGHT_RED$INVALID_2$CLEAR\n"
			fi
		done
	}

	# Determine which disk to pick and assign it to the DISK variable
	function determine_disk_to_pick () {
		function free_disk_space_lsblk () {
			# Because partx prints out an error if there're no partitions to read, there needs to be a hack to mute the error message.
			# If an error occurs, this (usually) means there're no partitions to read which in turn means that the free disk space should be equal to the disks size.
			partx --show /dev/$disk &> /dev/null
			PARTX_EXIT=$?
			if [[ $PARTX_EXIT -ne 0 ]]; then
				FREE_DISK_SPACE=$(lsblk --bytes --noheadings --output SIZE /dev/$disk | head --lines=1)
				FREE_DISK_SPACE=$(( $FREE_DISK_SPACE / 2**30 ))
			else
				local partition_count=$(partx --noheadings --show /dev/$disk | wc --lines)
				local partition_size_sum=0
				for (( i=1; i<=$partition_count; i++ )); do
					partition_size_sum=$(( $partition_size_sum + $(lsblk --bytes --noheadings --output SIZE /dev/${disk}p$i) ))
				done
				FREE_DISK_SPACE=$(( $(lsblk --bytes --noheadings --output SIZE /dev/$disk | head --lines=1) - $partition_size_sum ))
				FREE_DISK_SPACE=$(( $FREE_DISK_SPACE / 2**30 ))
			fi
		}
		for disk in $DIR/sd?* $DIR/nvme?*; do
			disk=$(echo "$disk" | awk --field-separator '/' '{print $NF}')
			if [[ -d $DIR/$disk ]]; then
				local removable=$(cat $DIR/$disk/removable)
				if [[ $removable -eq 0 ]]; then
					local block_size=$(cat $DIR/$disk/size)
					# Convert Block size to gibibytes (1 GiB = 2**21 Block)
					## 1 Gibibyte (GiB) = 1024**3 Byte or 2**30 Byte
					## 1 Block = 512 Byte, 1/512 Block = 1 Byte, (2**30)/512 Block or 2**21 (2097152) Block = 2**30 Byte = 1 GiB
					## GiB=$(( $block_size / 2**21 ))
					# Convert Block size to gigabytes (1 GB = (10**9)/512 Block)
					## 1 Gigabyte (GB) = 1000**3 Byte or 10**9 Byte
					## 1 Block = 512 Byte, 1/512 Block = 1 Byte, (10**9)/512 (1953125) Block = 10**9 Byte = 1 GB
					local GB=$(( $block_size / 1953125 ))
					# DEBUGGING
					# Check free $disk space in Byte and convert to GB
					## Using df -> Does not detect my SSD (nvme0n1 device) for some reason...
					#local free_disk_space=$(( $(df --block-size=1 --output=avail /dev/$disk | tail --lines=1) / 10**9 ))
					## Using lsblk's size value
					free_disk_space_lsblk
					## Using parted: https://stackoverflow.com/questions/12313384/how-to-view-unallocated-free-space-on-a-hard-disk-through-terminal
					# Check if size of block device (/dev/$disk) is greater or equal to $MINSIZE
					if [[ $GB -ge $MINSIZE ]]; then
						echo -e "\n${BRIGHT_CYAN}Found $BRIGHT_GREEN/dev/$disk$BRIGHT_CYAN to be sufficient. It has about $BRIGHT_GREEN${FREE_DISK_SPACE}GB$BRIGHT_CYAN of free disk space available."
						# Ask whether to use the found disk
						while true; do
							echo -n -e "${BRIGHT_YELLOW}Do you want to use this disk? [y/N] > $CLEAR"
							read reply
							# 'yes' -> checks for partitions
							if [[ $reply =~ (^[yY][eE][sS]$)|^[yY]$ ]]; then
								DISK="/dev/$disk"
								check_for_partitions "automatic"
								break
							# 'no' -> manual setup
							elif [[ $reply =~ ^[nN][oO]{0,1}$ ]]; then
								available_disk_devices
								manual_disk_input
								break
							else
								echo -e "$BRIGHT_RED$INVALID_1$CLEAR\n"
							fi
						done
					else
						echo -e "\n${BRIGHT_RED}No disk with the storage capacity you specified was found. Please manually specify on which disk to install.$CLEAR"
						available_disk_devices
						manual_disk_input
					fi
				fi
			fi
		done
	}

	# Check for existing partitions
	function check_for_ESP () {
		fdisk --list $DISK | grep --ignore-case --quiet "EFI"
		local grep_exit=$?
		if [[ $grep_exit -eq 0 ]]; then
			echo -e "\n${BRIGHT_CYAN}Firmware type ${BRIGHT_GREEN}UEFI$BRIGHT_CYAN detected. The system will boot in ${BRIGHT_GREEN}UEFI$BRIGHT_CYAN mode.$CLEAR\n"
			FIRMWARE_TYPE="UEFI"
		else
			echo -e "\n${BRIGHT_CYAN}Firmware type ${BRIGHT_GREEN}BIOS$BRIGHT_CYAN detected. The system will boot in ${BRIGHT_GREEN}Legacy BIOS$BRIGHT_CYAN mode.$CLEAR\n"
			FIRMWARE_TYPE="BIOS"
		fi
		# Set Windows' ESP
		if [[ $(partx --noheadings --show $DISK | wc --lines) -gt 4 ]]; then
			echo -e "${BRIGHT_RED}The script couldn't figure out which partition is the Windows EFI system partition.$CLEAR"
			while true; do
				echo -n -e "\n${BRIGHT_WHITE}Please manually input the device path, i.e. /dev/...:$CLEAR "
				read input
				if [[ -n $input ]]; then
					# Check if partition exists
					fdisk --list | grep --quiet "$input"
					local grep_exit=$?
					if [[ $grep_exit -eq 0 ]]; then
						WIN_ESP_PATH=$input
						break
					else
						echo -e "${BRIGHT_RED}This partition doesn't exist.$CLEAR\n"
					fi
				else
					echo -e "$BRIGHT_RED$INVALID_2$CLEAR\n"
				fi
			done
		else
			WIN_ESP_PATH=$(fdisk --list | grep --ignore-case "EFI" | grep --extended-regexp --only-matching "/dev/(sd[a-z][1-9]*|nvme0n1p[1-9]*)")
		fi
	}
	function check_for_windows_partitions () {
		lsblk --list --noheadings --output NAME,SIZE,TYPE,FSTYPE,PTTYPE,LABEL $DISK | grep --ignore-case --quiet "ntfs"
		local grep_exit=$?
		if [[ $grep_exit -eq 0 ]]; then
			WIN_PARTITION_FOUND=true
			echo -e "${BRIGHT_CYAN}Existing Windows partitions have been found on $DISK.$CLEAR"
			# Check for EFI system partition (ESP)
			check_for_ESP
		fi
	}
	function check_for_partitions () {
		local select=$1
		local destination=$2
		#PARTITION_FOUND=false
		WIN_PARTITION_FOUND=false
		if [[ $select = "automatic" ]]; then
			if [[ $PARTX_EXIT -eq 0 ]]; then
				#PARTITION_FOUND=true
				echo -e "\n${BRIGHT_CYAN}Existing partitions have been found on $DISK.$CLEAR"
				check_for_windows_partitions
				check_results "individual" "partitions"
			else
				echo -e "\n${BRIGHT_CYAN}No partitions have been found on $DISK.$CLEAR"
			fi
		elif [[ $select = "manual" ]]; then
			partx --show /dev/$destination &> /dev/null
			PARTX_EXIT=$?
			if [[ $PARTX_EXIT -eq 0 ]]; then
				#PARTITION_FOUND=true
				DISK="/dev/$destination"
				echo -e "\n${BRIGHT_CYAN}Existing partitions have been found on $DISK.$CLEAR"
				check_for_windows_partitions
				check_results "individual" "partitions"
			else
				DISK="/dev/$destination"
				echo -e "\n${BRIGHT_CYAN}No partitions have been found on $DISK.$CLEAR"
			fi
		fi
	}

	# Disk wipe function
	function wipe () {
		local prompt="Do you want to wipe the disk the zeroes? [y/N] > "
		echo ""
		while true; do
			echo -n -e "$BRIGHT_YELLOW$prompt$CLEAR"
			read reply
			if [[ $reply =~ (^[yY][eE][sS]$)|^[yY]$ ]]; then
				echo -e "\n${BRIGHT_CYAN}Wiping disk$CLEAR..."
				dd if=/dev/zero of=$DISK status=progress
				break
			elif [[ $reply =~ ^[nN][oO]{0,1}$ ]]; then
				break
			else
				echo -n -e "\b$BRIGHT_RED$INVALID_1$CLEAR"
				read -n 1 -s
				echo -n -e "$BRIGHT_RED$INVALID_1$CLEAR\r\033[K$(overwrite "$INVALID_1")"
			fi
		done
	}

	## Action

	# Actual partitioning with fdisk/gdisk
	function actual_partitioning_with_fdisk_and_gdisk () {
		function partitioning_for_BIOS () {
			cat << EOF | fdisk $DISK
$PT_TABLE_TYPE
n
p
$BOOT_PT_PARTTOOL

+512M
n
p
$ROOT_PT_PARTTOOL


t
$ROOT_PT_PARTTOOL
8e
a
$BOOT_PT_PARTTOOL
w
EOF
		}

		function partitioning_for_UEFI () {
			# For Arch-only installs
			function UEFI_gdisk_both_ESP_and_boot_pt () {
				cat << EOF | gdisk $DISK
$PT_TABLE_TYPE
Y
n
$EFI_PT_PARTTOOL

+512M
ef00
c
$EFI_PT_PARTTOOL
Linux EFI system partition
n
$BOOT_PT_PARTTOOL

+512M
8300
c
$BOOT_PT_PARTTOOL
Linux boot partition
n
$ROOT_PT_PARTTOOL


8e00
c
$ROOT_PT_PARTTOOL
Arch Linux root partition
w
Y
EOF
			}
			# For dual boot with Windows installs
			function UEFI_gdisk_only_boot_pt () {
				cat << EOF | gdisk $DISK
n
$BOOT_PT_PARTTOOL

+512M
8300
c
$BOOT_PT_PARTTOOL
Linux boot partition
n
$ROOT_PT_PARTTOOL


8e00
c
$ROOT_PT_PARTTOOL
Arch Linux root partition
w
Y
EOF
			}
			if [[ $DUAL_BOOT_WITH_WIN = true ]]; then
				UEFI_gdisk_only_boot_pt
			else
				UEFI_gdisk_both_ESP_and_boot_pt
			fi
		}

		if [[ $FIRMWARE_TYPE = "BIOS" ]]; then
			if [[ $DUAL_BOOT_WITH_WIN = true ]]; then
				# Keep existing partition table
				PT_TABLE_TYPE=""
				echo -e "\n${BRIGHT_BLUE}Partitioning disk$CLEAR"
			else
				# Create a new empty MBR partition table using fdisk
				PT_TABLE_TYPE="o"
				echo -e "\n${BRIGHT_BLUE}Partitioning disk using the MBR partition table$CLEAR"
			fi
			partitioning_for_BIOS > /dev/null
		elif [[ $FIRMWARE_TYPE = "UEFI" ]]; then
			if [[ $DUAL_BOOT_WITH_WIN = true ]]; then
				# Keep existing partition table
				PT_TABLE_TYPE=""
				echo -e "\n${BRIGHT_BLUE}Partitioning disk$CLEAR"
			else
				# Create a new empty GUID Partition Table (GPT) using gdisk
				PT_TABLE_TYPE="o"
				echo -e "\n${BRIGHT_BLUE}Partitioning disk using the GUID Partition Table (GPT)$CLEAR"
			fi
			partitioning_for_UEFI > /dev/null
		fi
	}

	# Actual partitioning with parted
	function actual_partitioning_with_parted () {
		# Partition name variables
		local esp_name_for_parted="Linux_EFI_system_partition"
		local boot_pt_name_for_parted="Linux_boot_partition"
		local root_pt_name_for_parted="Arch_Linux_root_partition"
		# Sector variables
		## 1. Outputs information of $DISK - among others sector sizes
		## 2. Looks at last line which contains information about the last partition
		## 3. Greps only digits larger than two characters to only get the sector sizes
		## 4. Cuts out the last line and then cuts out the first line of the remaining two lines; the result is the end sector size of the last partition
		## 5. Increments that value by one to prevent overlapping
		local end_sectors_of_last_partition=$(echo $(fdisk --list $DISK | tail --lines=1 | grep --extended-regexp --only-matching "[0-9]{2,}" | head --lines=2 | tail --lines=1))
		## Adds 512MiB (in sector size) to $end_sectors_of_last_partition, thus getting the end sector size for the boot partition
		local end_sectors_of_boot_partition=$(( $end_sectors_of_last_partition + 1048576 ))
		## Increments $end_sectors_of_boot_partition by one to prevent overlapping, thus getting the start sector size for the root partition
		local start_sectors_of_root_partition=$(( $end_sectors_of_boot_partition + 1 ))
		# parted input chain for dual boot installs
		local parted_BIOS_for_dual_boot="mkpart primary ext4 ${end_sectors_of_last_partition}s ${end_sectors_of_boot_partition}s set $BOOT_PT_PARTTOOL boot on mkpart primary ext4 ${start_sectors_of_root_partition}s 100%"
		local parted_UEFI_for_dual_boot="mkpart $boot_pt_name_for_parted ext4 ${end_sectors_of_last_partition}s ${end_sectors_of_boot_partition}s set $BOOT_PT_PARTTOOL boot on mkpart $root_pt_name_for_parted ext4 ${start_sectors_of_root_partition}s 100% set $ROOT_PT_PARTTOOL lvm on"
		# parted input chain for Arch-only installs
		local parted_BIOS="mklabel msdos mkpart primary ext4 1MiB 513MiB set $BOOT_PT_PARTTOOL boot on mkpart primary ext4 513MiB 100%"
		local parted_UEFI="mklabel gpt mkpart $esp_name_for_parted fat32 1MiB 513MiB set $EFI_PT_PARTTOOL esp on mkpart $boot_pt_name_for_parted ext4 513MiB 1025MiB mkpart $root_pt_name_for_parted ext4 1025MiB 100% set $ROOT_PT_PARTTOOL lvm on"

		if [[ $FIRMWARE_TYPE = "BIOS" ]]; then
			if [[ $DUAL_BOOT_WITH_WIN = true ]]; then
				echo -e "\n${BRIGHT_BLUE}Partitioning disk$CLEAR"
				parted --script $DISK $parted_BIOS_for_dual_boot
			else
				echo -e "\n${BRIGHT_BLUE}Partitioning disk using the MBR partition table$CLEAR"
				parted --script $DISK $parted_BIOS
			fi
		elif [[ $FIRMWARE_TYPE = "UEFI" ]]; then
			if [[ $DUAL_BOOT_WITH_WIN = true ]]; then
				echo -e "\n${BRIGHT_BLUE}Partitioning disk$CLEAR"
				#parted --script $DISK $parted_UEFI_for_dual_boot
				echo $parted_UEFI_for_dual_boot
			else
				echo -e "\n${BRIGHT_BLUE}Partitioning disk using the GUID Partition Table (GPT)$CLEAR"
				parted --script $DISK $parted_UEFI
			fi
		fi
	}
	function actual_partitioning_with_parted_alt () {
		# Partition name variables
		local esp_name_for_parted="'Linux EFI system partition'"
		local boot_pt_name_for_parted="'Linux boot partition'"
		local root_pt_name_for_parted="'Arch Linux root partition'"
		# Sector variables
		## 1. Outputs information of $DISK - among others sector sizes
		## 2. Looks at last line which contains information about the last partition
		## 3. Greps only digits larger than two characters to only get the sector sizes
		## 4. Cuts out the last line and then cuts out the first line of the remaining two lines; the result is the end sector size of the last partition
		## 5. Increments that value by one to prevent overlapping
		local end_sectors_of_last_partition=$(( $(fdisk --list $DISK | tail --lines=1 | grep --extended-regexp --only-matching "[0-9]{2,}" | head --lines=2 | tail --lines=1) + 1 ))
		## Adds 512MiB (in sector size) to $end_sectors_of_last_partition, thus getting the end sector size for the boot partition
		local end_sectors_of_boot_partition=$(( $end_sectors_of_last_partition + 1048576 ))
		## Increments $end_sectors_of_boot_partition by one to prevent overlapping, thus getting the start sector size for the root partition
		local start_sectors_of_root_partition=$(( $end_sectors_of_boot_partition + 1 ))

		function parted_partitioning_for_BIOS () {
			# For dual boot with Windows installs
			if [[ $DUAL_BOOT_WITH_WIN = true ]]; then
				cat << EOF | parted $DISK
mkpart primary ext4 ${end_sectors_of_last_partition}s ${end_sectors_of_boot_partition}s set $BOOT_PT_PARTTOOL boot on
mkpart primary ext4 ${start_sectors_of_root_partition}s 100%
EOF
			# For Arch-only installs
			else
				cat << EOF | parted $DISK
mklabel msdos mkpart primary ext4 1MiB 513MiB set $BOOT_PT_PARTTOOL boot on
mkpart primary ext4 513MiB 100%
EOF
			fi
		}

		function parted_partitioning_for_UEFI () {
			# For dual boot with Windows installs
			if [[ $DUAL_BOOT_WITH_WIN = true ]]; then
				cat << EOF | parted $DISK
mkpart $boot_pt_name_for_parted ext4 ${end_sectors_of_last_partition}s ${end_sectors_of_boot_partition}s set $BOOT_PT_PARTTOOL boot on
mkpart $root_pt_name_for_parted ext4 ${start_sectors_of_root_partition}s 100% set $ROOT_PT_PARTTOOL lvm on
EOF
			# For Arch-only installs
			else
				cat << EOF | parted $DISK
mklabel gpt mkpart $esp_name_for_parted fat32 1MiB 513MiB set $EFI_PT_PARTTOOL esp on
mkpart $boot_pt_name_for_parted ext4 513MiB 1025MiB
mkpart $root_pt_name_for_parted ext4 1025MiB 100% set $ROOT_PT_PARTTOOL lvm on
EOF
			fi
		}
			
			if [[ $FIRMWARE_TYPE = "BIOS" ]]; then
				parted_partitioning_for_BIOS
			elif [[ $FIRMWARE_TYPE = "UEFI" ]]; then
				parted_partitioning_for_UEFI
			fi
	}

	# Encrypt partitions using LUKS and open them
	function LUKS_part () {
		if [[ $ENCRYPTED_BOOT_PT = true ]]; then
			local luks_version="luks1"
		else
			local luks_version="luks2"
		fi
		# Create LUKS partitions
		## This creates LUKS encrypted containers at the boot and root partition.
		echo -e "\n${BRIGHT_BLUE}Creating LUKS partitions$CLEAR"
		if [[ $ENCRYPTED_BOOT_PT = true ]]; then
			echo -n -e "Setting up boot partition as encrypted LUKS partition"
			echo -e "$LUKS_PASSPHRASE" | cryptsetup --cipher aes-xts-plain64 --key-size 512 --hash sha256 --iter-time 2000 --use-urandom --type $luks_version luksFormat $DISK$BOOT_PT > /dev/null &
			PID=$!; wait_animation "$PID" "dot"; echo ""
		fi
		echo -n -e "Setting up root partition as encrypted LUKS partition"
		echo -e "$LUKS_PASSPHRASE" | cryptsetup --cipher aes-xts-plain64 --key-size 512 --hash sha256 --iter-time 2000 --use-urandom --type $luks_version luksFormat $DISK$ROOT_PT > /dev/null &
		PID=$!; wait_animation "$PID" "dot"; echo ""

		# Unlock/Map LUKS partitions
		## This will map the partitions to a new device name using the device mapper.
		echo -e "\n${BRIGHT_BLUE}Unlocking LUKS partitions$CLEAR"
		if [[ $ENCRYPTED_BOOT_PT = true ]]; then
			echo -n -e "Opening LUKS boot container as $LUKS_BOOT"
			echo -e "$LUKS_PASSPHRASE" | cryptsetup luksOpen $DISK$BOOT_PT $LUKS_BOOT > /dev/null &
			PID=$!; wait_animation "$PID" "dot"; echo ""
		fi
		echo -n -e "Opening LUKS root container as $LUKS_LVM"
		echo -e "$LUKS_PASSPHRASE" | cryptsetup luksOpen $DISK$ROOT_PT $LUKS_LVM > /dev/null &
		PID=$!; wait_animation "$PID" "dot"; echo ""
	}

	# Configure LVM for partitions
	function LVM_manage () {
		function LVM_commands () {
			# Create a physical volume on top of the opened LUKS root container
			pvcreate /dev/mapper/$LUKS_LVM
			# Set volume group name variable
			VG="$HOSTNAME-vg"
			# Create a volume group, adding the previously created physical volume to it
			vgcreate --force $VG /dev/mapper/$LUKS_LVM
			# Define logical volumes in this volume group
			#lvcreate --size 2G $VG --name swap
			lvcreate --extents 25%VG $VG --name root
			lvcreate --extents 100%FREE $VG --name home
			# Load necessary kernel module, scan for volume groups and activate them
			modprobe dm_mod
			vgscan
			vgchange --activate y
		}
		# By calling the necessary LVM commands with a function all of their outputs can be muted by muting the function. Otherwise, each commands output would have to be muted individually.
		echo -e "\n${BRIGHT_BLUE}Logical Volume Management$CLEAR"
		# DEBUGGING
		LVM_commands #> /dev/null
	}

	# Format filesystems
	function format_fs () {
		function format_fs_for_BIOS () {
			mkfs.ext4 -L ROOT /dev/$VG/root
			mkfs.ext4 -L HOME /dev/$VG/home
			#mkswap -L SWAP /dev/$VG/swap
			if [[ $ENCRYPTED_BOOT_PT = false ]]; then
				mkfs.ext4 -L BOOT $DISK$BOOT_PT
			else
				mkfs.ext4 -L BOOT /dev/mapper/$LUKS_BOOT
			fi
		}

		function format_fs_for_UEFI () {
			mkfs.ext4 -L ROOT /dev/$VG/root
			mkfs.ext4 -L HOME /dev/$VG/home
			#mkswap -L SWAP /dev/$VG/swap
			mkfs.ext4 -L BOOT $DISK$BOOT_PT
			[[ $DUAL_BOOT_WITH_WIN = true ]] && mkfs.fat -F32 -n ESP $DISK$EFI_PT
		}

		echo -e "\n${BRIGHT_BLUE}Formatting filesystems$CLEAR"
		if [[ $FIRMWARE_TYPE = "BIOS" ]]; then
			format_fs_for_BIOS &> /dev/null
		elif [[ $FIRMWARE_TYPE = "UEFI" ]]; then
			format_fs_for_UEFI &> /dev/null
		fi
	}

	# Mount filesystems
	function mount_fs () {
		function mount_fs_for_BIOS () {
			mount /dev/$VG/root /mnt
			mkdir /mnt/home
			mount /dev/$VG/home /mnt/home
			#swapon /dev/$VG/swap
			mkdir /mnt/boot
			if [[ $ENCRYPTED_BOOT_PT = false ]]; then
				mount $DISK$BOOT_PT /mnt/boot
			else
				mount /dev/mapper/$LUKS_BOOT /mnt/boot
			fi
		}

		function mount_fs_for_UEFI () {
			mount /dev/$VG/root /mnt
			mkdir /mnt/home
			mount /dev/$VG/home /mnt/home
			#swapon /dev/$VG/swap
			mkdir /mnt/boot
			mount $DISK$BOOT_PT /mnt/boot
			mkdir /mnt/boot/efi
			if [[ $DUAL_BOOT_WITH_WIN = true ]]; then
				mount $WIN_ESP_PATH /mnt/boot/efi
			else
				mount $DISK$EFI_PT /mnt/boot/efi
			fi
		}

		echo -e "\n${BRIGHT_BLUE}Mounting filesystems$CLEAR"
		if [[ $FIRMWARE_TYPE = "BIOS" ]]; then
			mount_fs_for_BIOS > /dev/null
		elif [[ $FIRMWARE_TYPE = "UEFI" ]]; then
			mount_fs_for_UEFI > /dev/null
		fi
	}

	# Select installation setup and set partition variables accordingly
	function select_install_setup () {
		local options=("Arch Linux-only Setup:  Wipes everything and installs Arch Linux" "Dual boot Setup:  Installs Arch Linux alongside another OS, e.g. Windows")
		echo -e "\n${BRIGHT_YELLOW}Select how you want your system to be set up$CLEAR"
		select setup in "${options[@]}"; do
			case $setup in
				"${options[0]}")
					DUAL_BOOT_WITH_WIN=false
					[[ -z $FIRMWARE_TYPE ]] && firmware_type
					[[ $FIRMWARE_TYPE = "BIOS" ]] && encryption_options
					device_type
					auto_partitioning_setup "arch_only"
					wipe
					break
					;;
				"${options[1]}")
					DUAL_BOOT_WITH_WIN=true
					[[ -z $FIRMWARE_TYPE ]] && firmware_type
					[[ $FIRMWARE_TYPE = "BIOS" ]] && encryption_options
					device_type
					auto_partitioning_setup
					break
					;;
			esac
		done
	}

	# Pick the first disk that is not removable and is greater than or equal to MINSIZE
	DIR="/sys/block"
	# Minimum size of hard drive/solid state drive needed specified in GIBIBYTES; default: 120 GB
	MINSIZE="${1:-120}"
	local options=("Automatic Setup:  Picks the \"next best\" disk to install to (recommended for laptops)" "Manual Setup:  Type in which disk to install to")
	echo -e "\n${BRIGHT_YELLOW}Select a type of setup$CLEAR"
	select type in "${options[@]}"; do
		case $type in
			"${options[0]}")
				determine_disk_to_pick
				if [[ $WIN_PARTITION_FOUND = true ]]; then
					select_install_setup
				else
					[[ -z $FIRMWARE_TYPE ]] && firmware_type
					[[ $FIRMWARE_TYPE = "BIOS" ]] && encryption_options
					device_type
					auto_partitioning_setup
					wipe
				fi
				break
				;;
			"${options[1]}")
				available_disk_devices
				manual_disk_input
				if [[ $WIN_PARTITION_FOUND = true ]]; then
					select_install_setup
				else
					[[ -z $FIRMWARE_TYPE ]] && firmware_type
					[[ $FIRMWARE_TYPE = "BIOS" ]] && encryption_options
					device_type
					auto_partitioning_setup
					wipe
				fi
				break
				;;
		esac
	done
	# DEBUGGING
	#actual_partitioning_with_fdisk_and_gdisk
	actual_partitioning_with_parted
	partprobe $DISK
	LUKS_part
	LVM_manage
	format_fs
	mount_fs
	check_results "menu"
}

function unmount () {
	# Unmount every partition
	umount --recursive /mnt
	# Disable all swap devices
	swapoff --all
	# Deactivate volume group
	vgchange --activate n
	cryptsetup luksClose $LUKS_LVM
	[[ $ENCRYPTED_BOOT_PT = true ]] && cryptsetup luksClose $LUKS_BOOT
}

function end () {
	echo -e "\n\n${BRIGHT_GREEN}Installation of Arch Linux finished!${CLEAR}\n"
	echo -e "${BRIGHT_WHITE}You may reboot now. Press [ENTER] to unmount and exit...$CLEAR"
	read -s -p ""
}

## Call major functions with needed arguments for creating the user

function call () {
	warning
	sanitize_variables
	while true; do
		echo -n -e "\n${BRIGHT_YELLOW}Set minimal size of free disk space in GB (default: 120 GB): $CLEAR"
		read value
		if [[ -n $value && $value -gt 0 ]] &> /dev/null; then
			break
		elif [[ -z $value ]]; then
			break
		elif [[ $value -lt 0 ]]; then
			echo -e "${BRIGHT_RED}Value can't be negative!$CLEAR"
		else
			echo -e "$BRIGHT_RED$INVALID_2$CLEAR"
		fi
	done
	partitioning_setup "$value"
	end
}

# DEBUGGING
function initial_cleanup () {
	local device="/dev/nvme0n1"
	#local device="/dev/sda"
	local how_many_partitions_should_there_be=4
	umount --recursive /mnt
	swapoff --all
	vgchange --activate n
	cryptsetup luksClose $LUKS_LVM
	[[ $ENCRYPTED_BOOT_PT = true ]] && cryptsetup luksClose $LUKS_BOOT
	while [[ $(partx --noheadings --show $device | wc --lines) -gt $how_many_partitions_should_there_be ]]; do
		local part_number=$(echo "$(lsblk --list --noheadings --output NAME $device | tail --lines=1)" | grep --only-matching "[^nvme0n1p]")
		cat << EOF | gdisk $device > /dev/null
d
$part_number
w
Y
EOF
	done
	#wipefs -a -f $device
	partprobe $device
}

function main () {
	# DEBUGGING
	initial_cleanup
	call
}

main