# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: Copyright 2023-2024 SUSE LLC
# SPDX-FileCopyrightText: Copyright 2023-2024 Richard Brown

log(){
    echo "[${tik_module}][$(date +"%Y%m%d-%T")][LOG] $*" 1>&2
}

warn() {
    echo "[${tik_module}][$(date +"%Y%m%d-%T")][WARN] $*" 1>&2
    d --warning --text="$*"
}

error() {
    echo "[${tik_module}][$(date +"%Y%m%d-%T")][ERROR] $*" 1>&2
    d --error --text "$*"
    exit 1
}

d(){
    while true
    do
        retval=0
        result="$(zenity "$@")" || retval=$?
        log "[zenity][${retval}][${result}] $@"
        case $retval in
            0)
                return 0
            ;;
            1|255)
                zenity --question --text="Do you really want to quit?" && exit 1
            ;;
        esac
    done
}

# variant of privileged run (prun) function that doesn't require the pkexec call to return 0
prun-opt() {
    if [ "${debug}" == "1" ]; then
        log "[pkexec-noexec] $@"
    else
        retval=0
        pkexec "$@"
        retval=$?
        log "[pkexec][${retval}] $@"
    fi
}

# Most commonly used prun function, which requires the called command to work
prun() {
    prun-opt "$@"
    if [ "${retval}" != "0" ]; then
        error "Command <tt>$@</tt> FAILED"
    fi
}

get_persistent_device_from_unix_node() {
    local unix_device=$1
    local schema=$2
    local node
    local persistent_name
    node=$(basename "${unix_device}")
    for persistent_name in /dev/disk/"${schema}"/*; do
        if [ "$(basename "$(readlink "${persistent_name}")")" = "${node}" ];then
            if [[ ${persistent_name} =~ ^/dev/disk/"${schema}"/nvme-eui ]]; then
                # Filter out nvme-eui nodes as they are not descriptive to the user
                continue
            fi
            echo "${persistent_name}"
            return
        fi
    done
    warn "Could not find <tt>${schema}</tt> representation of <tt>${node}</tt>. Using original device <tt>${unix_device}</tt>"
    echo "${unix_device}"
}

get_disk() {
    # Volume label for the tik install media must be set to "TIKINSTALL" to filter it out from the device list
    tik_volid="TIKINSTALL"
    local disk_id="by-id"
    local disk_size
    local disk_device
    local disk_device_by_id
    local disk_meta
    local disk_list
    local device_array
    local list_items
    local blk_opts="-p -n -r -o NAME,SIZE,TYPE"
    local message
    local blk_opts_plus_label="${blk_opts},LABEL"
    local tik_install_disk_part
    local part_meta
    local part_count
    local part_size
    local part_info
    local part_fs
    local blk_opts_part_info="${blk_opts_plus_label},FSTYPE"
    local usb_match_1="usb"
    local usb_match_2=":0"

    tik_install_disk_part=$(
        eval lsblk "${blk_opts_plus_label}" | \
        tr -s ' ' ":" | \
        grep ":${tik_volid}" | \
        cut -f1 -d:
    )

    for disk_meta in $(
        eval lsblk "${blk_opts}" | grep -E "disk|raid" | tr ' ' ":"
    );do
        disk_size=$(echo "${disk_meta}" | cut -f2 -d:)
        if [[ "${disk_size}" == "0B" ]]; then
            # ignore disks with no size, e.g. empty SD card readers
            continue
        fi
        disk_device="$(echo "${disk_meta}" | cut -f1 -d:)"
        # find partitions and info for this disk
        part_count=0
        part_info=""
        for part_meta in $(
            eval lsblk "${blk_opts_part_info}" | grep -E "${disk_device}.+part.+" | tr ' ' ":"
        );do
            part_count=$(expr $part_count + 1)
            part_size=$(echo "${part_meta}" | cut -f2 -d:)
            part_fs=$(echo "${part_meta}" | cut -f5 -d:)
            if [ -n "${part_info}" ]; then
                part_info="${part_info},"
            fi
            if [ -n "${part_fs}" ]; then
                part_info="${part_info}${part_fs}(${part_size})"
            else
                part_info="${part_info}unknown(${part_size})"
            fi
        done
        if [[ ${part_count} -eq 0 ]]; then
            part_info="none"
        fi
        if [[ "${tik_install_disk_part}" == "${disk_device}"* ]]; then
            # ignore install source device
            continue
        fi
        if [[ ${disk_device} =~ ^/dev/fd ]];then
            # ignore floppy disk devices
            continue
        fi
        if [[ ${disk_device} =~ ^/dev/zram ]];then
            # ignore zram devices
            continue
        fi
        disk_device_by_id=$(
            get_persistent_device_from_unix_node "${disk_device}" "${disk_id}"
        )
        if [[ ( "${TIK_ALLOW_USB_INSTALL_DEVICES}" -ne 1 ) && ( "{$disk_device_by_id}" == *"${usb_match_1}"* || "{$disk_device_by_id}" == *"${usb_match_2}"* ) ]]; then
            # ignore USB devices if TIK_ALLOW_USB_INSTALL_DEVICES not set in config
            continue
        fi
        if [ -n "${disk_device_by_id}" ];then
            disk_device=${disk_device_by_id}
        fi
        list_items="${list_items} $(basename ${disk_device}) ${disk_size} ${part_count} ${part_info}"
        disk_list="${disk_list} $(basename ${disk_device}) ${disk_size}"
    done
    if [ -n "${TIK_INSTALL_DEVICE}" ];then
        # install device overwritten by config.
        local device=${TIK_INSTALL_DEVICE}
        local device_meta
        local device_size
        if [ ! -e "${device}" ];then
            local no_dev="Given device <tt>${device}</tt> does not exist."
            error "${no_dev}"
        fi
        if [ ! -b "${device}" ];then
            local no_block_dev="Given device <tt>${device}</tt> is not a block special."
            error "${no_block_dev}"
        fi
        device_meta=$(
            eval lsblk "${blk_opts}" "${device}" |\
            grep -E "disk|raid" | tr ' ' ":"
        )
        device_size=$(echo "${device_meta}" | cut -f2 -d:)
        # this case is not shown in manual selection, threfore we don't need partition info        
        list_items="$(basename ${device}) ${device_size}"
        disk_list="$(basename ${device}) ${device_size}"
        message="tik installation device set to to: ${device}"
        log "${message}"
    fi
    if [ -z "${list_items}" ];then
        local no_device_text="No device(s) for installation found."
        error "${no_device_text}"
    fi
    if [ -n "${disk_list}" ];then
        local count=0
        local device_index=0
        for entry in ${disk_list};do
            if [ $((count % 2)) -eq 0 ];then
                device_array[${device_index}]=${entry}
                device_index=$((device_index + 1))
            fi
            count=$((count + 1))
        done
        if [ "${device_index}" -eq 1 ];then
            # one single disk device found, use it
            # Add back full path to it
            TIK_INSTALL_DEVICE="/dev/disk/${disk_id}/${device_array[0]}"

            # Fallback to unix device in case by-id does not exist
            # see get_persistent_device_from_unix_node, it does fallback like this.
            if [ ! -e "${TIK_INSTALL_DEVICE}" ]; then
                TIK_INSTALL_DEVICE="/dev/${device_array[0]}"
            fi
        else
            # manually select from storage list
            d --list --column=Disk --column=Size --column=Partitions --column=Filesystems --width=1050 --height=340 --title="Select A Disk" --text="Select the disk to install the operating system to. <b>Make sure any important documents and files have been backed up.</b>\n" ${list_items}
            # Add back full path to it
            TIK_INSTALL_DEVICE="/dev/disk/${disk_id}/${result}"

            # Fallback to unix device in case by-id does not exist
            # see get_persistent_device_from_unix_node, it does fallback like this.
            if [ ! -e "${TIK_INSTALL_DEVICE}" ]; then
                TIK_INSTALL_DEVICE="/dev/${result}"
            fi
        fi
    fi
}

get_img() {
    local list_items
    local message
    local img_meta
    local img_item
    local img_list
    local img_array
    # Images are assumed to be named to the following standard
    # $ProductName.$Version.raw.xz
    # Any extraneous fields may confuse tik's detection, selection and presentation of the image to the user
    for img_meta in $(
        eval cd $TIK_IMG_DIR && (stat --printf="%n\t%s\n" *.raw.xz | tr '	' ":")
    );do
        img_filename="$(echo $img_meta | cut -f1 -d:)"
        img_size="$(echo $img_meta | cut -f2 -d:)"
        list_items="${list_items} ${img_filename} ${img_size}"
    done
    if [ -n "${TIK_INSTALL_IMAGE}" ];then
        # install image overwritten by config.
        local img=${TIK_INSTALL_IMAGE}
        local img_meta
        local img_size
        if [ ! -e "${img}" ];then
            local no_img="Given image <tt>${img}</tt> does not exist."
            error "${no_img}"
        fi
        if [ ! -s "${img}" ];then
            local empty_img="Given image <tt>${img}</tt> is empty."
            error "${empty_img}"
        fi
        img_meta=$(
            eval cd $TIK_IMG_DIR && (stat --printf="%n\t%s\n" $img | tr '	' ":")
        )
        img_filename="$(echo $img_meta | cut -f1 -d:)"
        img_size="$(echo $img_meta | cut -f2 -d:)"
        list_items="${list_items} ${img_filename} ${img_size}"
        message="tik installation image set to to: ${img}"
        log "${message}"
    fi
    if [ -z "${list_items}" ];then
        local no_image_text="No images(s) for installation found"
        error "${no_image_text}"
    fi
    img_list=${list_items}
    if [ -n "${img_list}" ];then
        local count=0
        local img_index=0
        for entry in ${img_list};do
            if [ $((count % 2)) -eq 0 ];then
                img_array[${img_index}]=${entry}
                img_index=$((img_index + 1))
            fi
            count=$((count + 1))
        done
        if [ "${img_index}" -eq 1 ];then
            # one single disk image found, use it
            TIK_INSTALL_IMAGE="${img_array[0]}"
        else
            # manually select from storage list
            d --list --column=Disk --column=Size --title="Select A Image" --text="Select the operating system image to install.\n" ${list_items}
            TIK_INSTALL_IMAGE="$result"
        fi
    fi
}

dump_image() {
    local image_source_files=$1
    local image_target=$2
    local image_source

    d --question --no-wrap --title="Begin Installation?" --text="Once the installation begins the changes to the selected disk are irreversible.\n\n<b>Proceeding will fully erase the disk.</b>\n\nContinue with installation?"

    (xzcat ${TIK_IMG_DIR}/${image_source_files} | pv -f -F "# %b copied in %t %r" | prun /usr/bin/dd of=${image_target} bs=64k) 2>&1 | d --progress --title="Installing ${TIK_OS_NAME}" --pulsate --auto-close --no-cancel --width=400
    prun /usr/bin/sync | d --progress --title="Syncing" --pulsate --auto-close --no-cancel --width=400
}

set_boot_target() {
    if [ "${debug}" == "1" ]; then
        log "[debug] Not setting EFI boot target"
    else
        # Cleanup any existing openSUSE boot entries
        prun-opt /usr/sbin/efibootmgr -B -L "openSUSE Boot Manager"
        prun /usr/sbin/efibootmgr -O
        # Currently assuming Aeon-like partition layout and shim name. This function will need extra intelligence to probe partitions for other image layouts
        prun /usr/sbin/efibootmgr -c -L "openSUSE Boot Manager" -d ${TIK_INSTALL_DEVICE} -l "\EFI\systemd\shim.efi" -p 2
        # Log to show the resulting eficonfig
        log "[efibootmgr] $(prun /usr/sbin/efibootmgr)"
    fi
}

load_modules() {
local module_dir
if [[ $2 = "custom" ]]; then
    module_dir=$TIK_CUSTOM_DIR/modules/$1
else
    module_dir=$tik_dir/modules/$1
fi
if [ -n "$(ls -A $module_dir)" ]; then
for f in $module_dir/*
    do
        tik_module="$f"
        log "[START] $module_dir/$f"
        . $f
        log "[STOP] $module_dir/$f"
    done
fi
tik_module="tik"
}
