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

# Needs: tlp-func-base, 34-tlp-func-platform

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

# shellcheck disable=SC2034
readonly ACPIBATDIR=/sys/class/power_supply

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

init_batteries_thresholds () {
    # apply thresholds from configuration to all batteries
    # optional depending on active plugin when specified in $1
    # - called from bg tasks tlp init [re]start/auto and tlp start
    # $1: plugin list (space separated)
    # rc: 0=ok/
    #     1=battery not present/
    #     2=threshold(s) out of range or non-numeric/
    #     3=minimum start stop diff violated/
    #     4=read error/
    #     5=write error/
    #     6=threshold write discarded by kernel or firmware/
    #     255=no thresh api

    local rc

    # select battery feature driver
    select_batdrv
    # shellcheck disable=SC2154
    if [ "$_bm_thresh" = "none" ]; then
        # thresholds not available --> quit
        echo_debug "bat" "set_charge_thresholds.no_method"
        return 255
    fi

    # apply thresholds
    # shellcheck disable=SC2154
    if [ -z "$1" ]; then
        batdrv_apply_configured_thresholds; rc=$?
    elif wordinlist "$_batdrv_plugin" "$1"; then
        batdrv_apply_configured_thresholds; rc=$?
    fi

    return $rc
}

setcharge_battery () {
    # apply charge thresholds for a single battery
    # - called from cmdline tlp setcharge/fullcharge/recalibrate
    # $1: start charge threshold || battery
    # $2: stop charge threshold
    # $3: battery
    # rc: 0=ok/
    #     1=battery not present/
    #     2=threshold(s) out of range or non-numeric/
    #     3=minimum start stop diff violated/
    #     4=read error/
    #     5=write error/
    #     6=threshold write discarded by kernel or firmware/
    #     255=no thresh api

    local bat rc start_thresh stop_thresh
    local use_cfg=0

    # select battery feature driver
    select_batdrv
    # shellcheck disable=SC2154
    if [ "$_bm_thresh" = "none" ]; then
        # thresholds not available --> quit
        cecho "Error: there is no hardware driver support for charge thresholds." 1>&2
        echo_debug "bat" "setcharge_battery.no_method"
        return 255
    fi

    # check params
    case $# in
        0) # no args
            bat=DEF   # use default(1st) battery
            use_cfg=1 # use configured values
            ;;

        1) # assume $1 is battery
            bat=$1
            use_cfg=1 # use configured values
            ;;

        2) # assume $1,$2 are thresholds
            start_thresh=$1
            stop_thresh=$2
            bat=DEF # use default(1st) battery
            ;;

        3|4) # assume $1,$2 are thresholds, $3 is battery
            start_thresh=$1
            stop_thresh=$2
            bat=${3:-DEF}
            ;;
    esac

    # check bat presence and/or get default(1st) battery
    if batdrv_select_battery "$bat"; then
        # battery present -> get configured values if requested
        if [ $use_cfg -eq 1 ]; then
            # shellcheck disable=SC2154
            eval start_thresh="\$START_CHARGE_THRESH_${_bt_cfg_bat}"
            # shellcheck disable=SC2154
            eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}"
        fi
    else
        # battery not present
        cecho "Error: battery $bat not present." 1>&2
        echo_debug "bat" "setcharge_battery.not_present($bat)"
        return 1
    fi

    # apply thresholds
    if [ $use_cfg -eq 1 ]; then
        # from configuration
        batdrv_write_thresholds "$start_thresh" "$stop_thresh" 2 1; rc=$?
    else
        # from command line
        batdrv_write_thresholds "$start_thresh" "$stop_thresh" 2; rc=$?
    fi
    return $rc
}

chargeonce_battery () {
    # charge battery to upper threshold once
    # $1: battery
    # rc: 0=ok/1=battery not present/255=no api

    local bat rc

    # select battery feature driver
    select_batdrv
    if [ "$_bm_thresh" = "none" ]; then
        # thresholds not available --> quit
        cecho "Error: there is no hardware driver support for charge thresholds." 1>&2
        echo_debug "bat" "chargeonce_battery.no_method"
        return 255
    fi

    # check params
    if [ -n "$1" ]; then
        # parameter(s) given, check $1
        bat="${1:-DEF}"
        bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")"
    else
        # no parameters given, use default(1st) battery
        bat=DEF
    fi

    # check bat presence and/or get default(1st) battery
    if ! batdrv_select_battery "$bat"; then
        # battery not present
        cecho "Error: battery $bat not present." 1>&2
        # shellcheck disable=SC2154
        echo_debug "bat" "chargeonce_battery.not_present($_bat_str)"
        return 1
    fi

    # apply temporary start threshold
    batdrv_chargeonce; rc=$?
    if [ $rc -eq 255 ]; then
        cecho "Error: chargeonce not available for your hardware." 1>&2
        echo_debug "bat" "chargeonce_battery.not_supported"
        return 255
    fi

    return $rc
}

discharge_battery () {
    # discharge battery
    # $1: discharge/recalibrate
    # $2: battery
    # $2 or $3: target soc 0(default)..99
    # rc: 0=ok/6=target soc out of bounds/7=target > actual soc/8=target soc reached/10=battery not present/11=fullcharge malfunction/12=no ac power/15=concurrent op running/16=safety lock/255=no api

    local bat mode rc target_soc

    # get params
    mode="${1:-discharge}"
    shift

    if ! check_ac_power "$mode"; then
        return 12
    fi
    check_root

    # select battery care plugin
    select_batdrv
    # shellcheck disable=SC2154
    if [ "$_bm_dischg" = "none" ]; then
        # no method available --> quit
        cecho "Error: there is no hardware driver support for battery $mode." 1>&2
        echo_debug "bat" "discharge_battery.no_method"
        return 255
    fi

    if batdrv_discharge_safetylock "$mode"; then
        return 16
    fi

    if ! lock_tlp_nb tlp_discharge; then
        cecho "Error: another $mode operation is pending." 1>&2
        echo_debug "bat" "discharge_battery.concurrent_op_running"
        return 15
    fi

    # check params $1, $2 (after shift)
    case "$mode" in
        recalibrate)
            # $1 is battery (if existent)
            bat="${1:-DEF}"
            target_soc=0
            ;;

        discharge)
            if [ -z "$1" ] && [ -z "$2" ]; then
                bat=DEF
                target_soc=0
            elif [ -n "$1" ] && [ -z "$2" ]; then
                # $1 is target soc value or battery
                if is_uint "$1"; then
                    bat=DEF
                    target_soc="$1"
                else
                    bat="$1"
                    target_soc=0
                fi
            else
                # $1 is battery, $2 is target soc value
                bat="$1"
                target_soc="$2"
            fi
            ;;
    esac
    bat=$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")

    # check bat presence and/or get default(1st) battery
    if ! batdrv_select_battery "$bat"; then
        # battery not present
        cecho "Error: battery $bat not present." 1>&2
        echo_debug "bat" "discharge_battery.not_present($bat)"
        unlock_tlp tlp_discharge
        return 10
    fi

    # enable fullcharge
    if [ "$mode" = "recalibrate" ] && ! batdrv_write_thresholds DEF DEF 2 ""; then
        echo_debug "bat" "discharge_battery.fullcharge_malfunction"
        unlock_tlp tlp_discharge
        return 11
    fi

    # execute discharge
    batdrv_discharge "$target_soc"; rc=$?
    if [ $rc -eq 0 ] && [ "$mode" = "recalibrate" ]; then
        cecho "Charging starts now. For a complete recalibration" "notice" 1>&2
        cecho "keep AC connected until the battery is fully charged." "notice" 1>&2
    fi

    unlock_tlp tlp_discharge

    return $rc
}

soc_gt_stop_notice () {
    # output notice to discharge on battery power if SOC is above stop threshold
    # global params: $_batteries, $_bm_thresh, $_bd_read, $_bat_str
    # prerequisite: batdrv_init(), batdrv_select_battery()

    # disable SOC check in unit-tests
    [ "$X_SOC_CHECK" = "0" ] && return 0

    # shellcheck disable=SC2154
    if batdrv_check_soc_gt_stop; then
        echo_message "Notice: $_bat_str charge level is above the stop threshold. Use your laptop"`
            `" on battery power until the battery is discharged to the stop threshold."
    fi

    return 0
}

soc_gt_stop_recommendation () {
    # output recommendation to discharge on battery power if SOC is above stop threshold
    # global params: $_batteries, $_bm_thresh, $_bd_read
    # prerequisite: batdrv_init()

    local bat

    # disable SOC check in unit-tests
    [ "$X_SOC_CHECK" = "0" ] && return 0

    # shellcheck disable=SC2154
    for bat in $_batteries; do # iterate detected batteries
        batdrv_select_battery "$bat"
        if batdrv_check_soc_gt_stop; then
            printf "%s charge level is above the stop threshold. Use your laptop on battery power"`
                `" until the battery is discharged to the stop threshold.\n" "$bat"
        fi
    done

    return 0
}
