#!/usr/bin/env bash

DAV1D="tools/dav1d"
ARGON_DIR='.'
FILMGRAIN=1
CPUMASK=-1
THREADS=1
JOBS=0

usage() {
    NAME=$(basename "$0")
    {
        printf "Usage:   %s [-d dav1d] [-a argondir] [-g \$filmgrain] [-c \$cpumask] [-t threads] [-j jobs] [DIRECTORY]...\n" "$NAME"
        printf "Example: %s -d /path/to/dav1d -a /path/to/argon/ -g 0 -c avx2 profile0_core\n" "$NAME"
        printf "Used to verify that dav1d can decode the Argon AV1 test vectors correctly.\n\n"
        printf " DIRECTORY one or more dirs in the argon folder to check against\n"
        printf "             (default: everything except large scale tiles and stress files)\n"
        printf " -d dav1d  path to dav1d executable (default: tools/dav1d)\n"
        printf " -a dir    path to argon dir (default: 'tests/argon' if found; '.' otherwise)\n"
        printf " -g \$num   enable filmgrain (default: 1)\n"
        printf " -c \$mask  use restricted cpumask (default: -1)\n"
        printf " -t \$num   number of threads per dav1d (default: 1)\n"
        printf " -j \$num   number of parallel dav1d processes (default: 0)\n\n"
    } >&2
    exit 1
}

error() {
    printf "\033[1;91m%s\033[0m\n" "$*" >&2
    exit 1
}

fail() {
    printf "\033[1K\rMismatch in %s\n" "$1"
    (( failed++ ))
}

check_pids() {
    new_pids=()
    done_pids=()
    for p in "${pids[@]}"; do
        if kill -0 "$p" 2>/dev/null; then
            new_pids+=("$p")
        else
            done_pids+=("$p")
        fi
    done
    pids=("${new_pids[@]}")
}

wait_pids() {
    pid_list=("$@")
    for p in "${pid_list[@]}"; do
        if ! wait "$p"; then
            local file_varname="file$p"
            fail "${!file_varname}"
        fi
    done
}

block_pids() {
    while [ ${#pids[@]} -ge "$JOBS" ]; do
        check_pids
        if [ ${#done_pids} -eq 0 ]; then
            sleep 0.2
        else
            wait_pids "${done_pids[@]}"
        fi
    done
}

wait_all_pids() {
    wait_pids "${pids[@]}"
}

# find tests/argon
tests_dir=$(dirname "$(readlink -f "$0")")
if [ -d "$tests_dir/argon" ]; then
    ARGON_DIR="$tests_dir/argon"
fi

while getopts ":d:a:g:c:t:j:" opt; do
    case "$opt" in
        d)
            DAV1D="$OPTARG"
            ;;
        a)
            ARGON_DIR="$OPTARG"
            ;;
        g)
            FILMGRAIN="$OPTARG"
            ;;
        c)
            CPUMASK="$OPTARG"
            ;;
        t)
            THREADS="$OPTARG"
            ;;
        j)
            JOBS="$OPTARG"
            ;;
        \?)
            printf "Error! Invalid option: -%s\n" "$OPTARG" >&2
            usage
            ;;
        *)
            usage
            ;;
    esac
done
shift $((OPTIND-1))

if [ "$JOBS" -eq 0 ]; then
    if [ "$THREADS" -gt 0 ]; then
        JOBS="$((($( (nproc || sysctl -n hw.logicalcpu || getconf _NPROCESSORS_ONLN || echo 1) 2>/dev/null)+THREADS-1)/THREADS))"
    else
        JOBS=1
    fi
fi

if [ "$#" -eq 0 ]; then
    # Everything except large scale tiles and stress files.
    dirs=("$ARGON_DIR/profile0_core"       "$ARGON_DIR/profile0_core_special"
          "$ARGON_DIR/profile0_not_annexb" "$ARGON_DIR/profile0_not_annexb_special"
          "$ARGON_DIR/profile1_core"       "$ARGON_DIR/profile1_core_special"
          "$ARGON_DIR/profile1_not_annexb" "$ARGON_DIR/profile1_not_annexb_special"
          "$ARGON_DIR/profile2_core"       "$ARGON_DIR/profile2_core_special"
          "$ARGON_DIR/profile2_not_annexb" "$ARGON_DIR/profile2_not_annexb_special"
          "$ARGON_DIR/profile_switching")
else
    mapfile -t dirs < <(printf "${ARGON_DIR}/%s\n" "$@" | sort -u)
fi

ver_info="dav1d $("$DAV1D" -v 2>&1) filmgrain=$FILMGRAIN cpumask=$CPUMASK" || error "Error! Can't run $DAV1D"
files=()

for d in "${dirs[@]}"; do
    if [ -d "$d/streams" ]; then
        files+=("${d/%\//}"/streams/*.obu)
    fi
done

num_files="${#files[@]}"
if [ "$num_files" -eq 0 ]; then
    error "Error! No files found at ${dirs[*]}"
fi

failed=0
pids=()
for i in "${!files[@]}"; do
    f="${files[i]}"
    if [ "$FILMGRAIN" -eq 0 ]; then
        md5=${f/\/streams\//\/md5_no_film_grain\/}
    else
        md5=${f/\/streams\//\/md5_ref\/}
    fi
    md5=$(<"${md5/%obu/md5}") || error "Error! Can't read md5 ${md5} for file ${f}"
    md5=${md5/ */}

    printf '\033[1K\r[%3d%% %*d/%d] Verifying %s' "$(((i+1)*100/num_files))" "${#num_files}" "$((i+1))" "$num_files" "${f#"$ARGON_DIR"/}"
    cmd=("$DAV1D" -i "$f" --filmgrain "$FILMGRAIN" --verify "$md5" --cpumask "$CPUMASK" --threads "$THREADS" -q)
    if [ "$JOBS" -gt 1 ]; then
        "${cmd[@]}" 2>/dev/null &
        p=$!
        pids+=("$p")
        declare "file$p=${f#"$ARGON_DIR"/}"
        block_pids
    else
        if ! "${cmd[@]}" 2>/dev/null; then
            fail "${f#"$ARGON_DIR"/}"
        fi
    fi
done

wait_all_pids

if [ "$failed" -ne 0 ]; then
    printf "\033[1K\r%d/%d files \033[1;91mfailed\033[0m to verify" "$failed" "$num_files"
else
    printf "\033[1K\r%d files \033[1;92msuccessfully\033[0m verified" "$num_files"
fi
printf " in %dm%ds (%s)\n" "$((SECONDS/60))" "$((SECONDS%60))" "$ver_info"

exit $failed
