#!/bin/bash

#    cvlc v4l2://"$STREAM_SRC_VIDEO_DEVICE" :input-slave=alsa://hw:0,0 --sout="#transcode{vcodec=theo,vb=2000,fps=20,scale=1.0,acodec=vorb,ab=90,channels=1,samplerate=44100}:rtp{sdp=$DEST_URL}"
#    cvlc v4l2://"$STREAM_SRC_VIDEO_DEVICE" --sout="#transcode{vcodec=theo,vb=2000,fps=20,scale=1.0,acodec=vorb,ab=90,channels=1,samplerate=44100}:rtp{sdp=$DEST_URL}" &
#    cvlc v4l2://"$STREAM_SRC_VIDEO_DEVICE" --sout="#transcode{vcodec=h264,vb=800,acodec=mpga,ab=128,channels=2,samplerate=44100,scodec=none}:rtp{sdp=$DEST_URL}" &

#==============================================================================
# Инициализация
#==============================================================================

# Список PID'ов запущенных стримов (При инициализации пуст)
declare -a STREAMS_PIDS

# Получаем имя запускаемого приложения
APP_NAME="$1"
# Получаем время ожидания (в миллисекундах)
WAITING_TIME=$2

# Если не указано время ожидания то установим его равным 10 секундам
if [ -z "$WAITING_TIME" ]; then
    WAITING_TIME=10000
fi

# Имя сетевого интерфейса waydroid
WAYDROID_INTERFACE_NAME='waydroid0'
# Путь к конфигурационному файлу
CONFIG_PATH='/etc/astra-mobile/astra-mobile-waydroid-camera-config.ini'
# Путь к lock файлу
LOCK_PATH='/var/lock/waydroid-rtsp.lock'

# Главная секция файла конфигураций
CONFIG_SECTION_GENERAL='GENERAL'
CONFIG_GENERAL_ACTIVE='Active'
CONFIG_GENERAL_APPS_LIST='AppsList'

# Секция стрима файла конфигураций
CONFIG_SECTION_STREAM='STREAM_#'
CONFIG_STREAM_NAME='Name'
CONFIG_STREAM_SRC_VIDEO_DEVICE='SrcVideoDevice'
CONFIG_STREAM_SRC_AUDIO_DEVICE='SrcAudioDevice'
CONFIG_STREAM_PORT='Port'
CONFIG_STREAM_VCODEC='vcodec'
CONFIG_STREAM_VB='vb'
CONFIG_STREAM_FPS='fps'
CONFIG_STREAM_SCALE='scale'
CONFIG_STREAM_ACODEC='acodec'
CONFIG_STREAM_AB='ab'
CONFIG_STREAM_CHANNELS='channels'
CONFIG_STREAM_SAMPLERATE='samplerate'

#==============================================================================
# Вспомогательные логические функции
#==============================================================================

## @calc
## @brief - Функция вычислит простое выражение (в том числе и с дробными числами)
## @param $1 - Выражение в формате строки. Например '10 / 3'
## @return Вернёт результат вычисления выражения
function calc() {
    local EXPRESSION="$1"
    echo $( echo "$EXPRESSION" | bc -l )
}
## @isTrue
## @brief - Функция вычислит простое логическое выражение (в том числе и с дробными числами)
## @param $1 - Выражение в формате строки. Например '10 >= 3'
## @return Вернёт признак истинности выражения
function isTrue() {
    local EXPRESSION="$1"
    [ $(calc "$EXPRESSION") -ne 0 ]
}

#==============================================================================
# Вспомогательные функции времени
#==============================================================================

## @tick
## @brief - Функция вернёт количество миллисекунд от начала эпохи
## @return Вернёт количество миллисекунд от начала эпохи
function tick() {
    echo $(date +%s%N | cut -b1-13)
}
## @tock
## @brief - Функция вернёт разницу между текущим и полученным временем в миллисекундах от начала эпохи
## @param $1 - Количество миллисекунд от начала эпохи (Из функции tick)
## @return Вернёт разницу во времени в миллисекундах
function tock() {
    local tick_time=$1
    tock_time=$(tick)
    echo $(calc "$tock_time-$tick_time")
}

#==============================================================================
# Функции работы с сетевым интерфейсом waydroid
#==============================================================================

## get_waydroid_interface_ip
## @brief - Функция вернёт ip адрес интерфейса waydroid
## @return Вернёт ip адрес интерфейса waydroid либо пустую строку
function get_waydroid_interface_ip() {
    local regex='[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'
    local data=$(/sbin/ifconfig "$WAYDROID_INTERFACE_NAME" 2>/dev/null)
    local ip=''

    if [[ $data =~ $regex ]]; then
        ip="${BASH_REMATCH[0]}"
    fi

    echo "$ip"
}

## wait_waydroid_interface_ip
## @brief - Функция дождётся запуска интерфейса waydroid и вернёт его ip адрес
## @return Вернёт ip адрес интерфейса waydroid либо пустую строку
function wait_waydroid_interface_ip() {
    # IP хоста сетевого интерфейса waydroid
    local ip=''
    # Время начала ожидания
    local startTime=$(tick)
    # Запускаем ожидание
    while $(isTrue "$WAITING_TIME>$(tock $startTime)")
    do
        # Пытаемся получить IP интерфейса
        ip=$(get_waydroid_interface_ip)

        if [[ ! -z "$ip" ]]; then
            # Сетевой интерфейс обнаружен
            break
        else
            # Сетевой интерфейс не обнаружен, ждём
            sleep 0.5
        fi
    done

    echo "$ip"
}

#==============================================================================
# Функции чтения файла конфигураций
#==============================================================================

## iniSectionExists
## @brief - Функция проверит существование секции ini файла
## @param $1 - Имя секции ini файла (без квадратных скобок)
## @param $2 - Путь к ini файлу
## @return Вернёт логическое значение (существует/не существует)
function iniSectionExists() {
    local SECTION="$1"
    local INI_PATH="$2"

    grep -Fxq "[$SECTION]" "$INI_PATH"
}

## iniGetValue
## @brief - Функция вернёт значение ключа в указанной секции ini файла
## @param $1 - Имя секции ini файла (без квадратных скобок)
## @param $2 - Имя параметра (ключ)
## @param $3 - Значение по умолчанию (Вернётся если ключ не удастся найти)
## @param $4 - Путь к ini файлу
## @return Вернёт значение указанного ключа или значение по умолчанию
function iniGetValue() {
    local SECTION="$1"
    local KEY="$2"
    local DEFAULT="$3"
    local INI_PATH="$4"

    local Value=""

    if [ -f "$INI_PATH" ]; then
        #   Принцип работы:
        #   Пропускаем строки до заданной секции;
        #   Читаем секцию до совпадения ключа;
        #   При совпадении выдаём значение и останавливаем перебор;
        #   При попадании в следующую секцию завершим поиск
        Value=$(awk -F '=' -v section="$SECTION" -v key="$KEY" '
            BEGIN { inSection = 0; }
            {
                if (!inSection) {
                    if ($0 == "["section"]") { inSection = 1; }
                } else {
                    if ($0 ~ /\[(.*?)\]/) {
                        exit 1
                    } else {
                        if ($1 == key) { print $2; exit 0; }
                    }
                }
            }' $INI_PATH
        )
    fi

    if [[ -z $Value ]]; then echo $DEFAULT; else echo $Value; fi
}

#==============================================================================
# Функции управления RTSP
#==============================================================================

## getRtspDestURL
## @brief - Функция сформирует URL назначения на основе параметров из файла конфигураций
## @param $1 - Секция стрима в файле конфигураций
## @param $2 - Адрес сетевого интерфейса на который будет направлен стрим
## @return Вернёт сформированный URL назначения или пустую строку
function getRtspDestURL() {
    local STREAM_SECTION="$1"
    local STREAM_DEST_ADDRESS="$2"
    local destURL=''

    local streamName=$(iniGetValue "$STREAM_SECTION" "$CONFIG_STREAM_NAME" '' "$CONFIG_PATH")
    local streamPort=$(iniGetValue "$STREAM_SECTION" "$CONFIG_STREAM_PORT" '' "$CONFIG_PATH")

    if [ ! -z "$STREAM_DEST_ADDRESS" ] && [ ! -z "$streamName" ] && [ ! -z "$streamPort" ]; then
        destURL="rtsp://$STREAM_DEST_ADDRESS:$streamPort/$streamName"
    fi

    echo "$destURL"
}

## getStreamTranscodeParams
## @brief - Функция сформирует параметры кодирования на основе параметров из файла конфигураций (если в конфигурационном файле отсутствуют параметры то они будут заданы по умолчанию)
## @param $1 - Секция стрима в файле конфигураций
## @return Вернёт сформированную строку параметров
function getStreamTranscodeParams() {
    local STREAM_SECTION="$1"

    # Эта опция позволяет указать кодек, в который должны быть перекодированы видеодорожки входного потока.
    local vcodec=$(iniGetValue "$STREAM_SECTION" "$CONFIG_STREAM_VCODEC" 'h264' "$CONFIG_PATH")
    # Эта опция позволяет установить битрейт транскодируемого видеопотока в кбит/с.
    local vb=$(iniGetValue "$STREAM_SECTION" "$CONFIG_STREAM_VB" '800' "$CONFIG_PATH")
    # Эта опция позволяет установить частоту кадров транскодируемого видео в кадрах в секунду; уменьшение частоты кадров видео может помочь уменьшить его битрейт.
    local fps=$(iniGetValue "$STREAM_SECTION" "$CONFIG_STREAM_FPS" '20' "$CONFIG_PATH")
    # Эта опция позволяет указать коэффициент, с которого видео должно масштабироваться при перекодировании. Этот параметр может быть особенно полезен для уменьшения битрейта потока.
    local scale=$(iniGetValue "$STREAM_SECTION" "$CONFIG_STREAM_SCALE" '1.0' "$CONFIG_PATH")
    # Эта опция позволяет вам указать кодек, в который аудиодорожки входного потока должны быть перекодированы.
    local acodec=$(iniGetValue "$STREAM_SECTION" "$CONFIG_STREAM_ACODEC" 'vorb' "$CONFIG_PATH")
    # Эта опция позволяет установить битрейт транскодируемого аудиопотока в кбит/с.
    local ab=$(iniGetValue "$STREAM_SECTION" "$CONFIG_STREAM_AB" '90' "$CONFIG_PATH")
    # Эта опция позволяет установить количество каналов результирующего аудиопотока. Это полезно для кодеков, не поддерживающих более 2 каналов, или для снижения битрейта аудиопотока.
    local channels=$(iniGetValue "$STREAM_SECTION" "$CONFIG_STREAM_CHANNELS" '2' "$CONFIG_PATH")
    # Эта опция позволяет установить частоту дискретизации транскодированного аудиопотока в Гц. Уменьшение частоты дискретизации — это способ снизить битрейт результирующего аудиопотока.
    local samplerate=$(iniGetValue "$STREAM_SECTION" "$CONFIG_STREAM_SAMPLERATE" '44100' "$CONFIG_PATH")

    local transcodeParamsStr="vcodec=$vcodec,vb=$vb,fps=$fps,scale=$scale,acodec=$acodec,ab=$ab,channels=$channels,samplerate=$samplerate"

    echo "$transcodeParamsStr"
}

## run_camera_rtsp
## @brief - Функция запустит стрим с устройства захвата
## @param $1 - Устройство захвата видео v4l2 (например: /dev/video0)
## @param $2 - Устройство захвата аудио (например: alsa://hw:0,0)
## @param $3 - Параметры кодирования (через запятую)
## @param $4 - URL стрима
function run_camera_rtsp() {
    local STREAM_SRC_VIDEO_DEVICE="$1"
    local STREAM_SRC_AUDIO_DEVICE="$2"
    local TRANSCODE_PARAMS="$3"
    local DEST_URL="$4"

    local sout_str="#transcode{$TRANSCODE_PARAMS}:rtp{sdp=$DEST_URL}"

    if [ -z "$STREAM_SRC_AUDIO_DEVICE" ]; then
        # Стрим без аудио
        cvlc v4l2://"$STREAM_SRC_VIDEO_DEVICE" --sout="$sout_str" &
    else
        # Стрим с аудио
        cvlc v4l2://"$STREAM_SRC_VIDEO_DEVICE" :input-slave="$STREAM_SRC_AUDIO_DEVICE" --sout="$sout_str" &
    fi

    STREAMS_PIDS[${#STREAMS_PIDS[@]}]=$!
}

## start_camera_rtsp
## @brief - Функция запустит стримы (данные будут считанны с конфигурационного файла)
function start_camera_rtsp() {
    local waydroidInterfaceIP="$(get_waydroid_interface_ip)"

    for ((streamIndex=0; streamIndex<100; ++streamIndex)); do
        local streamSection="${CONFIG_SECTION_STREAM//#/$streamIndex}"

        if $(iniSectionExists "$streamSection" "$CONFIG_PATH"); then
            local streamSrcVideoDevice=$(iniGetValue "$streamSection" "$CONFIG_STREAM_SRC_VIDEO_DEVICE" '' "$CONFIG_PATH")
            local streamSrcAudioDevice=$(iniGetValue "$streamSection" "$CONFIG_STREAM_SRC_AUDIO_DEVICE" '' "$CONFIG_PATH")
            local transcodeParams=$(getStreamTranscodeParams $streamSection)
            local rtspDestURL=$(getRtspDestURL $streamSection $waydroidInterfaceIP)

            if [ ! -z "$streamSrcVideoDevice" ] && [ ! -z "$rtspDestURL" ] && [ ! -z "$transcodeParams" ]; then
                run_camera_rtsp "$streamSrcVideoDevice" "$streamSrcAudioDevice" "$transcodeParams" "$rtspDestURL"
            fi
        fi
    done
}

## stop_camera_rtsp
## @brief - Функция остановит запущенные стримы
function stop_camera_rtsp() {
    # Убиваем параллельные процессы потоков
    for streamPID in ${STREAMS_PIDS[@]}
    do
        if [ $streamPID -le 0 ]; then
            continue
        elif kill -0 "$streamPID" &> /dev/null; then
            echo "Stopping stream PID: $streamPID"
            kill -TERM "$streamPID"
        fi
    done

    wait
}

#==============================================================================
# Функции взаимодействия с окном приложения waydroid
#==============================================================================

## get_window_metadata
## @brief - Функция вернёт json метаданные окна приложения (или пустой json массив)
## @param $1 - Имя приложения
## @return Вернёт json метаданные окна приложения (или пустой json массив)
function get_window_metadata() {
    local APP_NAME="$1"
    local json_val=$(dbus-send --session --dest='org.kde.KWin' --print-reply /WindowsRunner org.kde.krunner1.Match string:"$APP_NAME")
    echo "$json_val"
}

## is_app_have_window
## @brief - Функция проверит, существует ли у приложения окно
## @param $1 - Имя приложения
## @return Вернёт логическое значение существования окна приложения
function is_app_have_window() {
    local APP_NAME="$1"
    local regex='struct {'
    [[ "$(get_window_metadata $APP_NAME)" =~ "$regex" ]]
}

## wait_app_start
## @brief - Функция будет ждать запуска приложения waydroid (время ожидания задаётся в WAITING_TIME)
## @return Вернёт логическое значение (дождалась\не дождалась)
function wait_app_start() {
    # Флаг обнаружения окна приложения
    local detected=false
    # Время начала ожидания
    local startTime=$(tick)
    # Запускаем ожидание
    while $(isTrue "$WAITING_TIME>$(tock $startTime)")
    do
        if $(is_app_have_window "$APP_NAME"); then
            # Окно приложения обнаружено
            detected=true
            break
        else
            # Окно приложения не обнаружено, ждём
            sleep 0.5
        fi
    done

    [ $detected == true ]
}

## wait_app_end
## @brief - Функция будет ждать закрытия приложения waydroid
function wait_app_end() {
    local strList=$(iniGetValue "$CONFIG_SECTION_GENERAL" "$CONFIG_GENERAL_APPS_LIST" '' "$CONFIG_PATH")
    local appList=( $(echo "$strList" | tr ';' '\n') )
    local isEnd=false

    # Ожидаем разрешения на завершение
    while [ $isEnd == false ]
    do
        sleep 1
        # Предварительно разрешаем завершение
        isEnd=true
        # Проверяем, не запущено ли одно из приложений, для которых требуется стриминг
        for appName in "${appList[@]}"
        do
            # Если существует окно приложения
            if $(is_app_have_window "$appName"); then
                # Запрещаем завершение
                isEnd=false
                break
            fi
        done
    done
}

#==============================================================================
# Основные рабочие функции
#==============================================================================

## is_ready
## @brief - Функция проверит готовность к работе
function is_ready() {
    # Должно быть передано имя запускаемого приложения
    [ ! -z "$APP_NAME" ] &&
    # Должен существовать конфигурационный файл
    [ -f "$CONFIG_PATH" ] &&
    # Стриминг должен быть разрешён в конфигурационном файле
    [ $(iniGetValue "$CONFIG_SECTION_GENERAL" "$CONFIG_GENERAL_ACTIVE" 'false' "$CONFIG_PATH") == 'true' ] &&
    # Должен существовать сетевой интерфейс waydroid
    [ ! -z "$(wait_waydroid_interface_ip)" ]
}

## app_in_list
## @brief - Функция проверит, входит ли приложение в список конфигурационного файла
## @return Вернёт логическое значение (входит/не входит)
function app_in_list() {
    local strList=$(iniGetValue "$CONFIG_SECTION_GENERAL" "$CONFIG_GENERAL_APPS_LIST" '' "$CONFIG_PATH")
    local appList=( $(echo "$strList" | tr ';' '\n') )
    local inList=false

    for appName in "${appList[@]}"
    do
        if [ "$APP_NAME" == "$appName" ]; then
            inList=true
            break
        fi
    done

    [ $inList == true ]
}

## onFinish
## @brief - Функция, выполняемая при завершении работы
function onFinish() {
    # Остановим TRSP если запущен
    stop_camera_rtsp
    # Удалим lock файл
    rm -f "$LOCK_PATH"
}

## main
## @brief - Первичная функция
## @return Вернёт признак ошибки
function main() {
    local exitCode=0

    if ! $(is_ready); then
        exitCode=1
    else
        if $(app_in_list); then
            if ! $(wait_app_start); then
                exitCode=2
            else
                start_camera_rtsp
                wait_app_end
            fi
        fi
    fi

    return $exitCode
}

#==============================================================================
if { set -C; 2>/dev/null >"$LOCK_PATH"; }; then
    # Расставляем ловушки, автоматически завершающие работу стримов
    trap onFinish EXIT
    trap onFinish SIGTERM
    trap onFinish SIGKILL
    # Запускаем основной функционал
    main
    exit $?
else
    echo "Already running… exiting"
    exit 0
fi
#==============================================================================
