#!/bin/bash

################################################################################
# Copyright (C) 2017-2026 NICE s.r.l.
# All Rights Reserved
#
# This software is the confidential and proprietary information
# of NICE s.r.l. ("Confidential Information").
# You shall not disclose such Confidential Information
# and shall use it only in accordance with the terms of
# the license agreement you entered into with NICE.
################################################################################

set -u
set -e

declare -r -i DEBUG_ENABLE_TRACING=0

if [[ $DEBUG_ENABLE_TRACING -ne 0 ]]; then
    set -x
fi

# Create temporary log file
readonly USERNAME=$(whoami)
readonly TMP_SESSION_LOG="/tmp/dcv-agentstarter.$USERNAME.tmp$$.log"
exec >"$TMP_SESSION_LOG" 2>&1

# Load environment from stdin
source <(cat -)

# Configure substitutions
readonly prefix=/usr
readonly exec_prefix=/usr
readonly bindir=/usr/bin
readonly datadir=/usr/share
readonly libdir=/usr/lib64
readonly libexecdir=/usr/lib

# Buildtime constants
if [[ ! ${DCV_AGENT_DIR-} ]]; then
    DCV_AGENT_DIR="${libexecdir}/dcv"
fi
readonly DCV_AGENT_DIR
readonly DCV_AGENT_BIN="$DCV_AGENT_DIR/dcvagent"
readonly AGENT_DCV_MODULE_DIR="${libdir}/dcv/modules"
readonly AGENT_GIO_EXTRA_MODULES=$(echo "${libdir}/dcv/gio/modules:${GIO_EXTRA_MODULES-}" | sed 's/:$//g')
readonly AGENT_LD_LIBRARY_PATH=$(echo "${libdir}/dcv:${LD_LIBRARY_PATH-}" | sed 's/:$//g')
readonly AGENT_XDG_DATA_DIRS=$(echo "${datadir}:${XDG_DATA_DIRS-}" | sed 's/:$//g')
readonly AGENT_GST_PLUGIN_SCANNER="${libexecdir}/dcv/gst-plugin-scanner"
readonly AGENT_GST_PLUGIN_SYSTEM_PATH="${libdir}/dcv/gstreamer-1.0"

# Defaults
readonly DEFAULT_LOG_LEVEL="info"

# Log settings
if [[ ! ${DCV_STARTER_LOG_VERBOSITY-} ]]; then
    declare -r -i DCV_STARTER_LOG_VERBOSITY=3
fi
declare -r -i LOG_VERBOSITY=$DCV_STARTER_LOG_VERBOSITY # 0: silent, 1: error, 2: warning, 3:info, 4:debug
readonly LOG_DOMAIN="agent-starter"

# Constants
declare -r -i UNRECOVERABLE_ERROR=1
declare -r -i DISPLAY_READ_TIMEOUT=10 #secs
declare -r -i DCV_AGENT_STATUS_UNRECOVERABLE_ERROR=6 # see agentapplication.h

# Global variables
declare -i AGENT_PID=0
declare -i EXITING=0

error() { log 1 "ERROR" "$1"; }
warn() { log 2 "WARN " "$1"; }
message() { log 3 "INFO " "$1"; }
debug() { log 4 "DEBUG" "$1"; }
log() {
    if [[ $DEBUG_ENABLE_TRACING -ne 0 ]]; then
        set +x
    fi

    local -r -i verbosity=$1
    local -r level="$2"
    local -r msg="$3"

    if [ $LOG_VERBOSITY -ge "$verbosity" ]; then
         datestring=$(date -u +'%Y-%m-%d %H:%M:%S,%N')
         echo -e "$datestring [$$] $level $LOG_DOMAIN - $msg"
    fi

    if [[ $DEBUG_ENABLE_TRACING -ne 0 ]]; then
        set -x
    fi
}

check_mandatory_env_vars() {
    if [[ ! ${XDG_SESSION_TYPE-} ]]; then
        error "Missing mandatory environment variable XDG_SESSION_TYPE"
        die "$UNRECOVERABLE_ERROR"
    fi
    readonly XDG_SESSION_TYPE

    if [[ ! ${DCV_SESSION_ID-} ]]; then
        error "Missing mandatory environment variable DCV_SESSION_ID"
        die "$UNRECOVERABLE_ERROR"
    fi
    readonly DCV_SESSION_ID

    if [[ ! ${DISPLAY-} ]]; then
        error "Missing mandatory environment variable DISPLAY"
        die "$UNRECOVERABLE_ERROR"
    fi
    readonly DISPLAY

    if [[ ! ${DCV_LOG_DIR-} ]]; then
        error "Missing mandatory environment variable DCV_LOG_DIR"
        die "$UNRECOVERABLE_ERROR"
    fi
    readonly DCV_LOG_DIR

    if [[ ! ${DCV_LOG_MAX_FILE_ROTATIONS-} ]]; then
        error "Missing mandatory environment variable DCV_LOG_MAX_FILE_ROTATIONS"
        die "$UNRECOVERABLE_ERROR"
    fi
    readonly DCV_LOG_MAX_FILE_ROTATIONS

    if [[ ! ${DCV_AGENT_MODE-} ]]; then
        error "Missing mandatory environment variable DCV_AGENT_MODE"
        die "$UNRECOVERABLE_ERROR"
    fi
    readonly DCV_AGENT_MODE
}

rotate_log() {
    local -r -i num=$1
    local -r fname="$2"

    debug "Rotate log file: $fname"

    for ((i = num - 1; i > 0 ; i--)); do
        if [ -r "$fname.$i" ]; then
            mv -f "$fname.$i" "$fname.$((i+1))" 2>/dev/null
        fi
    done

    if [ -r "$fname" ]; then
        if [[ $num -gt 0 ]]; then
            mv -f "$fname" "$fname.1"
        else
            rm -f "$fname"
        fi
    fi

    debug "Done"
}

check_process() {
    local -r -i pid=$1

    if [[ $pid -lt 1 ]]; then
        return 1
    fi

    kill -0 "$pid" 2> /dev/null && :
    if [[ $? -eq 0 ]]; then
        return 0
    else
        return 1
    fi
}

kill_process() {
    local -r -i pid=$1
    local -r pname="$2"

    if ! check_process "$pid"; then
        return
    fi

    message "Kill $pname [$pid]"
    kill -9 "$pid" && :
    if [[ $? -ne 0 ]]; then
        warn "Failed to kill $pname [$pid]"
    fi
    wait "$pid" && :
}

cleanup_processes() {
    kill_process "$AGENT_PID" "DCV agent" && :
}

is_process_alive() {
    local -r -i pid=$1
    local -r pname="$2"

    if ! check_process "$pid"; then
        if wait "$pid"; then
            message "$pname [$pid] exited normally"
            return 1
        else
            warn "$pname [$pid] exited abnormally (returned $?)"
            return 2
        fi
    else
        return 0
    fi
}

die() {
    local -r -i ret=$1

    if [[ $EXITING -ne 0 ]]; then
        return
    fi

    EXITING=1

    # [Judges 16:30] Samson said, “Let me die with the Philistines!”
    cleanup_processes && :
    message "Exiting (exit value: $ret)"
    exit "$ret"
}

launch_agent() {
    local -a cmd
    local -i idx=0

    # Prepare the command line to execute
    cmd[idx++]="$DCV_AGENT_BIN"
    cmd[idx++]="--mode" && cmd[idx++]="$DCV_AGENT_MODE"
    cmd[idx++]="--session-id" && cmd[idx++]="$DCV_SESSION_ID"

    cmd[idx++]="--display" && cmd[idx++]="$DISPLAY"
    cmd[idx++]="--settings-path" && cmd[idx++]="$DCV_SETTINGS_PATH"
    cmd[idx++]="--log-level" && cmd[idx++]="$DCV_LOG_LEVEL"
    cmd[idx++]="--log-dir" && cmd[idx++]="$DCV_LOG_DIR"
    [[ ${1-} ]] && cmd[idx++]="--log-rotate-at-startup"

    message "Launch DCV agent: ${cmd[*]}"
    if [ ! -x "$DCV_AGENT_BIN" ]; then
        error "Cannot launch DCV agent: '$DCV_AGENT_BIN' is not executable"
        die "$UNRECOVERABLE_ERROR"
    fi

    # Create execution env for the agent
    declare -x DCV_MODULE_DIR="$AGENT_DCV_MODULE_DIR"
    debug "DCV_MODULE_DIR=$DCV_MODULE_DIR"

    # force pci-bus-id ordering so we match nvidia-smi output
    declare -x CUDA_DEVICE_ORDER="PCI_BUS_ID"
    debug "CUDA_DEVICE_ORDER=$CUDA_DEVICE_ORDER"

    if [[ ${GIO_EXTRA_MODULES-} ]]; then
        local -r old_gio_extra_modules="$GIO_EXTRA_MODULES"
    fi
    declare -x GIO_EXTRA_MODULES="$AGENT_GIO_EXTRA_MODULES"
    debug "GIO_EXTRA_MODULES=$GIO_EXTRA_MODULES"

    if [[ ${LD_LIBRARY_PATH-} ]]; then
        local -r old_ld_library_path="$LD_LIBRARY_PATH"
    fi
    declare -x LD_LIBRARY_PATH="$AGENT_LD_LIBRARY_PATH"
    debug "LD_LIBRARY_PATH=$LD_LIBRARY_PATH"

    if [[ ${XDG_DATA_DIRS-} ]]; then
        local -r old_xdg_data_dirs="$XDG_DATA_DIRS"
    fi
    declare -x XDG_DATA_DIRS="$AGENT_XDG_DATA_DIRS"
    debug "XDG_DATA_DIRS=$XDG_DATA_DIRS"

    if [[ ${GST_PLUGIN_SCANNER-} ]]; then
        local -r old_gst_plugin_scanner="$GST_PLUGIN_SCANNER"
    fi
    declare -x GST_PLUGIN_SCANNER="$AGENT_GST_PLUGIN_SCANNER"
    debug "GST_PLUGIN_SCANNER=$GST_PLUGIN_SCANNER"

    if [[ ${GST_PLUGIN_SYSTEM_PATH-} ]]; then
        local -r old_gst_plugin_system_path="$GST_PLUGIN_SYSTEM_PATH"
    fi
    declare -x GST_PLUGIN_SYSTEM_PATH="$AGENT_GST_PLUGIN_SYSTEM_PATH"
    debug "GST_PLUGIN_SYSTEM_PATH=$GST_PLUGIN_SYSTEM_PATH"

    "${cmd[@]}" </dev/null &
    AGENT_PID=$!

    unset CUDA_DEVICE_ORDER
    unset DCV_MODULE_DIR

    if [[ ${old_gio_extra_modules-} ]]; then
        GIO_EXTRA_MODULES="$old_gio_extra_modules"
    else
        unset GIO_EXTRA_MODULES
    fi

    if [[ ${old_ld_library_path-} ]]; then
        LD_LIBRARY_PATH="$old_ld_library_path"
    else
        unset LD_LIBRARY_PATH
    fi

    if [[ ${old_xdg_data_dirs-} ]]; then
        XDG_DATA_DIRS="$old_xdg_data_dirs"
    else
        unset XDG_DATA_DIRS
    fi

    if [[ ${old_gst_plugin_scanner-} ]]; then
        GST_PLUGIN_SCANNER="$old_gst_plugin_scanner"
    else
        unset GST_PLUGIN_SCANNER
    fi

    if [[ ${old_gst_plugin_system_path-} ]]; then
        GST_PLUGIN_SYSTEM_PATH="$old_gst_plugin_system_path"
    else
        unset GST_PLUGIN_SYSTEM_PATH
    fi

    if ! check_process "$AGENT_PID"; then
        error "Cannot launch DCV agent ($AGENT_PID)"
        die "$UNRECOVERABLE_ERROR"
    fi
    debug "DCV agent launched with pid '$AGENT_PID'"
}

main() {
    check_mandatory_env_vars
    local -r agent_log="$DCV_LOG_DIR/agentstarter.$USERNAME.$DCV_SESSION_ID.log"

    if [[ ${DCV_LOG_MAX_FILE_ROTATIONS-} ]]; then
        rotate_log "$DCV_LOG_MAX_FILE_ROTATIONS" "$agent_log"
    fi

    exec >> "$agent_log" 2>&1

    message "Agent starter launched (PID: $$)"

    # Use file descriptor 3 to communicate back to the session launcher the PID of the process
    echo "$$" >&3

    trap '
        debug "Handle SIGINT and SINGTERM"
        die 0
    ' INT TERM

    # Remove temporary log file
    cat "$TMP_SESSION_LOG"
    rm "$TMP_SESSION_LOG"

    if [ $LOG_VERBOSITY -ge 4 ]; then
        debug "Execution environment:"
        echo
        env
        echo "========="
    fi

    launch_agent

    set -m
    wait && :
    set +m

    debug "No more children"
}

main "$@"

# ex: set ts=4 sw=4 et syntax=sh :
