#!/bin/sh

: ${TEST_BOARDS_AVAILABLE:="samr21-xpro"}
: ${TEST_BOARDS_LLVM_COMPILE:="iotlab-m3 native nrf52dk mulle nucleo-f401re samr21-xpro slstk3402a"}

export RIOT_CI_BUILD=1
export STATIC_TESTS=${STATIC_TESTS:-1}
export CFLAGS_DBG=""
export DLCACHE_DIR=${DLCACHE_DIR:-~/.dlcache}

NIGHTLY=${NIGHTLY:-0}
RUN_TESTS=${RUN_TESTS:-${NIGHTLY}}

DWQ_ENV="-E BOARDS -E APPS -E NIGHTLY -E RUN_TESTS"

check_label() {
    local label="${1}"
    [ -z "${CI_PULL_LABELS}" ] && return 1
    echo "${CI_PULL_LABELS}" | grep -q "${label}"
    return $?
}

[ "$RUN_TESTS" != "1" ] && {
    check_label "CI: run tests" && RUN_TESTS=1
}

error() {
    echo "$@"
    exit 1
}

# if MURDOCK_HOOK is set, this function will execute it and pass on all it's
# parameters. should the hook script exit with negative exit code, hook() makes
# this script exit with error, too.
# hook() will be called from different locations of this script.
# currently, the only caller is "run_test", which calls "hook run_test_pre".
# More hooks will be added as needed.
hook() {
    if [ -n "${MURDOCK_HOOK}" ]; then
        echo "- executing hook $1"
        "${MURDOCK_HOOK}" "$@" || {
            error "$0: hook \"${MURDOCK_HOOK} $@\" failed!"
        }
        echo "- hook $1 finished"
    fi
}

# true if word "$1" is in list of words "$2", false otherwise
# uses grep -w, thus only alphanum and "_" count as word bounderies
# (word "def" matches "abc-def")
is_in_list() {
    [ $# -ne 2 ] && return 1

    local needle="$1"
    local haystack="$2"

    echo "$haystack" | grep -q -w "$needle"
}

_greplist() {
    if [ $# -eq 0 ]; then
        echo cat
    else
        echo -n "grep -E ($1"
        shift
        for i in $*; do
            echo -n "|$i"
        done
        echo ")"
    fi
}

# get list of all app directories
get_apps() {
    make -f makefiles/app_dirs.inc.mk info-applications \
        | $(_greplist $APPS) | sort
}

# take app dir as parameter, print all boards that are supported
# Only print for boards in $BOARDS.
get_supported_boards() {
    local appdir=$1
    local boards="$(make --no-print-directory -C$appdir info-boards-supported 2>/dev/null || echo broken)"

    if [ "$boards" = broken ]; then
        echo "makefile_broken"
        return
    fi

    for board in $boards
    do
        echo $board
    done | $(_greplist $BOARDS)
}

get_supported_toolchains() {
    local appdir=$1
    local board=$2
    local toolchains="gnu"

    if is_in_list "${board}" "${TEST_BOARDS_LLVM_COMPILE}"; then
        toolchains="$(make -s --no-print-directory -C${appdir} BOARD=${board} \
                      info-toolchains-supported 2> /dev/null | grep -o -e "llvm" -e "gnu")"
    fi
    echo "${toolchains}"
}

# given an app dir as parameter, print "$appdir $board:$toolchain" for each
# supported board and toolchain. Only print for boards in $BOARDS.
get_app_board_toolchain_pairs() {
    local appdir=$1
    for board in $(get_supported_boards $appdir)
    do
        for toolchain in $(get_supported_toolchains $appdir $board)
        do
            echo $appdir $board:$toolchain
        done
    done | $(_greplist $BOARDS)
}

# use dwqc to create full "appdir board toolchain" compile job list
get_compile_jobs() {
    get_apps | \
        dwqc ${DWQ_ENV} -s \
        "$0 get_app_board_toolchain_pairs \${1}" \
        | xargs '-d\n' -n 1 echo $0 compile
}

print_worker() {
    [ -n "$DWQ_WORKER" ] && \
        echo "-- running on worker ${DWQ_WORKER} thread ${DWQ_WORKER_THREAD}, build number $DWQ_WORKER_BUILDNUM."
}

# compile one app for one board with one toolchain. delete intermediates.
compile() {
    local appdir=$1
    local board=$(echo $2 | cut -f 1 -d':')
    local toolchain=$(echo $2 | cut -f 2 -d':')

    [ "$board" = "makefile_broken" ] && error "$0: Makefile in \"$appdir\" seems to be broken!"

    # set build directory. CI ensures only one build at a time in $(pwd).
    export BINDIR="$(pwd)/build"
    export PKGDIRBASE="${BINDIR}/pkg"

    # Pre-build cleanup
    rm -rf ${BINDIR}

    print_worker

    # sanity checks
    [ $# -ne 2 ] && error "$0: compile: invalid parameters (expected \$appdir \$board:\$toolchain)"
    [ ! -d "$appdir" ] && error "$0: compile: error: application directory \"$appdir\" doesn't exist"
    [ ! -d "boards/$board" ] && error "$0: compile: error: board directory \"boards/$board\" doesn't exist"

    # compile
    CCACHE_BASEDIR="$(pwd)" BOARD=$board TOOLCHAIN=$toolchain RIOT_CI_BUILD=1 \
        make -C${appdir} clean all -j${JOBS:-4}
    RES=$?

    # run tests
    if [ $RES -eq 0 ]; then
        if [ $RUN_TESTS -eq 1 -o "$board" = "native" ]; then
            if [ -f "${BINDIR}/.test" ]; then
                if [ "$board" = "native" ]; then
                    BOARD=$board make -C${appdir} test
                    RES=$?
                elif is_in_list "$board" "$TEST_BOARDS_AVAILABLE"; then
                    BOARD=$board TOOLCHAIN=$toolchain make -C${appdir} test-murdock
                    RES=$?
                fi
            fi
        fi
    fi

    if [ -d ${BINDIR} ]
    then
        echo "-- build directory size: $(du -sh ${BINDIR} | cut -f1)"

        # cleanup
        rm -rf ${BINDIR}
    fi

    return $RES
}

test_job() {
    local appdir=$1
    local board=$(echo $2 | cut -f 1 -d':')
    local toolchain=$(echo $2 | cut -f 2 -d':')
    local flashfile="$3"

    [ ! -f "$flashfile" ] && {
        echo "$0: _test(): flashfile \"$flashfile\" doesn't exist!"
        return 1
    }

    dwqc \
        ${DWQ_ENV} \
        ${DWQ_JOBID:+--subjob} \
        --file $flashfile:$appdir/bin/${board}/$(basename $flashfile) \
        --queue ${TEST_QUEUE:-$board} \
        --maxfail 1 \
        "./.murdock run_test $appdir $board:$toolchain"
}

run_test() {
    local appdir=$1
    local board=$(echo $2 | cut -f 1 -d':')
    local toolchain=$(echo $2 | cut -f 2 -d':')
    print_worker
    echo "-- executing tests for $appdir on $board (compiled with $toolchain toolchain):"
    hook run_test_pre
    BOARD=$board TOOLCHAIN=${toolchain} make -C$appdir flash-only test
}

# execute static tests
static_tests() {
    local repo=${CI_BASE_REPO:-https://github.com/RIOT-OS/RIOT}
    local branch=${CI_BASE_BRANCH:-master}

    print_worker

    OUT="$(git remote add upstream $repo 2>&1 && git fetch upstream ${branch}:${branch} 2>&1)"
    RES=$?
    if [ $RES -ne 0 ]; then
        echo "$OUT"
        exit 1
    fi

    BUILDTEST_MCU_GROUP=static-tests ./dist/tools/ci/build_and_test.sh
}

get_jobs() {
    [ "$STATIC_TESTS" = "1" ] && \
        echo "$0 static_tests###{ \"jobdir\" : \"exclusive\" }"
    get_compile_jobs
}

$*