#!/usr/bin/env bash
# install_keyboard_shortcut=<Control>E

# Source the script 'common-functions.sh'.
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
ROOT_DIR=$(grep --only-matching "^.*scripts[^/]*" <<<"$SCRIPT_DIR")
source "$ROOT_DIR/common-functions.sh"

_main() {
    local input_files=""
    local output_dir=""

    # Execute initial checks.
    _check_dependencies ""
    input_files=$(_get_files "par_type=file")

    local dependencies=""
    dependencies=$(_get_dependencies "$input_files")
    _check_dependencies "$dependencies"
    _display_wait_box "0"

    export -f \
        _all_commands_exists \
        _get_command \
        _run_command

    # Execute the function '_main_task' for each file in parallel.
    _run_task_parallel "$input_files" "$output_dir"
    _display_result_box ""
}

_main_task() {
    local input_file=$1
    local output_dir=$2

    # Run the main process.
    local command=""
    command=$(_get_command "$input_file" "available")
    _run_command "$input_file" "$command" "$output_dir"
}

_get_dependencies() {
    local input_files=$1
    local dependencies=""

    # Check dependencies for each file.
    for input_file in $input_files; do
        local command=""
        command=$(_get_command "$input_file" "available")

        # If there is an available command, skip the dependency.
        if [[ -n "$command" ]]; then
            continue
        fi

        command=$(_get_command "$input_file" "default")

        case $command in
        "7za")
            dependencies+="
            command=7za; pkg_manager=apt; package=p7zip-full |
            command=7za; pkg_manager=dnf; package=p7zip |
            command=7za; pkg_manager=pacman; package=p7zip |
            command=7za; pkg_manager=zypper; package=p7zip"
            ;;
        "ar") dependencies+="command=ar; package=binutils" ;;
        "bsdtar")
            dependencies+="
                command=bsdtar; pkg_manager=apt; package=libarchive-tools |
                command=bsdtar; pkg_manager=dnf |
                command=bsdtar; pkg_manager=pacman; package=libarchive |
                command=bsdtar; pkg_manager=zypper"
            ;;
        "bsdtar+unar+7za")
            dependencies+="
                command=bsdtar; pkg_manager=apt; package=libarchive-tools |
                command=bsdtar; pkg_manager=dnf |
                command=bsdtar; pkg_manager=pacman; package=libarchive |
                command=bsdtar; pkg_manager=zypper"
            dependencies+=$'\n'
            dependencies+="
                command=unar; pkg_manager=apt |
                command=unar; pkg_manager=dnf |
                command=unar; pkg_manager=pacman; package=unarchiver |
                command=unar; pkg_manager=zypper"
            dependencies+=$'\n'
            dependencies+="
                command=7za; pkg_manager=apt; package=p7zip-full |
                command=7za; pkg_manager=dnf; package=p7zip |
                command=7za; pkg_manager=pacman; package=p7zip |
                command=7za; pkg_manager=zypper; package=p7zip"
            ;;
        "bzip2") dependencies+="command=bzip2" ;;
        "cpio") dependencies+="command=cpio" ;;
        "gpg")
            dependencies+="
                command=gpg; pkg_manager=apt; package=gpg |
                command=gpg; pkg_manager=dnf; package=gnupg2 |
                command=gpg; pkg_manager=pacman; package=gnupg |
                command=gpg; pkg_manager=zypper; package=gpg2"
            ;;
        "gzip") dependencies+="command=gzip" ;;
        "lha") dependencies+="command=lha; package=lhasa" ;;
        "lrzip") dependencies+="command=lrzip" ;;
        "lz4") dependencies+="command=lz4" ;;
        "lzip") dependencies+="command=lzip" ;;
        "lzop") dependencies+="command=lzop" ;;
        "tar") dependencies+="command=tar" ;;
        "tar+bzip2") dependencies+="command=tar | command=bzip2" ;;
        "tar+gzip") dependencies+="command=tar | command=gzip" ;;
        "tar+lrzip") dependencies+="command=lrztar; package=lrzip" ;;
        "tar+lz4") dependencies+="command=tar | command=lz4" ;;
        "tar+lzip") dependencies+="command=tar | command=lzip" ;;
        "tar+lzop") dependencies+="command=tar | command=lzop" ;;
        "tar+xz")
            dependencies+="
                command=tar |
                command=xz; pkg_manager=apt; package=xz-utils |
                command=xz; pkg_manager=dnf |
                command=xz; pkg_manager=pacman |
                command=xz; pkg_manager=zypper"
            ;;
        "tar+zstd") dependencies+="command=tar | command=zstd" ;;
        "unar")
            dependencies+="
                command=unar; pkg_manager=apt |
                command=unar; pkg_manager=dnf |
                command=unar; pkg_manager=pacman; package=unarchiver |
                command=unar; pkg_manager=zypper"
            ;;
        "unrar") dependencies+="command=unrar" ;;
        "unsquashfs")
            dependencies+="
            command=unsquashfs; pkg_manager=apt; package=squashfs-tools |
            command=unsquashfs; pkg_manager=dnf; package=squashfs-tools |
            command=unsquashfs; pkg_manager=pacman; package=squashfs-tools |
            command=unsquashfs; pkg_manager=zypper; package=squashfs"
            ;;
        "unzip") dependencies+="command=unzip" ;;
        "xorriso") dependencies+="command=xorriso" ;;
        "xz")
            dependencies+="
                command=xz; pkg_manager=apt; package=xz-utils |
                command=xz; pkg_manager=dnf |
                command=xz; pkg_manager=pacman |
                command=xz; pkg_manager=zypper"
            ;;
        "zpaq") dependencies+="command=zpaq" ;;
        "zstd") dependencies+="command=zstd" ;;
        esac
        dependencies+=$'\n'
    done

    printf "%s" "$dependencies"
}

_get_command() {
    local input_file=$1
    local command_status=$2
    local preferred_commands=""
    local command=""

    # Define the preferred commands for each archive format.
    case "${input_file,,}" in
    *.tar.bz | *.tbz | *.tar.bz2 | *.tbz2 | *.tb2) preferred_commands="tar+bzip2 unar 7za" ;;
    *.tar.gz | *.tgz) preferred_commands="tar+gzip unar 7za" ;;
    *.tar.lrz | *.tlrz) preferred_commands="tar+lrzip" ;;
    *.tar.lz) preferred_commands="tar+lzip" ;;
    *.tar.lz4) preferred_commands="tar+lz4" ;;
    *.tar.lzma | *.tlz) preferred_commands="tar+lzma unar 7za" ;;
    *.tar.lzo | *.tzo) preferred_commands="tar+lzop" ;;
    *.tar.xz | *.txz) preferred_commands="tar+xz unar 7za" ;;
    *.tar.z | *.taz) preferred_commands="tar+gzip unar 7za" ;;
    *.tar.zst | *.tzst) preferred_commands="tar+zstd" ;;
    *.7z) preferred_commands="7za unar bsdtar" ;;
    *.ace) preferred_commands="unar" ;;
    *.alz) preferred_commands="unar" ;;
    *.ar) preferred_commands="ar bsdtar" ;;
    *.arc) preferred_commands="unar" ;;
    *.arj) preferred_commands="unar" ;;
    *.bz | *.bz2) preferred_commands="bzip2 unar 7za" ;;
    *.cab) preferred_commands="unar" ;;
    *.cpio) preferred_commands="cpio unar bsdtar" ;;
    *.deb | *.udeb) preferred_commands="ar" ;;
    *.gpg | *.asc | *.pgp) preferred_commands="gpg" ;;
    *.gz) preferred_commands="gzip unar 7za" ;;
    *.iso) preferred_commands="xorriso bsdtar" ;;
    *.lrz) preferred_commands="lrzip" ;;
    *.lz) preferred_commands="lzip" ;;
    *.lz4) preferred_commands="lz4" ;;
    *.lzh | *.lzs | *.lha) preferred_commands="lha unar" ;;
    *.lzma) preferred_commands="xz unar" ;;
    *.lzo) preferred_commands="lzop" ;;
    *.msi) preferred_commands="unar" ;;
    *.pak) preferred_commands="unar" ;;
    *.rar) preferred_commands="unrar unar bsdtar" ;;
    *.sit) preferred_commands="unar" ;;
    *.sqsh | *.squashfs | *.sfs) preferred_commands="unsquashfs" ;;
    *.tar | *.gtar | *.gem) preferred_commands="tar bsdtar unar 7za" ;;
    *.xar) preferred_commands="unar bsdtar" ;;
    *.xz) preferred_commands="xz unar 7za" ;;
    *.z) preferred_commands="gzip unar" ;;
    *.zip) preferred_commands="7za bsdtar unar unzip" ;;
    *.zoo) preferred_commands="unar" ;;
    *.zpaq) preferred_commands="zpaq" ;;
    *.zst) preferred_commands="zstd" ;;
    *) preferred_commands="bsdtar+unar+7za" ;;
    esac

    local preferred_commands_array=()
    IFS=" " read -r -a preferred_commands_array <<<"$preferred_commands"

    if [[ "$command_status" == "default" ]]; then
        # The default command is the first command of preferred commands.
        command=${preferred_commands_array[0]}
    elif [[ "$command_status" == "available" ]]; then
        # Get the available command in the system.
        local preferred_command=""
        for preferred_command in "${preferred_commands_array[@]}"; do
            if [[ "$preferred_command" == *"+"* ]]; then
                if _all_commands_exists "${preferred_command//+/ }"; then
                    command=$preferred_command
                    break
                fi
            elif _command_exists "$preferred_command"; then
                command=$preferred_command
                break
            fi
        done
    fi

    printf "%s" "$command"
}

_run_command() {
    local input_file=$1
    local command=$2
    local output_dir=$3
    local exit_code=0
    local filename=""
    local std_output=""

    if [[ -z "$output_dir" ]]; then
        output_dir=$(dirname -- "$input_file")
    fi

    # Try to use the command '7z' instead of '7za'.
    local command_7za="7za"
    if [[ "$command" == *"7za"* ]]; then
        _command_exists "7z" && command_7za="7z"
    fi

    filename=$(basename -- "$input_file")             # Remove the path.
    filename=$(_strip_filename_extension "$filename") # Remove the extension.

    __temp_dir_cd() {
        temp_dir_output=$(_get_temp_dir_local "$output_dir" "$filename")
        pushd "$temp_dir_output" &>/dev/null || true
    }
    __temp_dir_exit() {
        # Exit the temporary directory.
        popd &>/dev/null || true
    }
    __temp_dir_remove() {
        rm -rf -- "$temp_dir_output" &>/dev/null
    }
    __get_password() {
        _display_password_box "Enter the password for the archive '$(basename -- "$input_file")'.\nPassword:"
    }

    # Use a local temporary directory to work.
    __temp_dir_cd

    case "$command" in
    "7za")
        std_output=$($command_7za x -aoa -p"null" -- "$input_file" 2>&1)
        exit_code=$?

        if ((exit_code != 0)) && grep -i -q "wrong password" <<<"$std_output"; then
            local password=""
            __temp_dir_exit && __temp_dir_remove
            password=$(__get_password) || exit 1
            __temp_dir_cd
            std_output=$($command_7za x -aoa -p"$password" -- "$input_file" 2>&1)
            exit_code=$?
        fi
        ;;
    "ar") std_output=$(ar x -- "$input_file" 2>&1) ;;
    "bsdtar")
        std_output=$(bsdtar --passphrase "null" -xf "$input_file" 2>&1)
        exit_code=$?

        if ((exit_code != 0)) && grep -i -q "incorrect passphrase" <<<"$std_output"; then
            local password=""
            __temp_dir_exit && __temp_dir_remove
            password=$(__get_password) || exit 1
            __temp_dir_cd
            std_output=$(bsdtar --passphrase "$password" -xf "$input_file" 2>&1)
            exit_code=$?
        fi
        ;;
    "bzip2") std_output=$(bzip2 -d -c -k -- "$input_file" >"$filename") ;;
    "cpio") std_output=$(cpio -idv <"$input_file" 2>&1) ;;
    "gpg") std_output=$(gpg --batch --yes --decrypt --output "$filename" -- "$input_file" 2>&1) ;;
    "gzip") std_output=$(gzip -d -c -k -- "$input_file" >"$filename") ;;
    "lha") std_output=$(lha -e "$input_file" 2>&1) ;;
    "lrzip") std_output=$(lrzip -d -o "$temp_dir_output/$filename" -- "$input_file" 2>&1) ;;
    "lz4") std_output=$(lz4 -d -c -- "$input_file" >"$filename") ;;
    "lzip") std_output=$(lzip -d -c -k -- "$input_file" >"$filename") ;;
    "lzop") std_output=$(lzop -d -- "$input_file" 2>&1) ;;
    "tar+bzip2") std_output=$(tar --extract --bzip2 --file="$input_file" 2>&1) ;;
    "tar+gzip") std_output=$(tar --extract --gzip --file="$input_file" 2>&1) ;;
    "tar+lrzip") std_output=$(lrztar -d "$input_file" 2>&1) ;;
    "tar+lzip") std_output=$(tar --extract --lzip --file="$input_file" 2>&1) ;;
    "tar+lz4") std_output=$(lz4 -d -c -- "$input_file" | tar xf -) ;;
    "tar+lzma") std_output=$(tar --extract --lzma --file="$input_file" 2>&1) ;;
    "tar+lzop") std_output=$(tar --extract --lzop --file="$input_file" 2>&1) ;;
    "tar+xz") std_output=$(tar --extract --xz --file="$input_file" 2>&1) ;;
    "tar+z") std_output=$(tar --extract --file="$input_file" 2>&1) ;;
    "tar+zstd") std_output=$(zstd -f -d -c -- "$input_file" | tar xf -) ;;
    "tar") std_output=$(tar --extract --file="$input_file" 2>&1) ;;
    "unar")
        std_output=$(unar -- "$input_file" 2>&1)
        exit_code=$?

        if ((exit_code != 0)) && grep -i -q "requires a password" <<<"$std_output"; then
            local password=""
            __temp_dir_exit && __temp_dir_remove
            password=$(__get_password) || exit 1
            __temp_dir_cd
            std_output=$(unar -force-overwrite -password "$password" -- "$input_file" 2>&1)
            exit_code=$?
        fi
        ;;
    "unrar")
        std_output=$(unrar x -p"null" -- "$input_file" 2>&1)
        exit_code=$?

        if ((exit_code != 0)) && grep -i -q "incorrect password for" <<<"$std_output"; then
            local password=""
            __temp_dir_exit && __temp_dir_remove
            password=$(__get_password) || exit 1
            __temp_dir_cd
            std_output=$(unrar x -p"$password" -- "$input_file" 2>&1)
            exit_code=$?
        fi
        ;;
    "unsquashfs") std_output=$(unsquashfs "$input_file" 2>&1) ;;
    "unzip")
        std_output=$(unzip -P "null" -- "$input_file" 2>&1)
        exit_code=$?

        if ((exit_code != 0)) && grep -i -q "incorrect password" <<<"$std_output"; then
            local password=""
            __temp_dir_exit && __temp_dir_remove
            password=$(__get_password) || exit 1
            __temp_dir_cd
            std_output=$(unzip -P "$password" -- "$input_file" 2>&1)
            exit_code=$?
        fi
        ;;
    "xorriso") std_output=$(xorriso -osirrox on -indev "$input_file" -- -extract / . -rollback_end 2>&1) ;;
    "xz") std_output=$(xz -d -c -k -- "$input_file" >"$filename") ;;
    "zpaq") std_output=$(zpaq x "$input_file" 2>&1) ;;
    "zstd") std_output=$(zstd -d -c -- "$input_file" >"$filename") ;;
    "bsdtar+unar+7za")
        exit_code=1

        # Try to extract with 'bsdtar'.
        if ((exit_code != 0)); then
            std_output=$(bsdtar -xf "$input_file" 2>&1)
            exit_code=$?
        fi

        # Try to extract with 'unar'.
        if ((exit_code != 0)); then
            std_output=$(unar -- "$input_file" 2>&1)
            exit_code=$?
        fi

        # Try to extract with '7za'.
        if ((exit_code != 0)); then
            std_output=$($command_7za x -aoa -- "$input_file" 2>&1)
            exit_code=$?
        fi

        if ((exit_code != 0)); then
            std_output="Could not recognize the archive format."
        fi
        ;;
    esac

    # Check for result errors.
    local exit_code_last=$?
    if ((exit_code != 0)) || ((exit_code_last != 0)); then
        _check_output "1" "$std_output" "$input_file" ""

        # If all extracted files have zero bytes, then abort.
        local non_zero_files=""
        non_zero_files=$(find . -type f ! -size 0 2>/dev/null)
        if [[ -z "$non_zero_files" ]]; then
            __temp_dir_exit && __temp_dir_remove
            exit 1
        fi
    fi

    # Fix permissions of extracted files in "iso" archive format.
    if [[ "${input_file,,}" == *".iso" ]]; then
        chmod --recursive u+rw -- .
    fi

    # Check if the archive has just one item (single file/directory compressed).
    local count_root_items=""
    local single_root_item=""
    count_root_items=$(find . -mindepth 1 -maxdepth 1 2>/dev/null | wc -l)
    if ((count_root_items == 1)); then
        single_root_item=$(find . -mindepth 1 -maxdepth 1 2>/dev/null)
        single_root_item="${single_root_item#./}"
    fi

    # Check if the 'temp_dir_output' is empty.
    if ((count_root_items == 0)); then
        __temp_dir_exit && __temp_dir_remove
        exit 1
    fi

    __temp_dir_exit

    # Move the items to the correct directory.
    if [[ "$single_root_item" == "$filename" ]] || [[ "$(_strip_filename_extension "$single_root_item")" == "$filename" ]]; then
        # For archives with "one item with the same name of the archive".
        # For example: "README.txt.tar.gz" or "README.tar.gz".
        std_output=$(_move_file "rename" "$temp_dir_output/$single_root_item" "$output_dir/$single_root_item" 2>&1)
        _check_output "$?" "$std_output" "$input_file" ""
    elif [[ "$single_root_item" == "squashfs-root" ]] && [[ "$command" == "unsquashfs" ]]; then
        # For the "squashfs" archive format.
        std_output=$(_move_file "rename" "$temp_dir_output/$single_root_item" "$output_dir/$filename" 2>&1)
        _check_output "$?" "$std_output" "$input_file" ""
    else
        # For archives with "one item with a different name of the archive" or "multiple items".
        std_output=$(_move_file "rename" "$temp_dir_output" "$output_dir/$filename" 2>&1)
        _check_output "$?" "$std_output" "$input_file" ""
    fi

    __temp_dir_remove
}

_all_commands_exists() {
    local commands=$1

    local command=""
    local commands_array=()
    IFS=" " read -r -a commands_array <<<"$commands"

    for command in "${commands_array[@]}"; do
        if ! _command_exists "$command"; then
            return 1
        fi
    done
    return 0
}

_main "$@"
