#!/bin/sh -eu
# shellcheck disable=SC2059 # use of $bold et al. in printf format str

# defaults
use_bundled_libva_default=0

# if not run from AppImage (eg. extracted), use default values
export APPIMAGE="${APPIMAGE-none}"
ARGV0=${ARGV0-$0}

bold=$(tput bold || true)
red=$(tput setaf 1 || true)
reset=$(tput sgr0 || true)
bold_str=${bold}%s${reset}

DIR=$(dirname "$(realpath "$0")")
readonly AI_LIB_PATH="$DIR"/usr/lib
n_ld_library_path="$AI_LIB_PATH"${LD_LIBRARY_PATH:+":$LD_LIBRARY_PATH"}
LD_PRELOAD=${LD_PRELOAD-}
# there is an issue with running_from_path() which evaluates this executable
# as being system-installed
#export PATH=$DIR/usr/bin:$PATH
export LIBDECOR_PLUGIN_DIR="$DIR/usr/lib/libdecor/plugins-1"
export QT_QPA_FONTDIR="$DIR/usr/share/fonts"
export UG_FONT_DIR="$DIR/usr/share/fonts"
QT_PLUGIN_PATH=$(set -- "$DIR"/usr/lib/qt?/plugins; echo "$1")
export QT_PLUGIN_PATH
export QT_QPA_PLATFORM_PLUGIN_PATH="$QT_PLUGIN_PATH/platforms"

export MAGICK_CONFIGURE_PATH="$DIR/etc/IM"
export MAGICK_CODER_FILTER_PATH="$DIR/usr/share/IM/filters"
export MAGICK_CODER_MODULE_PATH="$DIR/usr/share/IM/coders"

get_tools() {(
        find "$DIR/usr/bin" -mindepth 1 -exec basename {} \; | tr '\n' ' '
)}

# handle symlinks, so eg. if symlinked to hd-rum-transcode, ruh the transcoder
# (no need to use `U.traGrid...AppImage -o hd-rum-transcode`)
get_tool_from_progname() {(
        prog=$(basename "${ARGV0?}")
        # shellcheck disable=SC2046 # intentional
        set -- $(get_tools)
        while [ $# -gt 0 ]; do
                if [ "$prog" = "$1" ]; then
                        echo "$1"
                        return
                fi
                shift
        done
)}

# create convenience symlinks for the AppImage
mk_symlinks() {(
        if [ "$APPIMAGE" = none ]; then
                abs_path=$(realpath "$0")
                dstdir=.
        else
                abs_path=$APPIMAGE
                dstdir=$(dirname "$APPIMAGE")
        fi
        printf "Creating symlinks for ${bold_str}in $bold_str pointing to \
${bold_str}...\n" "$(get_tools)" "$dstdir" "$abs_path"
        cd "$dstdir"
        # shellcheck disable=SC2046 # intentional
        set -- $(get_tools)
        while [ $# -gt 0 ]; do
                ln -s "$abs_path" "$1" || true
                shift
        done
)}

## @param $1  non-empty to show full help
usage() {
        full=${1:+1}
        printf "usage:\n"
        printf "\t${bold}${red}%s${reset} ${bold}[--gui [args]]${reset}\n" "$ARGV0"
        printf "\t\tinvokes GUI\n"
        printf "\n"
        printf "\t${bold}${red}%s${reset} ${bold}--appimage-help${reset}\n" "$ARGV0"
        printf "\t\tprints AppImage related options\n"
        printf "\n"
        printf "\t${bold}${red}%s${reset} ${bold}-h | --help | --fullhelp${reset}\n" "$ARGV0"
        printf "\t\tprints this help (extended version with ${bold}--fullhelp${reset})\n"
        printf "\n"
        printf "\t${bold}${red}%s${reset} ${bold}--ug-help${reset}\n" "$ARGV0"
        printf "\t\tprints UltraGrid help (as '-h' if it weren't run from AppImage)\n"
        printf "\n"
        printf "\t${bold}${red}%s${reset} ${bold}-m|--man [uv|hd-rum-transcode]${reset}\n" "$ARGV0"
        printf "\t\tprints manual page\n"
        printf "\n"
        printf "\t${bold}${red}%s${reset} ${bold}-u|--update [args]${reset}\n" "$ARGV0"
        printf "\t\tupdates AppImage (args will be passed to appimageupdatetool)\n"
        printf "\n"
        printf "\t${bold}${red}%s${reset} ${bold}-o|--tool uv --help${reset}\n" "$ARGV0"
        printf "\t\tprints command-line UltraGrid help\n"
        printf "\n"
        printf "\t${bold}${red}%s${reset} ${bold}-o|--tool <t> [args]${reset}\n" "$ARGV0"
        printf "\t\tinvokes specified tool\n"
        printf "\t\ttool may be one of: ${bold}%s${reset}\n" "$(get_tools)"
        printf "\n"
        if [ "$full" ]; then
                printf "\t${bold}${red}%s${reset} ${bold}--mk-symlinks${reset}\n" "$ARGV0"
                printf "\t\tmake symlinks for the above tools to allow direct run\n"
                printf "\n"
        fi
        printf "\t${bold}${red}%s${reset} ${bold}args${reset}\n" "$ARGV0"
        printf "\t\tinvokes command-line UltraGrid\n"
        printf "\n"

        if [ ! "$full" ]; then
                return
        fi

        printf "environment variables:\n"
        printf "\tAPPIMAGE_DEBUG:          print debug info + used Firejail options\n"
        printf "\tULTRAGRID_AUTOUPDATE:    autoupdate interval in days (0 - check always); -1 - disable update advice\n"
        printf "\tULTRAGRID_BUNDLED_LIBVA: 1 - use bundled libva; 0 - use system libva (if available), default %d\n" $use_bundled_libva_default
        printf "\tULTRAGRID_USE_FIREJAIL:  run the UltraGrid executable with firejail. If\n"
        printf "\t                         the variable contains a profile path (ends with\n"
        printf "\t                         '.profile'), it will be used.\n"
        printf "\tFIREJAIL_OPTS:           custom options to be passed to Firejail\n"
        printf "\n"
}

update_check_days=90
## Trigger update if $ULTRAGRID_AUTOUPDATE days passed.
## If $ULTRAGRID_AUTOUPDATE unset or empty, print update hint if UG binary is older than $update_check_days days.
handle_autoupdates() {(
        if [ "${ULTRAGRID_AUTOUPDATE-0}" -eq -1 ]; then
                return
        fi
        if [ "$APPIMAGE" = none ]; then
                return
        fi
        if expr "$APPIMAGE" : '.*continuous' > /dev/null; then
                update_check_days=30
        fi
        if [ "${ULTRAGRID_AUTOUPDATE-}" ] && [ "$ULTRAGRID_AUTOUPDATE" -ge 0 ]; then
                update_check_days=$ULTRAGRID_AUTOUPDATE
        fi
        appimage_mtime=$(stat -c %Y "$APPIMAGE")
        curr_timestamp=$(date +%s)
        if [ "$curr_timestamp" -lt $((appimage_mtime + update_check_days * 24 * 60 * 60)) ]; then
                return
        fi
        if [ "${ULTRAGRID_AUTOUPDATE-}" ] && [ "$ULTRAGRID_AUTOUPDATE" -ge 0 ]; then
                echo "Running the UltraGrid update because the interval" \
                 "specified by ULTRAGRID_AUTOUPDATE env variable has passed..."
                # update AppImage mtime otherwise if there are no updates
                # after elapsing the interval, the check would run always
                touch "$APPIMAGE"
                if "$DIR/appimageupdatetool" -j "$APPIMAGE"; then
                        debug_msg "No UltraGrid AppImage update available"
                else
                        if "$DIR/appimageupdatetool" "$APPIMAGE"; then
                                # prevent update loops if the new version not
                                # stored for some reason
                                ULTRAGRID_AUTOUPDATE=-1
                                exec "$APPIMAGE" "$@"  # launch updated version
                        fi
                fi
                return
        fi
        printf "UltraGrid binary older than %d days, consider checking updates:\n" "$update_check_days"
        printf "\n"
        printf "%s -u\n" "$ARGV0"
        printf "\t- updates AppImage\n"
        printf "%s -u -j; [ \$? -eq 1 ] && echo Update available || echo No update available\n" "$ARGV0"
        printf "\t- check for update without actually updating\n"
        printf "%s -u -h\n" "$ARGV0"
        printf "\t- prints update options\n"
        printf "\n"
        printf "Hint: you can set environment variable ULTRAGRID_AUTOUPDATE to 1 for daily automatic update or -1 to suppress the above message.\n"
        printf "\n"
)}

debug_msg() {
        if [ -z "${APPIMAGE_DEBUG:-}" ]; then
                return
        fi
        echo "$@"
}

## Tries to find system libva. If found it is preloaded (+libva-drm+libva-x11)
## @retval 0 if preloaded; 1 otherwise
set_libva_ld_preload() {
        [ -f "$AI_LIB_PATH/ultragrid/ultragrid_vcompress_libavcodec.so" ] ||
                return 0
        LOADER=$(get_loader)
        [ -x "$LOADER" ] || return 1
        get_ai_lib_path() {(
                LD_LIBRARY_PATH=$n_ld_library_path LD_TRACE_LOADED_OBJECTS=1 \
                        "$LOADER" "$AI_LIB_PATH/ultragrid/ultragrid_vcompress_\
libavcodec.so" | grep "$1" | grep -v 'not found' | awk '{print $3}'
        )}
        AI_LIBAVCODEC_LIB=$(get_ai_lib_path libavcodec.so)
        check_libva_deps() { # check if all liva*.so satisfied from sys
                [ -n "$1" ] || return 1
                ! LD_TRACE_LOADED_OBJECTS=1 "$LOADER" "$1" 2>/dev/null |
                        grep -q 'libva.*not found'
        }
        check_libva_deps "$AI_LIBAVCODEC_LIB" && check_libva_deps \
                "$(get_ai_lib_path libavutil.so)" || return 1
        LIBVA_LIB=$(LD_TRACE_LOADED_OBJECTS=1 "$LOADER" "$AI_LIBAVCODEC_LIB" \
                2>/dev/null | grep libva.so | grep -v 'not found' |
                awk '{print $3}')
        [ -n "$LIBVA_LIB" ] || return 1
        libva_libs=$LIBVA_LIB
        # add also libva-drm, libva-x11 if present
        for n in libva-drm libva-x11; do
                NAME=$(echo "$LIBVA_LIB" | sed s/libva/$n/)
                if [ -f "$NAME" ]; then
                        libva_libs=$libva_libs:$NAME
                fi
        done
        # preload also VDPAU
        libvdpau_lib=$(LD_TRACE_LOADED_OBJECTS=1 "$LOADER" "$AI_LIBAVCODEC_LIB"\
                 2>/dev/null | grep libvdpau.so | grep -v 'not found' |
                 awk '{print $3}')
        if [ -n "$libvdpau_lib" ]; then
                libva_libs=$libva_libs:$libvdpau_lib
        fi

        S_LD_PRELOAD=$LD_PRELOAD
        export LD_PRELOAD="$libva_libs${LD_PRELOAD:+:$LD_PRELOAD}"

        # check if lavc still binds with system libva
        if LD_LIBRARY_PATH=$n_ld_library_path LD_BIND_NOW=1 LD_WARN=1 \
                        LD_TRACE_LOADED_OBJECTS=1 "$LOADER" \
                        "$AI_LIBAVCODEC_LIB" 2>&1 |
                        grep -q 'undefined symbol'; then
                LD_PRELOAD=$S_LD_PRELOAD
                return 1
        fi
}

## Tries to set LD_PRELOAD to system libva (see set_libva_ld_preload()). If failed, sets path to bundled libva drivers.
setup_vaapi() {
        ULTRAGRID_BUNDLED_LIBVA=${ULTRAGRID_BUNDLED_LIBVA:-$use_bundled_libva_default}
        if [ "$ULTRAGRID_BUNDLED_LIBVA" -ne 1 ]; then
                if ! set_libva_ld_preload; then
                        echo "${bold}${red}Could not set system libva, using bundled libraries instead!${reset}" >&2
                        ULTRAGRID_BUNDLED_LIBVA=1
                fi
        fi
        if [ "$ULTRAGRID_BUNDLED_LIBVA" -eq 1 ]; then
                if [ -d "$DIR/usr/lib/va" ] && [ -z "${LIBVA_DRIVERS_PATH:-}" ]; then
                        export LIBVA_DRIVERS_PATH="$AI_LIB_PATH/va"
                fi
        fi
}

setup_wayland() {
        # use bundled Wayland libs only when not present in system
        sdl_l=$(echo "$DIR"/usr/lib/libSDL[2-9][.-]*so*)
        LOADER=$(get_loader)
        if [ -x "$LOADER" ] && [ -f "$sdl_l" ]; then
                ldd=$(LD_TRACE_LOADED_OBJECTS=1 "$LOADER" "$sdl_l")
                if ! echo "$ldd" | grep libwayland | grep -q 'not found'; then
                        return # we have libwayland* in system
                fi
        elif [ -n "${WAYLAND_DISPLAY-}" ]; then # fallback - don't have SDL2,
                return                          # use env var instead
        fi
        debug_msg "Using bundled libwayland*"
        n_ld_library_path="$n_ld_library_path:$AI_LIB_PATH/wayland"
}

setup_libopengl() {
        qt_ogl=$(echo "$DIR"/usr/lib/libQt[5-9]OpenGL.so*)
        LOADER=$(get_loader)
        if [ ! -x "$LOADER" ] || [ ! -f "$qt_ogl" ]; then
                return
        fi
        ldd=$(LD_TRACE_LOADED_OBJECTS=1 "$LOADER" "$qt_ogl")
        if ! echo "$ldd" | grep libOpenGL.so.0 | grep -q 'not found'; then
                return # we have libOpenGL.so.0 in system
        fi
        debug_msg "Using bundled libOpenGL.so.0*"
        n_ld_library_path="$n_ld_library_path:$AI_LIB_PATH/libopengl"
}

add_whitelist() {
        # expand tilde expansion (like ~/video.mp4; only single ~ now)
        # shellcheck disable=SC2088 # intentional
        if ! expr "$1" : '~/' >/dev/null; then
                path="$1"
        else
                path"$HOME${1#\~}"
        fi

        # if the path doesn't exist, whitelist will fail, solves eg. "file:help"
        if [ ! -e "$path" ]; then
                return
        fi

        if contains_mount "$path"; then
                disable_mount=no
        fi

        if expr "$path" : /tmp >/dev/null; then
                printf -- "$separator--read-write=\"%s\" --mkdir=\"%s\"" "$path" "$path"
        else
                printf -- "$separator--whitelist=\"%s\"" "$path"
        fi
        separator=' '

        abs_path=$(realpath "$path")
        # resolve also symlinks
        if [ "$abs_path" != "$path" ]; then
                add_whitelist "$abs_path"
        fi
}

# whitelist path that may not exist (file that will be created), so eg. also the parent
whitelist_file_path() {
        path=$1
        add_whitelist "$path"
        if expr "$n" : 'dump:' >/dev/null || expr "$n" : '--record' >/dev/null
        then
                printf -- "$separator--mkdir=\"%s\"" "$path" # may not exist
        fi
        if expr "$n" : '.*file[:=]' >/dev/null || expr "$n" : '.*filename' \
                        >/dev/null; then
                fullpath=$(realpath "path")
                parent_dir=$(dirname "$fullpath")
                if [ "$parent_dir" = /dev ] || [ "$parent_dir" = /tmp ]; then
                        return
                fi
                # whitelist the parent folder to allow file creation
                add_whitelist "$parent_dir"
        fi
}

contains_mount() {
        expr "$1" : '.*/mnt' >/dev/null ||
                expr "$1" : '.*/media' >/dev/null ||
                expr "$1" : '.*/run/mount' >/dev/null ||
                expr "$1" : '.*/run/media' >/dev/null
}

## Parse params to get custom rules like whitelists that may be needed to add
## @todo spaces inside paths doesn't work
get_custom_firejail_rules() {
        separator=''
        playback_path=$(echo "$@" | sed -n 's/.*--playback \([^ :]*\).*/\1/p')
        if [ -n "$playback_path" ]; then
                add_whitelist "$playback_path"
        fi
        # print every argument of "filename=", "file[:=]", "dump:" or "--record=" pattern
        for n in "$@"; do
                file_path=$(echo "$n" | sed -n \
                        -e 's/.*filename=\([^:]*\).*/\1/p' \
                        -e 's/.*\(n\|name\)=\([^:]*\).*/\2/p' \
                        -e 's/.*file[:=]\([^:]*\).*/\1/p' \
                        -e 's/dump:\([^:]*\).*/\1/p' \
                        -e 's/^--record=\([^:]*\).*/\1/p')
                if [ -n "$file_path" ]; then
                        whitelist_file_path "$file_path"
                fi
        done
        # -d dump or -d dummy:dump_to_file
        if expr "$*" : '.*dump' >/dev/null || expr "$*" : '.*record' >/dev/null; then
                add_whitelist "$(pwd)"
        fi
        if ! expr "$DIR" : /tmp >/dev/null; then # add AppRun path if not in tmp (== extracted AppImage)
                add_whitelist "$DIR"
        fi
        if [ -z "$separator" ]; then # no whitelist was set
                printf -- "--private"
        fi
        # screen pw - requires root (could be disabled by default.profile) and dbus
        if expr "$*" : '.*screen' >/dev/null; then
                printf " --ignore=noroot"
        elif firejail --version | grep -iq "d-\{0,1\}bus.*enabled"; then
                printf " --dbus-system=filter \
--dbus-system.talk=org.freedesktop.Avahi"
        fi

        if ! contains_mount "$*" && [ "${disable_mount-}" != no ]
        then
                printf " --disable-mnt"
        fi
}

# shellcheck source=/dev/null
. "$DIR/scripts/preload.sh"
setup_vaapi
setup_wayland
setup_libopengl
# ultragrid_aplay_jack.so is not used because it loads JACK with dlopen,
# while portaudio is linked directly to JACK library
set_ld_preload ultragrid_aplay_portaudio.so libjack
set_ld_preload ultragrid_vidcap_pipewire.so libpipewire

## @param $@   <tool> <arguments
setup_firejail() {
        tool=$1
        if [ "${ULTRAGRID_USE_FIREJAIL:-0}" = 0 ] ||
                [ "$ULTRAGRID_USE_FIREJAIL" = no ]; then
                return
        fi
        command -v firejail >/dev/null || { echo "Firejail not present in system!"; exit 1; }
        fj_opt_file=$(mktemp)
        if expr "$ULTRAGRID_USE_FIREJAIL" : '.*\.profile' >/dev/null; then
                FIREJAIL_OPTS="${FIREJAIL_OPTS+$FIREJAIL_OPTS }--profile=$ULTRAGRID_USE_FIREJAIL"
        else
                FJ_TMPDIR=${TMPDIR-/tmp/ultragrid-$(id -u)}
                FIREJAIL_OPTS="${FIREJAIL_OPTS+$FIREJAIL_OPTS }--caps.drop=all\
 --nonewprivs --protocol=unix,inet,inet6,netlink --seccomp\
 --private-bin=bash --private-opt=none --mkdir=$FJ_TMPDIR\
 --read-write=$FJ_TMPDIR --writable-var --ignore=private-tmp"
                FIREJAIL_OPTS="$FIREJAIL_OPTS $(get_custom_firejail_rules "$@") --private-etc=alsa,group,hostname,ld.so.conf,ld.so.cache,ld.so.conf.d,nsswitch.conf,passwd,resolv.conf --ignore=novideo"
                if ! expr "$FIREJAIL_OPTS" : '.*--read-write="/\{1,\}tmp/*"' \
                        > /dev/null; then
                        FIREJAIL_OPTS="--read-only=/tmp $FIREJAIL_OPTS"
                fi
        fi
        if firejail --help | grep -q -- --keep-var-tmp; then
                FIREJAIL_OPTS="$FIREJAIL_OPTS --keep-var-tmp"
        fi
        if [ "$tool" = hd-rum-av.sh ]; then
                FIREJAIL_OPTS="$FIREJAIL_OPTS --private-bin=basename,dirname,\
expr,kill,ps,sed,seq,sh,tput,tr,tty,uname"
        fi
        echo "firejail --env=LD_PRELOAD=${LD_PRELOAD} --env=LD_LIBRARY_PATH=${n_ld_library_path}${FJ_TMPDIR+ --env=TMPDIR=${FJ_TMPDIR}} $FIREJAIL_OPTS " > "$fj_opt_file"
        if [ "${APPIMAGE_DEBUG:-}" ]; then
                opts=$(cat "$fj_opt_file")
                printf "Firejail arguments:\n\t%s\n\n" "$opts"
        fi
}

# run only if Firejail is used to workaround not restored TIO by UG if FJ kills
# the UG process prematurelly; otherwise not needed (exec is used)
fj_sigaction() {
        trap '' TERM # inhibit following signal to ourselves
        if ps -o cmd >/dev/null 2>&1; then
                pgid=$(ps -o pgid= -p $$ | tr -d ' ')
        fi
        if [ $$ -eq "$pgid" ]; then
                kill -- -$$
        else
                echo "pgid $pgid not pid of the script ($$), not sending kill" \
                        >&2
        fi
        trap - INT TERM
        wait
        stty "$stty_orig"
        echo "Restored TIO" >&2
}

run() {
        setup_firejail "$@"
        tool=$1
        shift
        export LD_LIBRARY_PATH="$n_ld_library_path"
        if [ -n "${fj_opt_file-}" ] && [ "$tool" != uv-qt ]; then
                stty_orig=$(stty -g)
                trap fj_sigaction INT TERM
                # shellcheck disable=SC2046 # intentional
                eval $(cat "$fj_opt_file"; rm "$fj_opt_file") "$DIR/usr/bin/$tool" '"$@"'
        else
                exec "$DIR/usr/bin/$tool" "$@"
        fi
}

# TODO remove
if [ "${FIREJAIL_DEBUG-}" ]; then
        export APPIMAGE_DEBUG="$FIREJAIL_DEBUG"
        printf "${bold}Use APPIMAGE_DEBUG instead of FIREJAIL_DEBUG \
(replacement)${reset}\n"
fi

if [ "${APPIMAGE_DEBUG-undef}" = undef ] && [ "${ULTRAGRID_VERBOSE-}" ]; then
        APPIMAGE_DEBUG=$ULTRAGRID_VERBOSE
fi

tool=$(get_tool_from_progname)
if [ "$tool" ]; then
        set -- -o "$tool" "$@"
fi

if [ $# -eq 0 ] || [ "${1-}" = "--gui" ]; then
        handle_autoupdates "$@"
        if [ $# -eq 0 ]; then usage; else shift; fi
        if [ -x "$DIR/usr/bin/uv-qt" ]; then
                run uv-qt --with-uv "$DIR/usr/bin/uv" "$@"
        else
                echo "GUI was not compiled in!" >&2
                exit 1
        fi
elif [ "$1" = "-o" ] || [ "$1" = "--tool" ]; then
        handle_autoupdates "$@"
        TOOL=$2
        shift 2
        if [ "$TOOL" = help ]; then
                printf "available tools: ${bold}%s${reset}\n" "$(get_tools)"
                exit 0
        fi
        # shellcheck disable=SC2086
        run "$TOOL" "$@"
elif [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
        usage
        exit 0
elif [ "$1" = "--ug-help" ]; then
        run uv -h
elif [ "$1" = "--fullhelp" ]; then
        usage full
        exit 0
elif { [ $# -eq 1 ] || [ $# -eq 2 ]; } && { [ "$1" = "-m" ] || [ "$1" = "--man" ]; }; then
        PAGE=${2:-uv}
        man -l "$DIR/usr/share/man/man1/$PAGE.1"
elif [ "$1" = "-u" ] || [ "$1" = "--update" ]; then
        shift
        if [ -d "$DIR/appimageupdatetool-lib" ]; then
                export LD_LIBRARY_PATH="$DIR/appimageupdatetool-lib"
        fi
        touch "$APPIMAGE" # update AppImage mtime to avoid update notices if there are no updates avalable but were checked for
        "$DIR/appimageupdatetool" ${1+"$@" }"$APPIMAGE"
elif [ "$1" = "--mk-symlinks" ]; then
        mk_symlinks
else
        handle_autoupdates "$@"
        run uv "$@"
fi
