#!/bin/bash

CURDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VERSION=$(git describe HEAD)
VERSION=${VERSION}_$(git describe HEAD | xargs git show -s --format=format:"%cd" --date=short)

# Default Android build to arm.
ANDROID_ARCH=${ANDROID_ARCH:-arm}
if [ -z "${ANDROID_FULL_ARCH+x}" ]; then
    if [ "$ANDROID_ARCH" = arm ]; then
        ANDROID_FULL_ARCH_APK="${ANDROID_FULL_ARCH_APK:-arm-linux-androideabi}"
    elif [ "$ANDROID_ARCH" = x86 ]; then
        ANDROID_FULL_ARCH_APK="${ANDROID_FULL_ARCH_APK:-i686-linux-android}"
    else
        ANDROID_FULL_ARCH_APK="${ANDROID_ARCH}"
    fi
fi
# Default to Android 4.0+; required for NDK 15 but with a custom NDK the strict minimum is 9.
NDKABI=${NDKABI:-14}
export NDKABI

function assert_ret_zero() {
    if [ "$1" -ne 0 ]; then
        if [ ! -z "$2" ]; then
            echo "$2"
        fi
        exit 1
    fi
}

function check_submodules() {
    if git submodule status | grep -qE '^\-'; then
        kodev-fetch-thirdparty
    fi
}

# Takes two arguments:
# $1 arguments to pass to pgrep
# $2 process name to pgrep for
function gnuplot_wrapper() {
    # inspired by https://gist.github.com/nicolasazrak/32d68ed6c845a095f75f037ecc2f0436
    trap capture_ctrl_c INT
    TEMP_DIR=$(mktemp --directory /tmp/tmp.koreaderXXX)
    LOG="$TEMP_DIR/memory.log"
    SCRIPT_PNG="$TEMP_DIR/script_png.p"
    SCRIPT_SHOW="$TEMP_DIR/script_show.p"
    IMAGE_PNG="$TEMP_DIR/graph.png"

    echo "Memory plot output to $TEMP_DIR"

    cat >"$SCRIPT_PNG" <<EOL
set term pngcairo size 1600,1200
set output "$IMAGE_PNG"
set ylabel "RSS"
set y2label "VSZ"
set ytics nomirror
set y2tics nomirror in
set yrange [0:*]
set y2range [0:*]
plot "$LOG" using 3 with lines axes x1y1 title "RSS", "$LOG" using 2 with lines axes x1y2 title "VSZ"
EOL

    cat >"$SCRIPT_SHOW" <<EOL
set term wxt noraise
set ylabel "RSS"
set y2label "VSZ"
set ytics nomirror
set y2tics nomirror in
set yrange [0:*]
set y2range [0:*]
plot "$LOG" using 3 with lines axes x1y1 title "RSS", "$LOG" using 2 with lines axes x1y2 title "VSZ"
pause 1
reread
EOL

    function capture_ctrl_c() {
        kill "$LOOP_PID"
        kill "$GNUPLOT_PID"
        gnuplot "$SCRIPT_PNG"
        exit
    }

    # initialize at 0 so gnuplot has something to show
    echo "0 0 0" >"${LOG}"
    while true; do
        # shellcheck disable=SC2086
        ps -p "$(pgrep --delimiter ' ' $1 "$2")" -o pid=,vsz=,rss= >>"${LOG}"
        sleep 1
    done &
    LOOP_PID=$!
    gnuplot "$SCRIPT_SHOW" &
    GNUPLOT_PID=$!
}

function setup_env() {
    SETUP_ENV_GREP_COMMAND="grep -v debug"
    if [ -n "${KODEBUG+x}" ]; then
        SETUP_ENV_GREP_COMMAND="grep debug"
        # for android adb install
        KODEBUG_SUFFIX=-debug
    fi
    files=$(find . -maxdepth 1 -name 'koreader-emulator-*' | ${SETUP_ENV_GREP_COMMAND})/koreader
    assert_ret_zero $? "Emulator not found. Please build it first."
    export EMU_DIR=${files[0]}
}

function kodev-fetch-thirdparty() {
    make fetchthirdparty
}

SUPPORTED_TARGETS="
    kindle          For kindle with touch support
    kindle5         For kindle models >= kindle4 < paper white 2
    kindlepw2       For kindle models >= paper white 2
    kindle-legacy   For kindle2/3/DXG
    kobo
    android
    pocketbook
    ubuntu-touch
    appimage
    emu             (*default) If no TARGET is given, assume emulator
    win32
"

function kodev-build() {
    BUILD_HELP_MSG="
usage: build <OPTIONS> <TARGET>

OPTIONS:

    -v, --verbose   Build in verbose mode.
    --debug         include debugging symbols (default for emulator)
    --no-debug      no debugging symbols (default for target devices)

TARGET:
${SUPPORTED_TARGETS}"

    while [[ $1 == '-'* ]]; do
        PARAM=$(echo "$1" | awk -F= '{print $1}')
        VALUE=$(echo "$1" | awk -F= '{print $2}')
        case $PARAM in
            -v | --verbose)
                export VERBOSE=1
                ;;
            -h | --help)
                echo "${BUILD_HELP_MSG}"
                exit 0
                ;;
            --no-debug)
                export KODEBUG=
                KODEBUG_NO_DEFAULT=1
                ;;
            --debug)
                export KODEBUG=1
                KODEBUG_NO_DEFAULT=1
                ;;
            *)
                echo "ERROR: unknown option \"$PARAM\""
                echo "${BUILD_HELP_MSG}"
                exit 1
                ;;
        esac
        shift 1
    done

    check_submodules
    case $1 in
        kindle)
            make TARGET=kindle
            assert_ret_zero $?
            ;;
        kindle5)
            make TARGET=kindle5
            assert_ret_zero $?
            ;;
        kindlepw2)
            make TARGET=kindlepw2
            assert_ret_zero $?
            ;;
        kobo)
            make TARGET=kobo
            assert_ret_zero $?
            ;;
        kindle-legacy)
            make TARGET=kindle-legacy
            assert_ret_zero $?
            ;;
        android)
            if [ -z "${NDK+x}" ]; then
                if [ -n "${ANDROID_NDK+x}" ]; then
                    # some distributions use `ANDROID_NDK` instead, fall back to it
                    export NDK="${ANDROID_NDK}"
                else
                    export NDK="${CURDIR}/base/toolchain/android-ndk-r15c"
                fi
            fi
            [ -e "${CURDIR}/base/toolchain/android-toolchain-${ANDROID_ARCH}/bin/" ] || {
                { [ -e "${NDK}" ] || make -C "${CURDIR}/base/toolchain" android-ndk; }
                assert_ret_zero $?
                make android-toolchain
                assert_ret_zero $?
            }
            echo "Using NDK: ${NDK}..."
            make TARGET=android
            assert_ret_zero $?
            ;;
        pocketbook)
            if [ ! -d "${CURDIR}/base/toolchain/pocketbook-toolchain" ]; then
                make pocketbook-toolchain
                assert_ret_zero $?
            fi
            make TARGET=pocketbook
            assert_ret_zero $?
            ;;
        ubuntu-touch)
            make TARGET=ubuntu-touch
            assert_ret_zero $?
            ;;
        appimage)
            make TARGET=appimage
            assert_ret_zero $?
            ;;
        win32)
            make TARGET=win32
            assert_ret_zero $?
            ;;
        *)
            if [ -z "${KODEBUG_NO_DEFAULT+x}" ]; then # no explicit --debug / --no-debug
                # builds a debug build by default, like kodev-run
                export KODEBUG=1
            fi
            make
            assert_ret_zero $? "Failed to build emulator! Try run with -v for more information."
            setup_env
            ;;
    esac
}

function kodev-clean() {
    CLEAN_HELP_MSG="
usage: clean <TARGET>

TARGET:
${SUPPORTED_TARGETS}"

    case $1 in
        -h | --help)
            echo "${CLEAN_HELP_MSG}"
            exit 0
            ;;
        kindle)
            make TARGET=kindle clean
            ;;
        kindle5)
            make TARGET=kindle5 clean
            ;;
        kindlepw2)
            make TARGET=kindlepw2 clean
            ;;
        kobo)
            make TARGET=kobo clean
            ;;
        kindle-legacy)
            make TARGET=kindle-legacy clean
            ;;
        android)
            make TARGET=android clean
            rm -f ./*.apk
            ;;
        pocketbook)
            make TARGET=pocketbook clean
            ;;
        ubuntu-touch)
            make TARGET=ubuntu-touch clean
            ;;
        appimage)
            make TARGET=appimage clean
            ;;
        win32)
            make TARGET=win32 clean
            ;;
        *)
            make clean
            ;;
    esac
}

function kodev-release() {
    # SUPPORTED_RELEASE_TARGETS=$(echo ${SUPPORTED_TARGETS} | sed 's/win32//')
    SUPPORTED_RELEASE_TARGETS="${SUPPORTED_TARGETS/emu*/""}"
    RELEASE_HELP_MSG="
usage: release <OPTIONS> <TARGET>

OPTIONS:

    --debug               debug-enabled release (for remote gdb)
    --ignore-translation  do not fetch translation for release

TARGET:
${SUPPORTED_RELEASE_TARGETS}"
    [ $# -lt 1 ] && {
        echo "${RELEASE_HELP_MSG}"
        exit 1
    }

    ignore_translation=0

    while [[ $1 == '-'* ]]; do
        PARAM=$(echo "$1" | awk -F= '{print $1}')
        VALUE=$(echo "$1" | awk -F= '{print $2}')
        case $PARAM in
            --debug)
                export KODEBUG=1
                ;;
            --ignore-translation)
                ignore_translation=1
                ;;
            -v | --verbose)
                export VERBOSE=1
                ;;
            -h | --help)
                echo "${RELEASE_HELP_MSG}"
                exit 0
                ;;
            *)
                echo "ERROR: unknown option \"$PARAM\""
                echo "${RELEASE_HELP_MSG}"
                exit 1
                ;;
        esac
        shift 1
    done

    if [ "${ignore_translation}" -eq 0 ]; then
        if command -v tx>/dev/null; then
            make po || {
                echo "ERROR: failed to fetch translation."
                echo "Tip: Use --ignore-translation OPTION if you want to build a release without translation."
                exit 1
            }
        else
            echo "WARN: Transifex client not found, no translation pulled."
        fi
    fi

    case $1 in
        kindle)
            kodev-build kindle
            make TARGET=kindle update
            ;;
        kindle5)
            kodev-build kindle5
            make TARGET=kindle5 update
            ;;
        kindlepw2)
            kodev-build kindlepw2
            make TARGET=kindlepw2 update
            ;;
        kobo)
            kodev-build kobo
            make TARGET=kobo update
            ;;
        kindle-legacy)
            kodev-build kindle-legacy
            make TARGET=kindle-legacy update
            ;;
        android)
            kodev-build android
            export PATH=$PATH:${CURDIR}/base/toolchain/android-sdk-linux/tools
            command -v android &>/dev/null || {
                make -C "${CURDIR}/base/toolchain" android-sdk
            }
            ANDROID_HOME=$(dirname "$(dirname "$(command -v android)")")
            export ANDROID_HOME
            export PATH=$PATH:${NDK}
            make TARGET=android update
            ;;
        pocketbook)
            kodev-build pocketbook
            make TARGET=pocketbook update
            ;;
        ubuntu-touch)
            kodev-build ubuntu-touch
            make TARGET=ubuntu-touch update
            ;;
        appimage)
            kodev-build appimage
            make TARGET=appimage update
            ;;
        *)
            echo "Unsupported target for release: $1."
            echo "${RELEASE_HELP_MSG}"
            exit 1
            ;;
    esac
}

function kodev-wbuilder() {
    kodev-build
    echo "[*] Running wbuilder.lua..."
    pushd "${EMU_DIR}" && {
        EMULATE_READER_W=540 EMULATE_READER_H=720 ./luajit ./tools/wbuilder.lua
    } && popd || exit
}

function kodev-run() {
    RUN_HELP_MSG="
usage: run <OPTIONS> <TARGET>

OPTIONS:

    -h=X, --screen-height=X  set height of the emulator screen (default: 720)
    -w=X, --screen-width=X   set width of the emulator screen (default: 540)
    -d=X, --screen-dpi=X     set DPI of the emulator screen (default: 160)
    --no-build               run reader without rebuilding
    --no-debug               no debugging symbols (requires rebuilding)
    --disable-touch          use this if you want to simulate keyboard only devices
    -s=FOO --simulate=FOO    simulate dimension and other specs for a given device model
                             supported model: kobo-aura-one, kindle3, hidpi
    --gdb=X                  run with debugger (default: nemiver)
    --graph                  graph memory use (requires gnuplot)
    --valgrind=X             run with valgrind (default: valgrind --trace-children=yes)

TARGET:

    android         install and run KOReader on an Android device connected through ADB
    "
    screen_width=540
    screen_height=720
    export KODEBUG=1
    while [[ $1 == '-'* ]]; do
        PARAM=$(echo "$1" | awk -F= '{print $1}')
        VALUE=$(echo "$1" | awk -F= '{print $2}')
        case $PARAM in
            --disable-touch)
                export DISABLE_TOUCH=1
                ;;
            --no-build)
                no_build=true
                ;;
            --no-debug)
                export KODEBUG=
                ;;
            --gdb)
                if [ ! -z "${VALUE}" ]; then
                    gdb=${VALUE}
                else
                    # Try to use friendly defaults for gdb
                    if command -v nemiver >/dev/null; then
                        # Nemiver is a nice GUI
                        gdb=nemiver
                    elif command -v ddd >/dev/null; then
                        # DDD is a slightly less nice GUI
                        gdb=ddd
                    elif command -v cgdb >/dev/null; then
                        # cgdb is a nice curses-based gdb front
                        gdb=cgdb
                    elif command -v gdb >/dev/null; then
                        # gdb -tui is a slightly less nice terminal user interface
                        gdb="gdb -tui"
                    else
                        echo "Couldn't find gdb."
                        exit 1
                    fi
                fi
                ;;
            --graph)
                graph_memory=true
                ;;
            --valgrind)
                if [ ! -z "${VALUE}" ]; then
                    valgrind=${VALUE}
                else
                    # Try to use sensible defaults for valgrind
                    if command -v valgrind >/dev/null; then
                        valgrind="valgrind --trace-children=yes"
                    else
                        echo "Couldn't find valgrind."
                        exit 1
                    fi
                fi
                ;;
            -w | --screen-width)
                screen_width=${VALUE}
                ;;
            -h | --screen-height)
                # simple numeric check due to overlap between -h for help and height
                if [ ! -z "${VALUE##*[!0-9]*}" ]; then
                    screen_height=${VALUE}
                else
                    echo "${RUN_HELP_MSG}"
                    exit 1
                fi
                ;;
            -d | --screen-dpi)
                screen_dpi=${VALUE}
                ;;
            -s | --simulate)
                device_model=${VALUE}
                case ${device_model} in
                    kindle3)
                        screen_width=600
                        screen_height=800
                        ;;
                    kobo-aura-one)
                        screen_width=1404
                        screen_height=1872
                        screen_dpi=300
                        ;;
                    hidpi)
                        screen_width=1500
                        screen_height=2000
                        screen_dpi=600
                        ;;
                    *)
                        echo "ERROR: spec unknown for ${device_model}."
                        ;;
                esac
                ;;
            --help)
                echo "${RUN_HELP_MSG}"
                exit 0
                ;;
            *)
                echo "ERROR: unknown option \"$PARAM\""
                echo "${RUN_HELP_MSG}"
                exit 1
                ;;
        esac
        shift
    done

    case $1 in
        android)
            command -v adb >/dev/null && {
                if [ ! ${no_build} ]; then
                    echo "[*] Building KOReader for Android…"
                    kodev-release --ignore-translation android
                    assert_ret_zero $?
                fi
                setup_env
                # clear logcat to get rid of useless cruft
                adb logcat -c
                # uninstall existing package to make sure *everything* is gone from memory
                # no assert_ret_zero; uninstall is allowed to fail if there's nothing to uninstall
                adb uninstall "org.koreader.launcher"
                adb install "koreader-android-${ANDROID_FULL_ARCH_APK}${KODEBUG_SUFFIX}-${VERSION}.apk"
                assert_ret_zero $?
                # there's no adb run so we do this…
                adb shell monkey -p org.koreader.launcher -c android.intent.category.LAUNCHER 1
                assert_ret_zero $?
                adb logcat
            } || echo "Failed to find adb in PATH to interact with Android device."
            ;;
        *)
            if [ ! ${no_build} ]; then
                echo "[*] Building KOReader…"
                kodev-build
            else
                setup_env
            fi

            if [ ! -d "${EMU_DIR}" ]; then
                echo "Failed to find emulator directory! Please try build command first."
                exit 1
            fi

            if [ ${graph_memory} ]; then
                gnuplot_wrapper "--parent $$" "reader.lua"
            fi

            # run with catchsegv by default when it is available
            # see https://github.com/koreader/koreader/issues/2878#issuecomment-326796777
            if command -v catchsegv >/dev/null; then
                CATCHSEGV=$(command -v catchsegv)
            fi

            KOREADER_COMMAND="${CATCHSEGV} ./reader.lua -d"

            if [ ! -z "${gdb}" ]; then
                KOREADER_COMMAND="${gdb} ./luajit reader.lua -d"
            fi

            if [ ! -z "${valgrind}" ]; then
                KOREADER_COMMAND="${valgrind} ./luajit reader.lua -d"
            fi

            echo "[*] Running KOReader with arguments: $*..."
            pushd "${EMU_DIR}" && {
                if [ $# -lt 1 ]; then
                    args=${CURDIR}/test
                else
                    args="$*"
                    [[ $args != /* ]] && args="${CURDIR}/${args}"
                fi
                KOREADER_COMMAND="$KOREADER_COMMAND ${args}"

                RETURN_VALUE=85
                while [ $RETURN_VALUE -eq 85 ]; do
                    EMULATE_READER_W=${screen_width} EMULATE_READER_H=${screen_height} EMULATE_READER_DPI=${screen_dpi} \
                        ${KOREADER_COMMAND}
                    RETURN_VALUE=$?
                done
            } && popd || exit

            if [ ${graph_memory} ]; then
                capture_ctrl_c
            fi
            ;;
    esac
}

function kodev-test() {
    TEST_HELP_MSG="
usage: test <OPTIONS> [front|base] <TEST_NAME>

    TEST_NAME is optional. If no TEST_NAME is given, all tests will be run.

OPTIONS:

    --tags=TAGS     only run tests with given tags
    "
    while [[ $1 == '-'* ]]; do
        PARAM=$(echo "$1" | awk -F= '{print $1}')
        VALUE=$(echo "$1" | awk -F= '{print $2}')
        case $PARAM in
            --graph)
                graph_memory=true
                ;;
            --tags)
                opts="--tags=${VALUE}"
                ;;
            -h | --help)
                echo "${TEST_HELP_MSG}"
                exit 0
                ;;
            *)
                echo "ERROR: unknown option \"$PARAM\""
                echo "${TEST_HELP_MSG}"
                exit 1
                ;;
        esac
        shift
    done

    [ $# -lt 1 ] && {
        echo "${TEST_HELP_MSG}"
        exit 1
    }
    [[ $1 != "front" && $1 != "base" ]] && {
        echo "Invalid test suite: $1!"
        echo "${TEST_HELP_MSG}"
        exit 1
    }

    check_submodules && make
    setup_env

    make "${EMU_DIR}/.busted"
    pushd "${EMU_DIR}" && {
        test_path="./spec/$1/unit"
        rm -rf "${test_path}"/data/*.sdr

        if [ ! -z "$2" ]; then
            test_path="${test_path}/$2"
        fi

        echo "Running tests in" "${test_path}"
        busted --lua="./luajit" "${opts}" \
            --no-auto-insulate \
            --lazy \
            --output=gtest \
            --exclude-tags=notest "${test_path}"
    } && popd || exit
}

function kodev-cov() {
    COV_HELP_MSG="
usage: cov <OPTIONS>

OPTIONS:

    --show-previous     show coverage stats from previous run
    --full              show full coverage report (down to each line)
    "
    show_full=0
    show_previous=0
    while [[ $1 == '-'* ]]; do
        PARAM=$(echo "$1" | awk -F= '{print $1}')
        VALUE=$(echo "$1" | awk -F= '{print $2}')
        case $PARAM in
            --full)
                show_full=1
                ;;
            --show-previous)
                show_previous=1
                ;;
            -h | --help)
                echo "${COV_HELP_MSG}"
                exit 0
                ;;
            *)
                echo "ERROR: unknown option \"$PARAM\""
                echo "${COV_HELP_MSG}"
                exit 1
                ;;
        esac
        shift
    done

    check_submodules && make
    setup_env
    make "${EMU_DIR}/.busted"
    pushd "${EMU_DIR}" && {
        target=front
        test_path="./spec/${target}/unit"
        if [ ${show_previous} -eq 0 ]; then
            echo "Running tests in" ${test_path}
            busted --lua="./luajit" \
                --sort-files \
                --no-auto-insulate \
                --lazy \
                -o "./spec/${target}/unit/verbose_print" \
                --coverage \
                --exclude-tags=nocov "${test_path}" || {
                    echo "Failed to run tests!" && exit 1
                }
        fi
        if [ ${show_full} -eq 1 ]; then
            cat luacov.report.out
        else
            LUACOV_REPORT_SUMMARY=$(grep -nm1 -e '^Summary$' luacov.report.out | cut -d: -f1)
            tail -n \
                +$((LUACOV_REPORT_SUMMARY - 1)) \
                luacov.report.out
        fi
    } && popd || exit
}

function kodev-log() {
    LOG_HELP_MSG="
usage: log <TARGET>

TARGET:

    android
"
    [ $# -lt 1 ] && {
        echo "${LOG_HELP_MSG}"
        exit 1
    }

    case $1 in
        -h | --help)
            echo "${LOG_HELP_MSG}"
            exit 0
            ;;
        android)
            adb logcat 'luajit-launcher:D KOReader:D *:S'
            ;;
        *)
            echo "Unsupported target: $1."
            echo "${LOG_HELP_MSG}"
            exit 1
            ;;
    esac
}

HELP_MSG="
usage: $0 COMMAND <ARGS>

Supported commands:

    activate            Bootstrap shell environment for kodev
    build               Build KOReader
    clean               Clean KOReader build
    fetch-thirdparty    Fetch thirdparty dependencies for build
    log                 Tail log stream for a running KOReader app
    release             Build KOReader release package
    run                 Run KOReader
    test                Run tests
    wbuilder            Run wbuilder.lua script (useful for building new UI widget)
"
[ $# -lt 1 ] && {
    echo "Missing command."
    echo "${HELP_MSG}"
    exit 1
}

case $1 in
    activate)
        echo "adding ${CURDIR} to \$PATH..."
        export PATH="${PATH}:${CURDIR}"
        eval "$(luarocks path --bin)"
        exec "${SHELL}"
        ;;
    fetch-thirdparty)
        kodev-fetch-thirdparty
        ;;
    clean)
        shift 1
        kodev-clean "$@"
        ;;
    build)
        shift 1
        kodev-build "$@"
        ;;
    release)
        shift 1
        kodev-release "$@"
        ;;
    wbuilder)
        kodev-wbuilder
        ;;
    run)
        shift 1
        kodev-run "$@"
        ;;
    test)
        shift 1
        kodev-test "$@"
        ;;
    cov)
        shift 1
        kodev-cov "$@"
        ;;
    prompt)
        kodev-build
        pushd "${EMU_DIR}" && {
            ./luajit -i setupkoenv.lua
        } && popd || exit
        ;;
    log)
        shift 1
        kodev-log "$@"
        ;;
    --help | -h)
        echo "${HELP_MSG}"
        exit 0
        ;;
    *)
        echo "Unknown command: $1."
        echo "${HELP_MSG}"
        exit 1
        ;;
esac
