#!/bin/sh
# 15-lenovo - Battery Plugin for Lenovo laptops:
# 1. Non-ThinkPad series
# 2. ThinkBook seriea
# Requires the ideapad_laptop driver as of kernel 6.17 providing
# /sys/class/power_supply/BAT[01]/charge_types: Standard [Long_Life]
#
# Copyright (c) 2026 Thomas Koch <linrunner at gmx.net> and others.
# SPDX-License-Identifier: GPL-2.0-or-later

# Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat

# --- Hardware Detection

readonly BATDRV_LENOVO_MD=/sys/bus/platform/drivers/ideapad_acpi

batdrv_is_ideapad () {
    # check if kernel module loaded
    # rc: 0=Ideapad, 1=other hardware
    [ -d $BATDRV_LENOVO_MD ]
}

# --- Plugin API functions

batdrv_init () {
    # detect hardware and initialize driver
    # rc: 0=matching hardware detected/1=not detected/2=no batteries detected
    # retval: $_batdrv_plugin, $_batdrv_kmod, $_batdrv_sim
    #
    # 1. check for charge_types kernel api
    #    --> retval $_natacpi:
    #       0=thresholds/
    #       32=disabled/
    #       128=no kernel support
    #
    # 2. determine method for
    #    reading battery data                   --> retval $_bm_read,
    #    reading/writing charging thresholds    --> retval $_bm_thresh,
    #    reading/writing force discharge        --> retval $_bm_dischg:
    #       none/natacpi
    #
    # 3. define sysfile basenames for natacpi
    #    stop threshold                         --> retval $_bn_stop,
    #
    # 4. determine present batteries
    #    list of batteries (space separated)    --> retval $_batteries;
    #
    # 5. define charge threshold defaults
    #    stop threshold                         --> retval $_bt_def_stop;

    _batdrv_plugin="lenovo"
    _batdrv_kmod="ideapad_laptop" # kernel module for natacpi
    _batdrv_sim=0

    # check plugin simulation override and denylist
    if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then
        if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then
            echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate"
            _batdrv_sim=1
        else
            echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip"
            return 1
        fi
    elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then
        echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist"
        return 1
    else
        # check if hardware matches
        if ! batdrv_is_ideapad; then
            echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match"
            return 1
        fi
    fi

    # presume no features at all
    _natacpi=128
    _bm_read="natacpi"
    _bm_thresh="none"
    _bm_dischg="none"
    _batteries=""
    _bn_stop=""
    _bt_def_stop=0

    # iterate batteries and check for charge_types kernel api
    local bd bs
    local done=0
    for bd in "$ACPIBATDIR"/BAT[01]; do
        if [ "$(read_sysf "$bd/present")" = "1" ]; then
            # record detected batteries and directories
            bs="${bd##/*/}"
            _batteries="${_batteries}${_batteries:+ }${bs}"
            # skip natacpi detection for 2nd and subsequent batteries
            [ $done -eq 1 ] && continue

            done=1
            if [ "$NATACPI_ENABLE" = "0" ]; then
                # natacpi disabled in configuration --> skip actual detection
                _natacpi=32
                continue
            fi

            if readable_sysf "$bd/charge_types" || [ "$_batdrv_sim" = "1" ] ; then
                # charge_types sysfile is actually readable (or simulated)
                _natacpi=0
                _bm_thresh="natacpi"
                _bn_stop="charge_types"
            else
                # charge_types sysfile not present --> no kernel support
                _natacpi=128
                continue
            fi
        fi
    done

    # quit if no battery detected, there is no point in activating the plugin
    if [ -z "$_batteries" ]; then
        echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_batteries"
        return 2
    fi

    # consider legacy kernels < 6.17
    if [ $_natacpi -eq 128 ]; then
        # kernel module detected but charge_types not present --> try 16-lenovo-legacy next
        echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_charge_types: batteries=$_batteries; natacpi=$_natacpi"
        return 1
    fi

    # shellcheck disable=SC2034
    _batdrv_selected=$_batdrv_plugin
    echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; bn_stop=$_bn_stop"
    return 0
}

batdrv_select_battery () {
    # determine battery acpidir and sysfiles
    # $1: BAT0/BAT1/DEF
    # global params: $_batdrv_plugin, $_batteries, $_bn_start, $_bn_stop, $_bn_dischg
    # rc: 0=bat exists/1=bat non-existent
    # retval: $_bat_str:    BAT0/BAT1/<other>;
    #         $_bt_cfg_bat: config suffix (BAT0/BAT1);
    #         $_bd_read:    directory with battery data sysfiles;
    #         $_bf_stop:    sysfile for stop threshold;
    # prerequisite: batdrv_init()

    # defaults
    _bat_str=""    # no bat
    _bt_cfg_bat=""
    _bd_read=""    # no directory
    _bf_stop=""

    local bat="$1"

    # convert battery param to uppercase
    bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")"

    # validate battery param
    case "$bat" in
        DEF) # 1st battery is default
            _bat_str="${_batteries%% *}"
            ;;

        *)
            if wordinlist "$bat" "$_batteries"; then
                _bat_str="$bat"
            else
                # battery not present --> quit
                echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present"
                return 1
            fi
            ;;
    esac

    # config suffix equals battery name
    _bt_cfg_bat="$_bat_str"

    # determine natacpi sysfile
    _bd_read="$ACPIBATDIR/$_bat_str"
    if [ "$_bm_thresh" = "natacpi" ]; then
        _bf_stop="$ACPIBATDIR/$_bat_str/$_bn_stop"
    fi

    echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; cfg=$_bt_cfg_bat; bd_read=$_bd_read; bf_stop=$_bf_stop"
    return 0
}

batdrv_read_threshold () {
    # read and print charge threshold (stop only)
    # $1: start/stop - unused dummy for plugin api compatibility
    # $2: 0=api/1=tlp-stat output - unused dummy for plugin api compatibility
    # global params: $_batdrv_plugin, $_bf_stop
    # out: threshold 0/1/"" on error
    # rc: 0=ok/4=read error/255=no api
    # prerequisite: batdrv_init(), batdrv_select_battery()

    local bct out="" rc=0

    if [ "$_bm_thresh" = "natacpi" ]; then
        bct="$(get_listitem "${X_THRESH_SIMULATE_BCT:-$(read_sysf "$_bf_stop")}")"
        out="$(bat_bct2bool "$bct")" || rc=4
    else
        # no threshold api
        rc=255
    fi

    # "return" threshold
    if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then
        printf "%s" "$out"
    else
        rc=4
    fi

    if [ -n "$X_THRESH_SIMULATE_BCT" ]; then
        echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold.simulate: bf_stop=$_bf_stop; bct=$bct; out=$out; rc=$rc"
    else
        echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold: bf_stop=$_bf_stop; bct=$bct; out=$out; rc=$rc"
    fi
    return $rc
}

batdrv_write_thresholds () {
    # write charge thresholds for a battery
    # use pre-determined method and sysfiles from global parms
    # $1: new start threshold -- unused dummy for plugin api compatibility
    # $2: new stop threshold 0/1/DEF(default)
    # $3: 0=quiet/1=output parameter errors/2=output progress and errors
    # $4: non-empty string indicates thresholds stem from configuration
    # global params: $_batdrv_plugin, $_bat_str, $_bt_cfg_bat, $_bf_stop
    # rc: 0=ok/
    #     1=not configured/
    #     2=threshold out of range or non-numeric/
    #     4=threshold read error/
    #     5=threshold write error
    # prerequisite: batdrv_init(), batdrv_select_battery()
    local new_stop="${2:-}"
    local verb="${3:-0}"
    local old_stop
    local new_bct old_bct

    # insert defaults
    [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop

    # --- validate thresholds
    local rc

    if [ -n "$4" ] && [ -z "$new_stop" ]; then
        # do nothing if unconfigured
        echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat"
        return 1
    fi

    # stop: check for 3 digits max, ensure 0 or 1
    if ! is_uint "$new_stop" 3 || \
       ! is_within_bounds "$new_stop" 0 1; then
        # threshold out of range
        echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop.  bat=$_bat_str; cfg=$_bt_cfg_bat"
        case $verb in
            1)
                if [ -n "$4" ]; then
                    echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": not specified or invalid (must be 0 or 1). Battery skipped."
                fi
                ;;

            2)
                if [ -n "$4" ]; then
                    cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": not specified or invalid (must be 0 or 1). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2
                else
                    cprintf "" "Error: charge type (%s) for battery %s is not specified or invalid (must be 0 or 1). Aborted.\n" "$new_stop" "$_bat_str" 1>&2
                fi
                ;;
        esac
        return 2
    fi

    # read active stop threshold value
    if ! old_stop=$(batdrv_read_threshold stop 0); then
        echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat"
        case $verb in
            1) echo_message "Error: could not read current charge type for battery $_bat_str. Battery skipped." ;;
            2) cprintf "" "Error: could not read current charge type for battery %s. Aborted.\n" "$_bat_str" 1>&2 ;;
        esac
        return 4
    fi
    old_bct="$(bat_bool2bct "$old_stop")"

    # write new threshold
    if [ "$verb" = "2" ]; then
        printf "Setting temporary charge type for battery %s:\n" "$_bat_str" 1>&2
    fi

    local rc=0
    new_bct="$(bat_bool2bct "$new_stop")"
    if [ "$old_stop" != "$new_stop" ]; then
        # new threshold differs from effective one --> write it (if not in simulation)
        if [ "$X_THRESH_SIMULATE_WRITEERR" = "1" ] \
            || { [ "$_batdrv_sim" = "0" ] && ! write_sysf "$new_bct" "$_bf_stop"; }; then
            rc=5
        fi
        echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_bct/$old_stop; new=$new_bct/$new_stop; rc=$rc"
        case $verb in
            2)
                if [ $rc -eq 0 ]; then
                    printf "  charge type = %s\n" "$new_bct" 1>&2
                else
                    cprintf "err" "  charge type = %s (Error: write failed)\n" "$new_bct" 1>&2
                fi
                ;;
            1)
                if [ $rc -gt 0 ]; then
                    echo_message "Error: writing charge type failed."
                fi
                ;;
        esac
    else
        echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).no_change: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_bct/$old_stop; new=$new_bct/$new_stop"
        if [ "$verb" = "2" ]; then
                printf "  charge type = %s (no change)\n" "$new_bct" 1>&2
        fi
    fi

    return $rc
}

batdrv_calc_soc () {
    # function not implemented as not required
    return 255
}

batdrv_chargeonce () {
    # function not implemented for Lenovo laptops
    # global param: $_batdrv_plugin
    # prerequisite: batdrv_init()

    echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented"
    return 255
}

batdrv_apply_configured_thresholds () {
    # apply configured charge type from configuration to all batteries
    # - called for bg tasks tlp init [re]start/auto and tlp start
    # output parameter errors only
    # prerequisite: batdrv_init()

    local bat stop_thresh

    for bat in BAT0 BAT1; do
        if batdrv_select_battery "$bat"; then
            eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}"
            batdrv_write_thresholds "DEF" "$stop_thresh" 1 1
        fi
    done

    return 0
}

batdrv_read_force_discharge () {
    # function not implemented for Lenovo laptops
    # global param: $_batdrv_plugin
    # prerequisite: batdrv_init()

    echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge.not_implemented"
    return 255
}

batdrv_write_force_discharge () {
    # function not implemented for Lenovo laptops
    # global param: $_batdrv_plugin
    # prerequisite: batdrv_init()

    echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented"
    return 255
}

batdrv_cancel_force_discharge () {
    # function not implemented for Lenovo laptops
    # global param: $_batdrv_plugin
    # prerequisite: batdrv_init()

    echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented"
    return 255
}

batdrv_force_discharge_active () {
    # function not implemented for Lenovo laptops
    # global param: $_batdrv_plugin
    # prerequisite: batdrv_init()

    echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented"
    return 255
}

batdrv_discharge_safetylock () {
    # check safety lock - force-discharge not implemented for Lenovo laptops
    # $1: discharge/recalibrate
    # rc: 0=engaged/1=disengaged

    return 1
}

batdrv_discharge () {
    # function not implemented for Lenovo laptops
    # global param: $_batdrv_plugin
    # prerequisite: batdrv_init()

    echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented"
    return 255
}

batdrv_show_battery_data () {
    # output battery status
    # $1: 1=verbose
    # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_natacpi, $_bm_thresh, $_bd_read, $_bf_stop
    # prerequisite: batdrv_init()
    local verbose="${1:-0}"

    printf "+++ Battery Care\n"
    printf "Plugin: %s\n" "$_batdrv_plugin"

    if [ "$_bm_thresh" = "natacpi" ]; then
        cprintf "success" "Supported features: charge threshold\n"
    else
        cprintf "warning" "Supported features: none available\n"
    fi

    printf "Driver usage:\n"
    # vendor specific kernel api
    case $_natacpi in
        0)   cprintf "success" "* vendor (%s) = active (charge type)\n" "$_batdrv_kmod" ;;
        32)  cprintf "notice"  "* vendor (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;;
        128) cprintf "err"     "* vendor (%s) = inactive (no kernel support)\n" "$_batdrv_kmod" ;;
        254) cprintf "warning" "* vendor (%s) = inactive (laptop not supported)\n" "$_batdrv_kmod" ;;
        *)   cprintf "err"     "* vendor (%s) = unknown status\n" "$_batdrv_kmod" ;;
    esac

    if [ "$_bm_thresh" = "natacpi" ]; then
        printf "Parameter value range:\n"
        printf "* STOP_CHARGE_THRESH_BAT0/1:  0(Standard)..1(Long_Life) -- charge_types\n"
    fi
    printf "\n"

    # -- show battery data
    local bat
    local bcnt=0

    for bat in $_batteries; do # iterate batteries
        batdrv_select_battery "$bat"

        printf "+++ Battery Status: %s\n" "$bat"

        print_bat_make "$verbose"
        print_bat_cycle_count "$_bd_read/cycle_count"
        print_bat_energy
        print_bat_state "$_bd_read/status"
        printf "\n"
        print_bat_voltages "$verbose"

        # --- show battery features: thresholds
        if [ "$_bm_thresh" = "natacpi" ]; then
            if [ "$_batdrv_sim" = "1" ]; then
                printf "%-59s = %s\n" "$_bf_stop" "$X_THRESH_SIMULATE_BCT"
            else
                printparm "%-59s = ##%s##" "$_bf_stop" "not available"
            fi
            printf "\n"
        fi

        print_bat_level

        bcnt=$((bcnt+1))

    done # for bat

    [ $bcnt -gt 1 ] && print_bat_level_total

    return 0
}

batdrv_check_soc_gt_stop () {
    # function not implemented for Lenovo laptops
    # note: the actual discrete threshold value varies depending on the model and cannot be read in userspace

    return 1
}

batdrv_recommendations () {
     # no recommendations for Lenovo laptops

    return 0
}
