#!/bin/bash

# load config
source /etc/de-p1st-installer/installer.cfg       || { exit 1; }

# load functions
source /usr/lib/de-p1st-installer/util.sh         || { exit 1; }
source /usr/lib/de-p1st-installer/user-input.sh   || { exit 1; }
source /usr/lib/de-p1st-installer/block-device.sh || { exit 1; }


function check_network() {
  echo "Sending ping to wikipedia.de ..."
  ping -c 1 wikipedia.de >/dev/null || {
    echo "Pleas set up network access."
    return 1
  }
}

function increase_cow_space() {
  # May be useful when running 'pacman -Syu' on the live medium.
  # Usually not necessary!

  # make sure that we are on a live medium:
  findmnt /run/archiso/cowspace >/dev/null || {
    echo "Not on live medium, did not increase cowspace!"
    return 1
  }

  echo "Increasing cowspace partition of live medium ..."
  sudo mount -o remount,size=2G /run/archiso/cowspace || return $?
}

function get_user_input() {
  # @post
  #   BIOS_TYPE (uefi or bios)
  #   FS        (BTRFS, EXT4, F2FS)
  #   HOSTNAME
  #   USERNAME, USER_PWD
  #   LUKS_PWD

  get_block_devices_with_size || return $?
  get_single_choice  TARGET_BLOCK_DEVICE "Select target device for installation" BLOCK_DEVICE_SIZES || return $?

  if [ "${BIOS_TYPE}" = "autodetect" ]; then
    # Detect BIOS type: https://askubuntu.com/a/162573

    # Check exit code; if 0 EFI, else BIOS.
    # "-q": tell grep to output nothing
    if dmesg | grep -q "EFI v"; then
      echo "Detected EFI boot."
      BIOS_TYPE="uefi"
    else
      echo "Detected BIOS boot"
      BIOS_TYPE="bios"
    fi

  else
    # Let user select BIOS type

    TMP1=('uefi' 'Newer mainboards' 'bios' 'Legacy BIOS on older mainboards')
    get_single_choice  BIOS_TYPE "Select your bios type" TMP1 || return $?
  fi

  TMP1=('BTRFS' 'Allows snapshots and dynamic extension of the FS' 'EXT4' 'Default FS of many distributions' 'F2FS' 'Flash-Friendly-FS for SSD or NVMe')
  get_single_choice  FS "Select filesystem to use" TMP1 || return $?

  get_text_input HOSTNAME "Enter hostname:" || return $?
  get_text_input USERNAME "Enter username:" || return $?

  if [ -z "${USER_PWD}" ]; then
    get_text_input USER_PWD "Enter a user password:" || return $?
    get_text_input USER_PWD2 "Please enter the password again:" || return $?
    [[ "${USER_PWD}" == "${USER_PWD2}" ]] || {
      echo "Passwords did not match";
      exit 1;
    }
  fi
  if [ -z "${LUKS_PWD}" ]; then
    get_text_input LUKS_PWD "Enter a disk encryption password:" || return $?
    get_text_input LUKS_PWD2 "Please enter the password again:" || return $?
    [[ "${LUKS_PWD}" == "${LUKS_PWD2}" ]] || {
      echo "Passwords did not match";
      exit 1;
    }
  fi
}

function get_default_mount_options() {
  # @pre
  #   FS
  # @post
  #   FS_DEFAULT_MOUNT_OPTIONS (array)

  FS_DEFAULT_MOUNT_OPTIONS=()

  case "${FS}" in
	BTRFS)
	  # "compress=lzo": archwiki -> Btrfs#Compression
	  # "compress=zstd:1":
	  #   -> https://btrfs.wiki.kernel.org/index.php/Compression#What_are_the_differences_between_compression_methods.3F
    #   -> https://fedoraproject.org/wiki/Changes/BtrfsTransparentCompression#Q:_Why_use_zstd:1_specifically.3F
    #
	  # "Enable compression (better performance, longer flash lifespan)"
		FS_DEFAULT_MOUNT_OPTIONS+=('compress=zstd:1')
		;;
	EXT4)
	  # https://wiki.archlinux.org/index.php/Ext4#Enabling_metadata_checksums
	  # If the CPU supports SSE 4.2, make sure the crc32c_intel kernel module is loaded
		FS_DEFAULT_MOUNT_OPTIONS+=('metadata_csum')
		;;
  F2FS)
    # When mounting the filesystem, specify compress_algorithm=(lzo|lz4|zstd|lzo-rle).
    # Using compress_extension=txt will cause all txt files to be compressed by default.
    FS_DEFAULT_MOUNT_OPTIONS+=('compress_algorithm=lz4')
    ;;
	*)
		echo "Filesystem $FS not yet supported!"
		return 1
		;;
  esac
}

function choose_mount_options() {
  # @pre
  #   FS
  # @post
  #   FS_CHOSEN_MOUNT_OPTIONS (array)

  case "${FS}" in
	BTRFS)
	  # noatime, nodiratime:
	  # - The atime options do impact drive performance;
	  # - noatime implies nodiratime, one does not need to specify both;
	  # - The noatime option fully disables writing file access times to the drive every time you read a file.
	  #   This works well for almost all applications, except for those that need to know if a file has been
	  #   read since the last time it was modified.
	  TMP1=('noatime' "Don't write file/folder access times" 'on' 'ssd' 'Enable if using SSD/NVMe' 'off')
		;;
	EXT4)
	  TMP1=('noatime'" Don't write file/folder access times" 'on')
		;;
  F2FS)
    TMP1=('noatime' "Don't write file/folder access times" 'on')
    ;;
	*)
		echo "Filesystem $FS not yet supported!"
		return 1
		;;
  esac

  get_multi_choice  FS_CHOSEN_MOUNT_OPTIONS "Select mount options" TMP1 || return $?
}

function run_pacstrap() {
  # @pre
  #   BIOS_TYPE

  echo "Running pacstrap ..."
  PKGS=("${ADDITIONAL_PKGS[@]}")

  case "${BIOS_TYPE}" in
  uefi)
    PKGS+=('de-p1st-base')
    ;;
  bios)
    echo "Not yet implemented"
    return 1
    ;;
  *)
    echo "Not yet implemented!"
    return 1
    ;;
  esac

  # If CPU_VENDOR is not empty, then
  if [ -n "${CPU_VENDOR}" ]; then
    case "${CPU_VENDOR}" in
    amd)
      PKGS+=('de-p1st-ucode-amd')
      ;;
    intel)
      PKGS+=('de-p1st-ucode-intel')
      ;;
    none)
      PKGS+=('de-p1st-ucode-placeholder')
      ;;
    *)
      echo "Invalid option '${CPU_VENDOR}'!"
      return 1
      ;;
    esac
  fi

  local args=()
  if [ "${PACSTRAP_INTERACTIVE}" = "1" ]; then
    args+=('-i')  # run interactively
  fi
  args+=('/mnt')

  sudo pacstrap "${args[@]}" "${PKGS[@]}" || return $?
}

function run_genfstab() {
  # @pre
  #   FS

  echo "Generating fstab ..."
  local fstab
  fstab="$(genfstab -U /mnt)"

  case "${FS}" in
	BTRFS)
	  # Remove "subvolid=..." mount option but leave "subvol=..." mount option
	  fstab=$(printf "%s" "${fstab}" | sed 's/,subvolid=[^,\s]\+//') || return $?
	  # Check if fstab does still contain subvolid mount option
    if printf "%s" "${fstab}" | grep -q 'subvolid='; then
	    echo "This should not happen!"
	    return 1
    fi
		;;
	EXT4)
	  true
		;;
  F2FS)
    true
    ;;
	*)
		echo "Filesystem $FS not yet supported!"
		return 1
		;;
  esac

  printf "%s" "${fstab}" | sudo tee /mnt/etc/fstab >/dev/null || return $?
}

function config_hostname_and_hosts() {
  # @pre
  #   HOSTNAME
  #   FQDN (optional, e.g. sub.domain.de)
  #   STATIC_IP (optional, e.g. 93.133.433.133)
  #   IPV6_CAPABLE (optional, e.g. 1)

  echo "Set hostname ..."
  echo "${HOSTNAME}" | sudo tee /mnt/etc/hostname >/dev/null || return $?

  echo "Create hosts file ..."
  # If the system has a permanent IP address, it should be used instead of 127.0.1.1.
  # * https://wiki.archlinux.org/index.php/Installation_guide#Network_configuration

  # Desirable entries IPv4/IPv6:
  # * https://man.archlinux.org/man/hosts.5#EXAMPLES

  # If FQDN not given, use $HOSTNAME.localdomain instead
  FQDN="${FQDN:="${HOSTNAME}.localdomain"}"
  # If STATIC_IP not given, use 127.0.1.1 instead
  STATIC_IP="${STATIC_IP:='127.0.1.1'}"

  echo "# The following lines are desirable for IPv4 capable hosts
127.0.0.1     localhost
# 127.0.1.1 is often used for the FQDN of the machine
${STATIC_IP}  ${FQDN}  ${HOSTNAME}" | sudo tee /mnt/etc/hosts >/dev/null || return $?

  if [ "${IPV6_CAPABLE}" = "1" ]; then
    echo "
# The following lines are desirable for IPv6 capable hosts
::1           localhost ip6-localhost ip6-loopback
ff02::1       ip6-allnodes
ff02::2       ip6-allrouters" | sudo tee -a /mnt/etc/hosts >/dev/null || return $?
  fi
}

function user_and_pwd() {
  # @pre
  #   USERNAME
  #   USER_PWD
  #   ROOT_PWD (optional)

  echo "Adding user and changing shell to /bin/zsh ..."
  # -m: create home
  # -U: Create a group with the same name as the user, and add the user to this group.
  sudo arch-chroot /mnt useradd -m -s /usr/bin/zsh -g wheel "${USERNAME}" || return $?
  sudo arch-chroot /mnt chsh       -s /usr/bin/zsh                        || return $?

  # If ROOT_PWD is not given, the use USER_PWD for root user
  ROOT_PWD="${ROOT_PWD:="${USER_PWD}"}"

  printf "%s:%s" "${USERNAME}" "${USER_PWD}" | sudo chpasswd --root /mnt || return $?
  printf "%s:%s" "root"        "${ROOT_PWD}" | sudo chpasswd --root /mnt || return $?
}

function bootloader() {
  # @pre
  #   TARGET_BLOCK_DEVICE
  #   LUKS_PART_UUID

  echo "Installing grub ..."
  case "${BIOS_TYPE}" in
  uefi)
    # portable fallback efi name for grub:
    # * https://www.rodsbooks.com/efi-bootloaders/installation.html#alternative-naming
    # * arch-chroot /mnt cp /boot/EFI/GRUB/grubx64.efi /boot/EFI/BOOT/bootx64.efi
    sudo arch-chroot /mnt grub-install --target=x86_64-efi --bootloader-id=GRUB --efi-directory=/boot --removable || return $?
    ;;
  bios)
    sudo arch-chroot /mnt grub-install --target=i386-pc "${TARGET_BLOCK_DEVICE}" || return $?
    ;;
  *)
    echo "Not yet implemented!"
    return 1
    ;;
  esac

  echo "Generating /boot/grub/grub.cfg ..."
  sudo sed -i "s|^GRUB_CMDLINE_LINUX=.*\$|GRUB_CMDLINE_LINUX=\"cryptdevice=/dev/disk/by-uuid/${LUKS_PART_UUID}:crypt\"|" \
    /mnt/etc/default/grub || return $?
  sudo arch-chroot /mnt grub-mkconfig -o /boot/grub/grub.cfg || return $?
}

function main() {
  # @pre
  #   bash libraries imported
  # @post
  #   installation finished

  check_network  || return $?
  # out: BIOS_TYPE, FS, HOSTNAME, USERNAME, USER_PWD, LUKS_PWD
  get_user_input || return $?
  # in: CPU_VENDOR (optional)
  # out: CPU_VENDOR
  get_cpu_vendor || return $?

  # in: FS
  # out: FS_DEFAULT_MOUNT_OPTIONS
  get_default_mount_options || return $?
  # in: FS
  # out: FS_CHOSEN_MOUNT_OPTIONS
  choose_mount_options      || return $?

  # in: TARGET_BLOCK_DEVICE, BIOS_TYPE
  # out: BOOT_PART, LUKS_PART
  partition || return $?
  # in: BIOS_TYPE, BOOT_PART, LUKS_PART, LUKS_PWD, FS
  # out: LUKS_PART_UUID, DATA_PART
  format    || return $?

  # Combine default and chosen mount options
  TMP1=("${FS_DEFAULT_MOUNT_OPTIONS[@]}" "${FS_CHOSEN_MOUNT_OPTIONS[@]}") || return $?
  # Join array elements by ","
  join_by "," TMP1 FS_MOUNT_OPTIONS || return $?

  echo "Mounting data partition with options: ${FS_MOUNT_OPTIONS}"
  sudo mount -o "${FS_MOUNT_OPTIONS}" "$DATA_PART" /mnt || return $?

  echo "Mounting boot partition ..."
  mkdir /mnt/boot              || return $?
  sudo mount "$BOOT_PART" /mnt/boot || return $?

  # in: BIOS_TYPE
  run_pacstrap  || return $?
  # in: FS
  run_genfstab  || return $?

  # in: HOSTNAME, FQDN (optional), STATIC_IP (optional), IPV6_CAPABLE (optional)
  config_hostname_and_hosts || return $?
  # in: USERNAME, USER_PWD, ROOT_PWD (optional)
  user_and_pwd  || return $?

  sudo arch-chroot /mnt mkinitcpio -P || return $?
  # in: TARGET_BLOCK_DEVICE, LUKS_PART_UUID
  bootloader    || return $?

  if [ "${LEAVE_MOUNTED}" -eq "1" ]; then
    echo "Leaving partitions below /mnt mounted and ${DATA_PART} opened."
  else
    sudo umount -R /mnt || return $?
    sudo cryptsetup luksClose "$(basename "${DATA_PART}")" || return $?
  fi
  echo "Finished installation without errors!"
}

main "$@"
