#!/usr/bin/env bash

if [ "${BASH_SOURCE[0]}" -ef "$0" ]
then
    echo "This script should not be directly executed!"
    exit 1
fi

# A declaration of parameters and their default values
declare -A ARGS=(
    ["profile"]=zutty
    ["step"]=no          # N.B.: --step=new will only stop at new snaps!
    ["update-sig"]=no
    ["ci-mode"]=no
    ["debug"]=no
)

# Include argument handler
source "testargs.sh"

PROFILE=${ARGS["profile"]}
STEP_MODE=${ARGS["step"]}
UPDATE_SIGNATURE=${ARGS["update-sig"]}
if [ ${PROFILE} == "zutty" ] ; then
    VERIFY_SNAPS=yes
else
    VERIFY_SNAPS=optional
    IMAGE_DIFF_TO=zutty
fi
if [ ${ARGS["ci-mode"]} == "yes" ] ; then
    STEP_MODE=no
    UPDATE_SIGNATURE=no
    EXIT_ON_FAILURE=yes
else
    EXIT_ON_FAILURE=no
fi
if [ ${ARGS["debug"]} == "yes" ] ; then
    DEBUG=yes
else
    DEBUG=no
fi

PLATFORM=$(uname -s)

EXIT_CODE=0

export TEST_NAME=$(basename -s .sh "$0")

export RED="\\e[1;31m"
export GREEN="\\e[1;32m"
export YELLOW="\\e[1;33m"
export WHITE="\\e[1;37m"
export DFLT="\\e[0;39m"
export ERASE_PROMPT="\\eM\\e[J"

function print_error {
    printf "${RED}ERROR: $@${DFLT}\n"
}

function CHECK_DEPS {
    TOOLS="$@"; shift
    MISSING=0
    for t in ${TOOLS} ; do
        which $t >/dev/null
        if [ $? -gt 0 ] ; then
            print_error "Missing dependency: $t"
            MISSING=1
        fi
    done
    if [ $MISSING -gt 0 ] ; then
        exit 1
    fi
}

function CHECK_FILES {
    FILES="$@"; shift
    MISSING=0
    for f in ${FILES} ; do
        if [ ! -f "$f" ] ; then
            print_error "Missing file: $f"
            MISSING=1
        fi
    done
    if [ $MISSING -gt 0 ] ; then
        exit 1
    fi
}

function CHECK_EXE {
    FILES="$@"; shift
    MISSING=0
    for f in ${FILES} ; do
        if [ ! -x "$f" ] ; then
            print_error "Missing executable: $f"
            MISSING=1
        fi
    done
    if [ $MISSING -gt 0 ] ; then
        exit 1
    fi
}

if [ ! -f "profiles/${PROFILE}.sh" ] ; then
    print_error "Could not load profile: ${PROFILE}"
    echo -n "Available profiles:"
    for p in $(ls profiles/*.sh) ; do
        echo -n " $(basename -s .sh $p)"
    done
    echo
    exit 1
fi
source "profiles/${PROFILE}.sh"
echo "Running test: ${TEST_NAME} with profile: ${PROFILE}"
[ -n "${UUT_VERSION}" ] && echo "UUT version: ${UUT_VERSION}"

export TEST_LOG="$(pwd)/output/${PROFILE}/${TEST_NAME}.log"
export UUT_LOG="$(pwd)/output/${PROFILE}/${TEST_NAME}.uut.log"
export UUT_SNAP="$(pwd)/output/${PROFILE}"

export TEST_COUNT=0
export NRES_COUNT=0
export FAIL_COUNT=0

CHECK_DEPS convert compare identify mogrify xrdb xvkbd wmctrl
mkdir -p $(dirname ${UUT_LOG})
mkdir -p ${UUT_SNAP}

CON_PID=$(ps -p $$ -o ppid=)
CON_PID=$(ps -p ${CON_PID} -o ppid=)
CON_WID=$(wmctrl -lp | grep ${CON_PID} | awk '{print $1}')
if [ -z "${CON_WID}" ] ; then
    echo "Console pid: ${CON_PID}"
    printf "        ${YELLOW}wid not found, prompts won't focus console${DFLT}\n"
else
    echo "Console pid: ${CON_PID} wid: ${CON_WID}"
fi

# Add path to installed dependencies, if any
export PATH="$(pwd)/deps/bin":$PATH

# Remove any permanent configuration that could change how Zutty looks/behaves:
[ ${PROFILE} == "zutty" ] && [ -f ${HOME}/.Xresources ] && xrdb -remove

# execute UUT and store its pid
${UUT_EXE} ${UUT_ARGS} >${UUT_LOG} 2>&1 &
PID=$!
echo "UUT pid: ${PID}"
echo "    log file: ${UUT_LOG}"
echo "    snap dir: ${UUT_SNAP}"

if [ ! -z ${UUT_PID_FROM_NAME} ] ; then
    sleep 0.5
    PID=$(pidof ${UUT_PID_FROM_NAME})
    echo "UUT pid: ${PID}"
fi

# wait until window opens and grab its X window id
WID=''
while true ; do
    if [[ -z "$(ps -p ${PID} -o pid=)" ]] ; then
        echo "UUT process ${PID} gone, aborting."
        exit 1
    fi
    WID=$(wmctrl -lp | grep ${PID} | awk '{print $1}')
    [[ -z "${WID}" ]] || break
done
echo "UUT X window id: ${WID}"

# focus the window
wmctrl -ia "${WID}"
# set it to float (this is i3 specific):
which i3-msg >/dev/null && i3-msg floating enable >/dev/null;

function COUNT_PASS {
    TEST_COUNT=$((TEST_COUNT + 1))
    echo -n "$(test_counters)"
}

function COUNT_FAIL {
    TEST_COUNT=$((TEST_COUNT + 1))
    FAIL_COUNT=$((FAIL_COUNT + 1))
    echo -n "$(test_counters)"
}

function COUNT_NRES {
    TEST_COUNT=$((TEST_COUNT + 1))
    NRES_COUNT=$((NRES_COUNT + 1))
    echo -n "$(test_counters)"
}

function test_counters {
    echo "(${TEST_COUNT}/${NRES_COUNT}/${FAIL_COUNT}) "
}

function FINISH_TEST {
    echo "Total tests: ${TEST_COUNT}  No result: ${NRES_COUNT}  Failed: ${FAIL_COUNT}"
    # display process statistics
    if [ "${PLATFORM}" == "Linux" ] ; then
        ps -p ${PID} -o pid,vsz,rss,trs,sz,time,maj_flt,min_flt,args \
            | tee -a ${TEST_LOG}
        pmap -x ${PID} >> ${TEST_LOG}
        echo "Process memory map appended to ${TEST_LOG}"
    else
        ps -p ${PID} -o pid,vsz,rss,time,args | tee -a ${TEST_LOG}
    fi

    trap - EXIT
    kill "${PID}"
    [ ${PROFILE} == "zutty" ] && [ -f ${HOME}/.Xresources ] && \
        xrdb ${HOME}/.Xresources
    exit ${EXIT_CODE}
}

trap FINISH_TEST EXIT

trap "exit 1" SIGINT

function check_uut {
    if [ $(wmctrl -lp | grep ${WID} | wc -l) -eq 0 ] ; then
        # If IN was interrupted, Return key might have gotten stuck...
        LANG=C xvkbd -window root -no-jump-pointer \
            -text "\{-Return}\D3\{-Return}\D3" 2>/dev/null
        echo "UUT window gone, exiting"
        echo "---------- LOG TAIL ----------"
        tail ${UUT_LOG}

        trap - EXIT
        [ ${PROFILE} == "zutty" ] && [ -f ${HOME}/.Xresources ] && \
            xrdb ${HOME}/.Xresources
        exit 1
    fi
}

function IN {
    local in="$1"; shift
    str=$(echo "$in" | sed 's|\\r|\\D3\\{+Return}\\D3\\{-Return}|');
    check_uut
    LANG=C xvkbd -window ${WID} -no-jump-pointer -delay 50 \
        -text "${str}\\D3" 2>&1 | \
        grep -v "Mode_switch not available as a modifier" | \
        grep -v "AltGr may not work correctly"
}

function FOCUS_CONSOLE {
    if [ ! -z "${CON_WID}" ] ; then
        # focus the console window
        # for some reason, a simple wmctrl -ia does not work;
        # it selects the window but keyboard focus is not active.
        LANG=C xvkbd -window ${CON_WID} -no-back-pointer \
              -text "\{+Control_L}\D1\{-Control_L}\D1" >/dev/null 2>&1
    fi
}

function do_update_sig {
    local name="$1"; shift
    local newsig="$1"; shift
    sed -i -e "s/SNAP ${name} .*$/SNAP ${name} ${newsig}/" "$(basename $0)"
    echo "${name}: signature updated to ${newsig}"
}

function update_sig {
    case ${UPDATE_SIGNATURE} in
        all)
            do_update_sig "$@"
            ;;
        yes)
            FOCUS_CONSOLE
            read -p "Update signature: [y]es / [N]o / [a]ll ? " ans
            printf "${ERASE_PROMPT}"
            case $ans in
                [aA]* ) export UPDATE_SIGNATURE=all; do_update_sig "$@" ;;
                [yY]* ) do_update_sig "$@" ;;
                * ) ;;
            esac
            ;;
        *)
            ;;
    esac
}

function step_prompt {
    FOCUS_CONSOLE
    read -p "[S]tep / [N]ew only / [C]ontinue / [Q]uit (s/n/c/q) ? " ans
    printf "${ERASE_PROMPT}"
    case $ans in
        [sS]* ) STEP_MODE=yes ;;
        [nN]* ) STEP_MODE=new ;;
        [cC]* ) STEP_MODE=no ;;
        [qQ]* ) exit ;;
        * ) ;;
    esac
}

function SNAP {
    local name="$1"; shift
    local refsig="$1"; shift
    local imgfile=${UUT_SNAP}/${name}.png
    check_uut
    xwd -silent -nobdrs -id ${WID} | convert xwd:- png:- > ${imgfile}
    mogrify -strip -taint -compress Lossless ${imgfile}

    case ${VERIFY_SNAPS} in
        yes)
            sig=$(identify -verbose ${imgfile} | grep signature | \
                      awk '{print $2}' | cut -c -32)
            if [ -z "${refsig}" ] ; then
                COUNT_NRES
                printf "${name}: ${YELLOW}NEW${DFLT} ${sig}\n"
                if [ ${STEP_MODE} == "new" ] ; then
                    step_prompt
                fi
            elif [ "${sig}" == "${refsig}" ] ; then
                COUNT_PASS
                printf "${name}: ${GREEN}OK${DFLT}\n"
            else
                COUNT_FAIL
                printf "${name}: ${RED}FAIL${DFLT} sig ${sig} ref ${refsig}\n"
                update_sig ${name} ${sig}
                EXIT_CODE=1
                if [ ${EXIT_ON_FAILURE} == "yes" ] ; then
                    exit
                fi
            fi
            ;;
        optional)
            sig=$(identify -verbose ${imgfile} | grep signature | \
                      awk '{print $2}' | cut -c -32)
            imgerr=${UUT_SNAP}/${name}.error.png
            if [ "${sig}" == "${refsig}" ] ; then
                rm -f ${imgerr}
                COUNT_PASS
                printf "${name}: ${GREEN}MATCH${DFLT}\n"
            else
                COUNT_NRES
                if [ ! -z ${IMAGE_DIFF_TO} ] ; then
                    imgref="$(pwd)/output/${IMAGE_DIFF_TO}/${name}.png"
                    rm -f ${imgerr}
                    AE=$(compare ${imgref} ${imgfile} -metric AE ${imgerr} 2>&1)
                    if [ $? -eq 2 ] ; then
                        printf "${name}: ${WHITE}DONE${DFLT} (different dimensions)\n"
                    else
                        printf "${name}: ${WHITE}DONE${DFLT} (AE=${AE})\n"
                    fi
                else
                    printf "${name}: ${WHITE}DONE${DFLT}\n"
                fi
            fi
            ;;
        *)
            COUNT_NRES
            printf "${name}: ${WHITE}DONE${DFLT}\n"
            ;;
    esac

    if [ ${STEP_MODE} == "yes" ] ; then
        step_prompt
    fi
}

function WAIT_FOR_DOT_COMPLETE {
    while true ; do
        [[ -f .complete ]] && break
        sleep 0.1
    done
    rm -f .complete
}
