#!/bin/sh
# func-test - Unit Test Helper Functions
#
# Copyright (c) 2025 Thomas Koch <linrunner at gmx.net> and others.
# SPDX-License-Identifier: GPL-2.0-or-later

# shellcheck disable=SC2034

# ----------------------------------------------------------------------------
# Constants

# ansi codes
readonly ANSI_RED="\033[31m"
readonly ANSI_GREEN="\033[32m"
readonly ANSI_BLACK="\033[m"

# power supplies
readonly PS_AC=0
readonly PS_BAT=1
readonly PS_UNKNOWN=128

# power profiles
readonly PP_PRF=0
readonly PP_BAL=1
readonly PP_SAV=2
readonly PP_SUS=3
readonly PP_VALID="0 1 2"

# manual/persistent mode exceptional cases
readonly MPM_UNKNOWN=128
readonly MPM_OFF=255

# ----------------------------------------------------------------------------
# Functions

# --- Checks

is_uint () { # check for unsigned integer -- $1: string
    printf "%s" "$1" | grep -E -q "^[0-9]+$" 2> /dev/null
}

wordinlist () { # test if word in list
                # $1: word, $2: whitespace-separated list of words
    local word

    if [ -n "${1-}" ]; then
        for word in ${2-}; do
            [ "${word}" != "${1}" ] || return 0 # exact match
        done
    fi

    return 1 # no match
}

test_root () {
    # test root privilege -- rc: 0=root, 1=not root
    [ "$(id -u)" = "0" ]
}

check_tlp () {
    # test if tlp installed
    if [ ! -f /usr/sbin/tlp ]; then
        printf_msg "Error: %s not installed." "$TLP" 1>&2
        exit 254
    fi
}

on_ac () {
    # Detect AC power
    # rc: 0=AC/1=BAT
    # Note: compared to get_sys_power_supply() this is primitive. but it will do.
    upower -i /org/freedesktop/UPower/devices/line_power_AC 2> /dev/null | grep -qE 'online:\s+yes'
}

bat_present () {
    # Check for battery
    # $1: battery name
    # rc: 0=present/1=absent

    [ "$(read_sysf "/sys/class/power_supply/$1/present")" = "1" ]
}

# --- sysfs
read_sysf () {
    # read and print contents of a sysfile
    # return 1 and print default if read fails
    # $1: sysfile
    # $2: default
    # rc: 0=ok/1=error
    if cat "$1" 2> /dev/null; then
        return 0
    else
        printf "%s" "$2"
        return 1
    fi
}

write_sysf () { # write string to a sysfile
    # $1: string
    # $2: sysfile
    # rc: 0=ok/1=error
    { printf '%s\n' "$1" > "$2"; } 2> /dev/null
}

compare_sysf () {
    # Compare a string to the contents of a sysfile
    # expression
    # $1: string
    # $2: file

    local cmp_str="$1"
    local sys_str

    if [ -f "$2" ]; then
        sys_str=$(read_sysf "$2")
        if [ "$sys_str" != "$cmp_str" ]; then
            printf_msg "\n*** Deviation at %s: %s (act) != %s (exp)\n" "$2" "$sys_str" "$cmp_str"
            return 1
        fi
    else
        # file is missing
        if [ -n "$cmp_str" ]; then
            printf_msg "\n*** Deviation for %s: sysfile does not exist.\n" "$2"
            return 2
        fi
    fi

    return 0
}

glob_compare_sysf () {
    # Compare a string to the contents of sysfiles selected by a glob
    # expression
    # $1: string
    # $2..$n: file, ...

    local cmp_str="$1"
    local file_pat="$*"
    file_pat="${file_pat#* }"
    local sys_str
    local cnt=0

    while shift && [ $# -gt 0 ]; do
        if [ -f "$1" ] && sys_str=$(read_sysf "$1"); then
            cnt=$((cnt + 1))
            if [ "$sys_str" != "$cmp_str" ]; then
                printf_msg "\n*** Deviation at %s: %s (act) != %s (exp)\n" "$1" "$sys_str" "$cmp_str"
                return 1
            fi
        fi
    done

    if [ "$cnt" -eq 0 ]; then
        printf_msg "\n*** Deviation for %s: no matching sysfile(s) exist(s).\n" "$file_pat"
        return 2
    fi

    return 0
}

read_saved_profile () {
    # read saved profile and power source
    # retval: $_prof, $_ps
    read -r _prof _ps < "$LASTPWR" 2> /dev/null
    if [ -z "$_prof" ]; then
        printf_msg "*** Profile: missing or empty statefile.\n"
    fi
    if [ -z "$_ps" ]; then
        printf_msg "*** Power source: missing or empty statefile.\n"
    fi
}

# --- Run
cache_root_cred () {
    # cache user credentials before actual testing
    sudo true
}

run_clitest () {
    # Run clitest script and record result line to file
    # $1: script filepath
    # $2: suffix
    # global param: $_report_file

    if [ -f "$_report_file" ]; then
        printf "%-50s --> " "${1##*/} $2" >> "$_report_file"
        clitest --color always "$1" | tee /dev/fd/2 | grep -E '(OK|FAIL):' >> "$_report_file"
    else
        clitest --color always "$1" 1>&2
    fi
    printf "\n" 1>&2
}

# --- Messages, Reports
printf_msg () {
    # print message to stderr and logfile
    # $1: format string
    # $2..$n: message string(s)
    local fmt="$1"
    shift
    # shellcheck disable=SC2154,SC2059
    printf "$fmt" "$@" | tee "${_logfile:-/dev/null}" 1>&2
}

start_report () {
    # Create report file
    # retval: $_report_file, $_nest_level

    if [ -z "$_report_file" ]; then
        # first call -> create report temp file
        if ! _report_file="$(mktemp --tmpdir "tlp-test-report.XXX")"; then
            printf "Error: failed to create report file.\n" 1>&2
        fi
        export _report_file
        export _nest_level=0
    else
        # increment level
        if is_uint "$_nest_level"; then
            export _nest_level="$((_nest_level + 1))"
        else
            export _nest_level=1
        fi
    fi
}

report_test () {
    # Write test name to report
    if [ -f "$_report_file" ]; then
        printf "%-50s --> " "$1" >> "$_report_file"
    fi
}

report_line () {
    # Write text line to report
    # $1: text
    if [ -f "$_report_file" ]; then
        # note: use output string in format for proper ansi esc sequence interpolation
        # shellcheck disable=SC2059
        printf "$1\n"  >> "$_report_file"
    fi
}

report_result () {
    # Write test result to terminal and report
    # $1: # of tests
    # $2: # of errors
    if [ "$2" -eq 0 ]; then
        printf_msg "${ANSI_GREEN}OK:${ANSI_BLACK} %s of %s tests passed\n\n" "$1" "$1"
        report_line "${ANSI_GREEN}OK:${ANSI_BLACK} $1 of $1 tests passed"
    else
        printf_msg "${ANSI_RED}FAIL:${ANSI_BLACK} %s of %s tests failed\n\n" "$2" "$1"
        report_line "${ANSI_RED}FAIL:${ANSI_BLACK} $2 of $1 tests failed"
    fi
}

print_report () {
    [ "$_nest_level" -gt 0 ] && return

    if [ -f "$_report_file" ]; then
        printf "+++ Test Summary ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"
        cat "$_report_file"
        printf "\n"
        rm -f "$_report_file"
    else
        printf "Error: missing report file ''%s''.\n" "$_report_file" 1>&2
    fi
}
