#!/bin/bash
###############################################################
### arch boostrap script
### Copyright (C) 2021 ALEXPRO100 (alealexpro100)
### License: GPL v3.0
###############################################################

set -e

#Use library
export ALEXPRO100_LIB_LOCATION="${ALEXPRO100_LIB_LOCATION:-./lib/alexpro100_lib.sh}"
# shellcheck disable=SC1091
source ./lib/common/lib_connect.sh

has_root || return_err "This script requries root permissions!"

# Check options.
if [[ -z $3 ]]; then
  echo_help "Arch linux boostrap script.\n" \
  "Supports CUSTOM_ARCH_REPO, CORE_NAME flags.\n" \
  "Example 1: $0 x86_64 https://mirrors.evowise.com/archlinux /mnt base-devel mc htop\n" \
  "Example 2: CUSTOM_ARCH_REPO=1 CORE_NAME=arch_repo $0 x86_64 http://mirror.site/arch_repo /mnt base-devel mc htop\n"
fi

arch=$1 mirror=$2 dir="$(realpath "$3")"; shift 3
install_packages="$*"

# Offline parameters verify
[[ $arch == "x86_64" || $arch == "aarch64" || $arch == "arm" || $arch == "armv6h" || $arch == "armv7h" || $arch == "i686" ]] || return_err "Incorrect arch $arch! Supported: x86_64,i686,aarch64,arm,armv6h,armv7h"
is_url "$mirror" || return_err "Link $mirror does not seem to be URL!"
[[ -d $dir ]] || return_err "Directory $dir does not exist!"


parse_arch "$(uname -m)"
if [[ $arch == "$arch_arch" ]]; then
  arch_chroot_command="chroot_rootfs auto"
else
  parse_arch "$arch"
  if qemu_chroot check "$qemu_arch" ok; then
    arch_chroot_command="qemu_chroot $qemu_arch"
  else
    return_err "Qemu emulation is not supported by your system. Aboring."
  fi
fi

create_tmp_dir arch_sys
cache_dir="$dir/var/cache/pacman/pkg"
CORE_NAME="${CORE_NAME:-core}"
db_file="$dir/var/lib/pacman/sync/$CORE_NAME.db"
core_repo_dir="$arch_sys/var/lib/pacman/local"

AP100_DBG msg_print debug "Preparing rootfs... "
mkdir "$dir" &>/dev/null || :
mkdir -m 0755 "$dir/var" "$dir/var/lib" "$dir/var/lib/pacman" "$dir/log" "$dir/dev" "$dir/run" "$dir/etc" "$dir/etc/pacman.d" &>/dev/null || :
mkdir -m 1777 "$dir/tmp" &>/dev/null || :
mkdir -m 0555 "$dir/sys" "$dir/proc" &>/dev/null || :
mkdir -p "$cache_dir" "$dir/var/lib/pacman/sync" &>/dev/null || :
mkdir -p "$core_repo_dir" &>/dev/null || :


msg_print note "(1) Downloading $CORE_NAME repo file..."
if [[ $CUSTOM_ARCH_REPO == "1" ]]; then
  get_file_s "$db_file" "$mirror/$CORE_NAME.db"
  repo="$mirror" core_repo="$mirror"
else
  if check_url "$mirror/$CORE_NAME/os/$arch/$CORE_NAME.db" &>/dev/null; then
    get_file_s "$db_file" "$mirror/$CORE_NAME/os/$arch/$CORE_NAME.db"
    core_repo="$mirror/$CORE_NAME/os/$arch"
    repo="$mirror/\$repo/os/\$arch"
  else
    if check_url "$mirror/$arch/$CORE_NAME/$CORE_NAME.db" &>/dev/null; then
      get_file_s "$db_file" "$mirror/$arch/$CORE_NAME/$CORE_NAME.db"
      core_repo="$mirror/$arch/$CORE_NAME"
      repo="$mirror/\$arch/\$repo"
    else
      if check_url "$mirror/$CORE_NAME/$arch/$CORE_NAME.db" &>/dev/null; then
        get_file_s "$db_file" "$mirror/$CORE_NAME/$arch/$CORE_NAME.db"
        core_repo="$mirror/$CORE_NAME/$arch"
        repo="$mirror/\$repo/\$arch"
      else
        return_err "Incorrect mirror $mirror!"
      fi
    fi
  fi
fi

arccat gz "$db_file" | tar xf - -C "$core_repo_dir" &>/dev/null

#Get dependenices and provides lists.
declare -A pkgs=() provides=() depends=() md5_sums=()
pkg_name='' pkg_file='' read_next=''
while IFS=$'\n' read -r line; do
  if [[ $line =~ \%([A-Z0-9]+)\%$ ]]; then
    read_next=${BASH_REMATCH[1]}
  else
    if [[ -z $read_next || -z $line || $line == "\n" ]]; then
      continue
    else
      case $read_next in
        FILENAME) pkg_file="$line";;
        NAME) pkg_name="$line"; provides[$pkg_name]="$pkg_name" depends[$pkg_name]='' pkgs[$pkg_name]="$pkg_file";;
        DEPENDS) [[ $line =~ ([a-zA-Z][a-zA-Z0-9\._\.\+\-]+)[\>\=\<]? ]] && depends[$pkg_name]="${depends[$pkg_name]}\n${BASH_REMATCH[1]}";;
        PROVIDES) [[ $line =~ ([a-zA-Z][a-zA-Z0-9\._\.\+\-]+)[\>\=\<]? ]] && provides[${BASH_REMATCH[1]}]="$pkg_name";;
        MD5SUM) md5_sums[$pkg_name]="$line";;
      esac
    fi
  fi
done < <(find "$core_repo_dir" -type f -exec cat {} \;)
unset pkg_name pkg_file read_next

#Resolve dependenices.
msg_print note "(2) Resolving dependencies..."
declare -A to_download=()
function resolve_pkg() {
  local line pkg dep
  for dep in "$@"; do
    pkg="${provides[$dep]}"
    # Some packages are located in other repository. Thus we ignore them.
    if [[ -n $pkg && -z ${to_download[$pkg]} ]]; then
      to_download[$pkg]="${pkgs[$pkg]}"
      #We use 'echo' to have correct newline symbols.
      while IFS=$'\n' read -r line; do
        [[ -z $line || $line == "\n" ]] && continue
        resolve_pkg "$line"
      done < <(echo -e "${depends[$pkg]}")
    fi
  done
}
resolve_pkg archlinux-keyring filesystem pacman ca-certificates
unset pkgs provides depends

function do_download() {
  for pkg in "${!to_download[@]}"; do
    AP100_DBG msg_print debug "I: Retriving $pkg... "
    # Download packages.
    if [[ -f "${cache_dir}/${to_download[$pkg]}" ]]; then
      if echo "${md5_sums[$pkg]} ${cache_dir}/${to_download[$pkg]}" | md5sum -c - &>> /dev/null; then
        AP100_DBG echo -n "exists!"
      else
        rm -rf "${cache_dir:?}/${to_download[$pkg]}"
        get_file_s "${cache_dir}/${to_download[$pkg]}" "${core_repo}/${to_download[$pkg]}"
      fi
    else
      get_file_s "${cache_dir}/${to_download[$pkg]}" "${core_repo}/${to_download[$pkg]}"
    fi
    AP100_DBG echo ""
  done
}
do_download &
show_progress train $! "(3) Downloading CORE packages: "

function do_check() {
  for pkg in "${!to_download[@]}"; do
    if ! echo "${md5_sums[$pkg]}  ${cache_dir}/${to_download[$pkg]}" | md5sum -c &>> /dev/null; then
      return_err "Wrong hash for package $pkg!"
    fi
  done
}
do_check &
show_progress train $! "(4) Checking packages integrity..."

function do_unpack() {
  for pkg_file in "${to_download[@]}"; do
    AP100_DBG msg_print debug "I: Unpacking $pkg_file... "
    case "${pkg_file}" in
      *.gz) tar -xzf "${cache_dir}/${pkg_file}" -C "$arch_sys" --exclude='.INSTALL' --exclude='.MTREE' --exclude='.PKGINFO' --exclude='.BUILDINFO' 2>/dev/null || return_err "Failed extraction ${pkg_file}!";;
      *.bz2) tar -xjf "${cache_dir}/${pkg_file}" -C "$arch_sys" --exclude='.INSTALL' --exclude='.MTREE' --exclude='.PKGINFO' --exclude='.BUILDINFO' 2>/dev/null || return_err "Failed extraction ${pkg_file}!";;
      *.xz) tar -xJf "${cache_dir}/${pkg_file}" -C "$arch_sys" --exclude='.INSTALL' --exclude='.MTREE' --exclude='.PKGINFO' --exclude='.BUILDINFO' 2>/dev/null || return_err "Failed extraction ${pkg_file}!";;
      *.zst) arccat zst "${cache_dir}/${pkg_file}" | tar -xf - -C "$arch_sys" --exclude='.INSTALL' --exclude='.MTREE' --exclude='.PKGINFO' --exclude='.BUILDINFO' 2>/dev/null || return_err "Failed extraction ${pkg_file}!";;
      *) return_err "Incorrect format of archive ${pkg_file}! Exiting...";;
    esac
  done
}
do_unpack &
show_progress train $! "(5) Unpacking packages to temporary rootfs: "

AP100_DBG msg_print debug "Preparing temporary rootfs..."
mkdir -p "$arch_sys/etc/pacman.d"
if [[ $CUSTOM_ARCH_REPO == "1" ]]; then
  sed -i 's|SigLevel    = Required DatabaseOptional|SigLevel = Never|;/Include =/d;/\[[a-z]\+\]/d;' "$arch_sys/etc/pacman.conf"
  echo -e "[$CORE_NAME]\nServer = $mirror" >> "$arch_sys/etc/pacman.conf"
else
  echo "Server = $repo" > "$arch_sys/etc/pacman.d/mirrorlist"
fi
echo -e "update-ca-trust\npacman-key --init\npacman-key --populate\ncp -a {,/mnt}/etc/pacman.d/gnupg" >> "$arch_sys/root/run.sh"
echo "pacman -r /mnt --cachedir /mnt/var/cache/pacman/pkg --noconfirm -Sy base $install_packages" >> "$arch_sys/root/run.sh"
chmod +x "$arch_sys/root/run.sh"

function do_install() {
  #Avoid "fixing" directory in order to mount filesystems normally (requried by pacman).
  ROOTFS_DIR_NO_FIX=1
  chroot_add_mount dir "$dir" "$arch_sys/mnt" --rbind --make-rslave
  chroot_setup "$arch_sys/mnt"
  $arch_chroot_command "$arch_sys" /bin/bash /root/run.sh
  msg_print note "Cleaning up..."
  rm -rf "$arch_sys"
  # Failsafe (file may does not exist)
  mv "$dir"/etc/resolv.conf{.pacnew,} || :
  mv "$dir"/etc/hosts{.pacnew,} || :
  [[ $CUSTOM_ARCH_REPO != "1" ]] && echo "Server = $repo" >> "$dir"/etc/pacman.d/mirrorlist.used
}
if [[ $AP100_DBG_ON == 1 ]]; then
  msg_print debug "(6) Installing to chosen directory..."
  do_install
else
  do_install &>/dev/null &
  show_progress train $! "(6) Installing to chosen directory..."
fi

msg_print note "Archlinux was succesfully bootstrapped to $dir."
