#! /bin/sh

# cpufreqctl - This script can configure the frequency scaling driver of your CPU.
#
# Copyright (C) 2015-2021
#     Martin Koppehel <psl.kontakt@gmail.com>,
#     Fin Christensen <christensen.fin@gmail.com>,
#
# This file is part of the gnome-shell extension cpupower.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

set -o errexit -o nounset

VERSION="10.1.2"
PRODUCTION=yes

log ()
{
    printf "%b\n" "$@" >&2
}

EXIT_CODE=0
# exit with the last set exit code
bail ()
{
    exit "${EXIT_CODE}"
}

# error with no arguments provided
no_arguments ()
{
    log "error: no arguments provided!"
    EXIT_CODE=3
}

# error with invalid argument
# $1    argument
# [$2]  subcommand if available
invalid_argument ()
{
    if [ $# = 1 ]
    then
        log "error: invalid argument '$1' provided!"
    else
        log "error: invalid argument '$1' provided in subcommand '$2'!"
    fi
    EXIT_CODE=4
}

# error with out of range
# $1  number
# $2  range
out_of_range ()
{
    log "error: number '$1' is out of range $2!"
    EXIT_CODE=5
}

# error with backend does not exist
# $1  backend
invalid_backend ()
{
    log "error: the backend '$1' does not exist!"
    log "       use 'cpufreqctl backends list' to see a list of available backends"
    EXIT_CODE=7
}

# error with internal error
# $1  component
internal_error ()
{
    log "error: an internal error occurred in component '$1'!"
    EXIT_CODE=8
}

# error with unsupported backend
# $1  backend
not_supported ()
{
    log "error: the backend '$1' is not supported on your system!"
    EXIT_CODE=9
}

fake_init ()
{
    FAKE_DIR=/tmp/cpufreqctl-fake-backend
    if [ ! -d "${FAKE_DIR}" ]
    then
        mkdir -p "${FAKE_DIR}"
        echo off > "${FAKE_DIR}/turbo"
        echo 20 > "${FAKE_DIR}/min_pct"
        echo 80 > "${FAKE_DIR}/max_pct"
    fi
}

fake_supported ()
{
    test "${PRODUCTION}" = no
}

fake_reset()
{
    rm -rf /tmp/cpufreqctl-fake-backend
}

fake_turbo_get ()
{
    fake_init
    report_turbo_get --turbo "$(cat "${FAKE_DIR}/turbo")"
}

fake_turbo_set ()
{
    fake_init
    echo "$1" > "${FAKE_DIR}/turbo"
}

fake_min_get ()
{
    fake_init
    report_min_get --min "$(cat "${FAKE_DIR}/min_pct")"
}

fake_min_set ()
{
    fake_init

    min="$1"
    if [ "${min}" -lt 10 ]
    then
        min="10"
    fi

    echo "${min}" > "${FAKE_DIR}/min_pct"
}

fake_max_get ()
{
    fake_init
    report_max_get --max "$(cat "${FAKE_DIR}/max_pct")"
}

fake_max_set ()
{
    fake_init

    max="$1"
    min=$(cat "${FAKE_DIR}/min_pct")
    if [ "${max}" -lt "${min}" ]
    then
        max="${min}"
    fi

    echo "${max}" > "${FAKE_DIR}/max_pct"
}

fake_info_frequencies ()
{
    fake_init
    report_info_frequencies --mode continuous --min 10 --max 100
}

fake_info_current ()
{
    fake_init

    shuf -i 800000-3600000 -n 12 | sort -n | awk '
        BEGIN {
            srand()
        }
        NR == 1 {
            printf "%d ", $1
        }
        {
            sum += $1
            r[NR] = $1
        }
        END {
            printf "%d %.0f %d\n", $1, sum / NR, r[int(rand() * NR) + 1]
        }' | while read -r min max avg rnd
    do
        report_info_current --min "${min}" --max "${max}" --avg "${avg}" --rnd "${rnd}"
    done
}

intel_pstate_supported ()
{
    test -d /sys/devices/system/cpu/intel_pstate
}

intel_pstate_reset ()
{
    intel_pstate_max_set 100
    intel_pstate_min_set 0
    intel_pstate_turbo_set "on"
}

intel_pstate_turbo_get ()
{
    value=$(cat "/sys/devices/system/cpu/intel_pstate/no_turbo")
    if [ "${value}" -eq 0 ]
    then
        report_turbo_get --turbo on
    else
        report_turbo_get --turbo off
    fi
}

intel_pstate_turbo_set ()
{
    case "$1" in
        on)
            echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo
            ;;
        off)
            echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo
            ;;
        *)
            internal_error intel_pstate_turbo_set
            bail
            ;;
    esac
}

intel_pstate_min_get ()
{
    report_min_get --min "$(cat /sys/devices/system/cpu/intel_pstate/min_perf_pct)"
}

intel_pstate_min_set ()
{
    echo "$1" > /sys/devices/system/cpu/intel_pstate/min_perf_pct
}

intel_pstate_max_get ()
{
    report_max_get --max "$(cat /sys/devices/system/cpu/intel_pstate/max_perf_pct)"
}

intel_pstate_max_set ()
{
    echo "$1" > /sys/devices/system/cpu/intel_pstate/max_perf_pct
}

intel_pstate_info_frequencies ()
{
    # prevent race condition if two cpufreqctl instances run simultaneously
    # by instancing lock on fd 7 (can be 3 to 9, I chose 7)
    exec 7> /dev/null
    flock 7

    prev=$(cat /sys/devices/system/cpu/intel_pstate/max_perf_pct)
    echo 100 > /sys/devices/system/cpu/intel_pstate/max_perf_pct
    max=$(cat /sys/devices/system/cpu/intel_pstate/max_perf_pct)
    echo "${prev}" > /sys/devices/system/cpu/intel_pstate/max_perf_pct

    prev=$(cat /sys/devices/system/cpu/intel_pstate/min_perf_pct)
    echo 0 > /sys/devices/system/cpu/intel_pstate/min_perf_pct
    min=$(cat /sys/devices/system/cpu/intel_pstate/min_perf_pct)
    echo "${prev}" > /sys/devices/system/cpu/intel_pstate/min_perf_pct

    report_info_frequencies --mode continuous --min "${min}" --max "${max}"
}

intel_pstate_info_current ()
{
    for scaling_cur_freq in /sys/devices/system/cpu/cpufreq/policy*/scaling_cur_freq
    do
        cat "${scaling_cur_freq}" &
    done | sort -n | awk '
        BEGIN {
            srand()
        }
        NR == 1 {
            printf "%d ", $1
        }
        {
            sum += $1
            r[NR] = $1
        }
        END {
            printf "%d %.0f %d\n", $1, sum / NR, r[int(rand() * NR) + 1]
        }' | while read -r min max avg rnd
    do
        report_info_current --min "${min}" --max "${max}" --avg "${avg}" --rnd "${rnd}"
    done
}

cpufreq_supported ()
{
    test -d /sys/devices/system/cpu/cpu0/cpufreq &&
        test -e /sys/devices/system/cpu/cpufreq/boost &&
        test -e /sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq &&
        test -e /sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq
}

cpufreq_reset ()
{
    cpufreq_max_set 100
    cpufreq_min_set 0
    cpufreq_turbo_set "on"
}

cpufreq_max_freq ()
{
    # default to currently set maximum frequency
    freq=$(cat /sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq)

    # if cpuinfo is reported, use this value
    if [ -e /sys/devices/system/cpu/cpufreq/policy0/cpuinfo_max_freq ]
    then
        freq=$(cat /sys/devices/system/cpu/cpufreq/policy0/cpuinfo_max_freq)
    fi

    # if cpu reports available frequencies, use the highest value
    if [ -e /sys/devices/system/cpu/cpufreq/policy0/scaling_available_frequencies ]
    then
        frequencies=$(cat /sys/devices/system/cpu/cpufreq/policy0/scaling_available_frequencies)
        # shellcheck disable=SC2086
        frequencies=$(printf '%s\n' ${frequencies} | sort -n)
        freq=$(echo "${frequencies}" | tail -n 1)
    fi

    # if a bios limit is reported, use it
    if [ -e /sys/devices/system/cpu/cpufreq/policy0/bios_limit ]
    then
        freq=$(cat /sys/devices/system/cpu/cpufreq/policy0/bios_limit)
    fi

    echo "${freq}"
}

cpufreq_make_pct ()
{
    if [ -z "${CPUFREQ_MAX-}" ]
    then
        CPUFREQ_MAX=$(cpufreq_max_freq)
    fi

    awk -v max="${CPUFREQ_MAX}" '{ printf "%.0f\n", $0 / max * 100 }'
}

cpufreq_make_abs ()
{
    if [ -z "${CPUFREQ_MAX-}" ]
    then
        CPUFREQ_MAX=$(cpufreq_max_freq)
    fi

    awk -v max="${CPUFREQ_MAX}" '{ printf "%.0f\n", $0 / 100 * max }'
}

cpufreq_turbo_get ()
{
    value=$(cat /sys/devices/system/cpu/cpufreq/boost)
    if [ "${value}" -eq 0 ]
    then
        report_turbo_get --turbo off
    else
        report_turbo_get --turbo on
    fi
}

cpufreq_turbo_set ()
{
    case "$1" in
        on)
            echo 1 > /sys/devices/system/cpu/cpufreq/boost
            ;;
        off)
            echo 0 > /sys/devices/system/cpu/cpufreq/boost
            ;;
        *)
            internal_error cpufreq_turbo_set
            bail
            ;;
    esac
}

cpufreq_min_read ()
{
    for scaling_min_freq in /sys/devices/system/cpu/cpufreq/policy*/scaling_min_freq
    do
        cat "${scaling_min_freq}" &
    done | awk '
        {
            sum += $1
        }
        END {
            printf "%.0f\n", sum / NR
        }'
}

cpufreq_min_write ()
{
    for scaling_min_freq in /sys/devices/system/cpu/cpufreq/policy*/scaling_min_freq
    do
        echo "$1" > "${scaling_min_freq}" &
    done
    wait
}

cpufreq_min_get ()
{
    report_min_get --min "$(cpufreq_min_read | cpufreq_make_pct)"
}

cpufreq_min_set ()
{
    cpufreq_min_write "$(echo "$1" | cpufreq_make_abs)"
}

cpufreq_max_read ()
{
    for scaling_max_freq in /sys/devices/system/cpu/cpufreq/policy*/scaling_max_freq
    do
        cat "${scaling_max_freq}" &
    done | awk '
        {
            sum += $1
        }
        END {
            printf "%.0f\n", sum / NR
        }' | cpufreq_make_pct
}

cpufreq_max_write ()
{
    for scaling_max_freq in /sys/devices/system/cpu/cpufreq/policy*/scaling_max_freq
    do
        echo "$1" > "${scaling_max_freq}" &
    done
    wait
}

cpufreq_max_get ()
{
    report_max_get --max "$(cpufreq_max_read)"
}

cpufreq_max_set ()
{
    cpufreq_max_write "$(echo "$1" | cpufreq_make_abs)"
}

cpufreq_info_frequencies ()
{
    # prevent race condition if two cpufreqctl instances run simultaneously
    # by instancing lock on fd 7 (can be 3 to 9, I chose 7)
    exec 7> /dev/null
    flock 7

    prev=$(cpufreq_min_read)
    cpufreq_min_write 0
    min=$(cpufreq_min_read)
    cpufreq_min_write "${prev}"

    report_info_frequencies --mode continuous --min "$(echo "${min}" | cpufreq_make_pct)" --max 100
}

cpufreq_info_current ()
{
    for scaling_cur_freq in /sys/devices/system/cpu/cpufreq/policy*/scaling_cur_freq
    do
        cat "${scaling_cur_freq}" &
    done | sort -n | awk '
        BEGIN {
            srand()
        }
        NR == 1 {
            printf "%d ", $1
        }
        {
            sum += $1
            r[NR] = $1
        }
        END {
            printf "%d %.0f %d\n", $1, sum / NR, r[int(rand() * NR) + 1]
        }' | while read -r min max avg rnd
    do
        report_info_current --min "${min}" --max "${max}" --avg "${avg}" --rnd "${rnd}"
    done
}

backend_select()
{
    if [ "${CPUFREQCTL_BACKEND}" = "automatic" ]
    then
        CPUFREQCTL_BACKEND="intel_pstate"
        if intel_pstate_supported
        then
            CPUFREQCTL_BACKEND="intel_pstate"
        elif cpufreq_supported
        then
            CPUFREQCTL_BACKEND="cpufreq"
        else
            not_supported "automatic"
            bail
        fi
    fi
}

backend ()
{
    if ! "${CPUFREQCTL_BACKEND}"_supported
    then
        not_supported "${CPUFREQCTL_BACKEND}"
        bail
    fi

    function_name="$1"
    shift
    "${CPUFREQCTL_BACKEND}"_"${function_name}" "$@"
}

report_info_frequencies () {
    MODE=""
    MIN=""
    MAX=""
    FREQUENCIES=""
    while true
    do
        if [ $# -le 0 ]
        then
            break
        fi

        case "$1" in
            --mode)
                shift
                MODE="$1"
                shift
                ;;
            --min)
                shift
                MIN="$1"
                shift
                ;;
            --max)
                shift
                MAX="$1"
                shift
                ;;
            *)
                FREQUENCIES="$*"
                break
                ;;
        esac
    done

    case "${FORMAT}" in
        human)
            case "${MODE}" in
                continuous)
                    log "mode: continuous"
                    log "min: ${MIN}"
                    log "max: ${MAX}"
                    ;;
                discrete)
                    log "mode: discrete"
                    log "frequencies: ${FREQUENCIES}"
                    ;;
                *)
                    internal_error report_info_frequencies
                    bail
                    ;;
            esac
            ;;
        json)
            case "${MODE}" in
                continuous)
                    echo '{'
                    echo '  "mode": "continuous",'
                    echo "  \"min\": ${MIN},"
                    echo "  \"max\": ${MAX}"
                    echo '}'
                    ;;
                discrete)
                    echo '{'
                    echo '  "mode": "discrete",'
                    echo '  "frequencies": ['
                    echo "${FREQUENCIES}" | awk -v indent=4 '
                        BEGIN {
                            ORS=""
                            RS=" "
                            p=""
                        }
                        {
                            printf "%s%" indent "s%s", p, " ", $0
                            p=",\n"
                        }'
                    echo '  ]'
                    echo '}'
                    ;;
                *)
                    internal_error report_info_frequencies
                    bail
                    ;;
            esac
            ;;
        *)
            internal_error report_info_frequencies
            bail
            ;;
    esac
}

report_info_current()
{
    MIN=""
    MAX=""
    AVG=""
    RND=""

    while true
    do
        if [ $# -le 0 ]
        then
            break
        fi

        case "$1" in
            --min)
                shift
                MIN="$1"
                shift
                ;;
            --max)
                shift
                MAX="$1"
                shift
                ;;
            --avg)
                shift
                AVG="$1"
                shift
                ;;
            --rnd)
                shift
                RND="$1"
                shift
                ;;
            *)
                internal_error report_info_current
                bail
                ;;
        esac
    done

    case "${FORMAT}" in
        human)
            log "Mininum frequency of all cores: ${MIN}"
            log "Maximum frequency of all cores: ${MAX}"
            log "Average frequency of all cores: ${AVG}"
            log "Frequency of a random core: ${RND}"
            ;;
        json)
            echo '{'
            echo "  \"min\": ${MIN},"
            echo "  \"max\": ${MAX},"
            echo "  \"avg\": ${AVG},"
            echo "  \"rnd\": ${RND}"
            echo '}'
            ;;
        *)
            internal_error report_info_current
            bail
            ;;
    esac
}

report_turbo_get ()
{
    TURBO=""

    while true
    do
        if [ $# -le 0 ]
        then
            break
        fi

        case "$1" in
            --turbo)
                shift
                TURBO="$1"
                shift
                ;;
            *)
                internal_error report_turbo_get
                bail
                ;;
        esac
    done

    case "${FORMAT}" in
        human)
            log "${TURBO}"
            ;;
        json)
            echo "\"${TURBO}\""
            ;;
        *)
            internal_error report_turbo_get
            bail
            ;;
    esac
}

report_min_get ()
{
    MIN=""

    while true
    do
        if [ $# -le 0 ]
        then
            break
        fi

        case "$1" in
            --min)
                shift
                MIN="$1"
                shift
                ;;
            *)
                internal_error report_min_get
                bail
                ;;
        esac
    done

    case "${FORMAT}" in
        human)
            log "${MIN}"
            ;;
        json)
            echo "${MIN}"
            ;;
        *)
            internal_error report_min_get
            bail
            ;;
    esac
}

report_max_get ()
{
    MAX=""

    while true
    do
        if [ $# -le 0 ]
        then
            break
        fi

        case "$1" in
            --max)
                shift
                MAX="$1"
                shift
                ;;
            *)
                internal_error report_max_get
                bail
                ;;
        esac
    done

    case "${FORMAT}" in
        human)
            log "${MAX}"
            ;;
        json)
            echo "${MAX}"
            ;;
        *)
            internal_error report_max_get
            bail
            ;;
    esac
}

help_copyright ()
{
    log "cpufreqctl ${VERSION}  Copyright (c) 2015-2021  Martin Koppehel, Fin Christensen"
}

usage_turbo ()
{
    log "usage: cpufreqctl turbo [-h] {get,set}"
}

help_turbo ()
{
    log "turbo parameters:"
    log "    get [-h]           get the current turbo boost state"
    log "    set [-h] {on,off}  set the turbo boost state"
}

turbo ()
{
    if [ $# -lt 1 ]
    then
        no_arguments
        usage_turbo
        bail
    fi

    while true
    do
        case "$1" in
            -h|--help)
                usage_turbo
                log
                log "Control the turbo boost setting of your CPU"
                log
                help_turbo
                log
                help_copyright
                break
                ;;
            get)
                shift
                turbo_get "$@"
                break
                ;;
            set)
                shift
                turbo_set "$@"
                break
                ;;
            *)
                invalid_argument "$1" "turbo"
                usage_turbo
                bail
                ;;
        esac
    done
}

usage_turbo_get ()
{
    log "usage: cpufreqctl turbo get [-h]"
}

turbo_get ()
{
    if [ $# -gt 0 ]
    then
        while true
        do
            case "$1" in
                -h|--help)
                    usage_turbo_get
                    log
                    log "Get the current turbo boost setting of your CPU"
                    log
                    help_copyright
                    break
                    ;;
                *)
                    invalid_argument "$1" "turbo get"
                    usage_turbo_get
                    bail
                    ;;
            esac
        done
    else
        backend turbo_get
    fi
}

usage_turbo_set ()
{
    log "usage: cpufreqctl turbo set [-h] {on,off}"
}

turbo_set ()
{
    if [ $# -lt 1 ]
    then
        no_arguments
        usage_turbo_set
        bail
    fi

    while true
    do
        case "$1" in
            -h|--help)
                usage_turbo_set
                log
                log "Set the turbo boost setting of your CPU"
                log
                help_copyright
                break
                ;;
            on)
                backend turbo_set on
                break
                ;;
            off)
                backend turbo_set off
                break
                ;;
            *)
                invalid_argument "$1" "turbo set"
                usage_turbo_get
                bail
                ;;
        esac
    done
}

usage_min ()
{
    log "usage: cpufreqctl min {get,set}"
}

help_min ()
{
    log "min parameters:"
    log "    get [-h]        get the current minimum frequency (in %, [0;100])"
    log "    set [-h] VALUE  set the minimum frequency to a value in [0;100]"
    log "                    the value is automatically clamped to the nearest allowed"
    log "                    CPU frequency"
}

min()
{
    if [ $# -lt 1 ]
    then
        no_arguments
        usage_min
        bail
    fi

    while true
    do
        case "$1" in
            -h|--help)
                usage_min
                log
                log "Control the minimum frequency setting of your CPU"
                log
                help_min
                log
                help_copyright
                break
                ;;
            get)
                shift
                min_get "$@"
                break
                ;;
            set)
                shift
                min_set "$@"
                break
                ;;
            *)
                invalid_argument "$1" "min"
                usage_min
                bail
                ;;
        esac
    done
}

usage_min_get ()
{
    log "usage: cpufreqctl min get [-h]"
}

min_get ()
{
    if [ $# -gt 0 ]
    then
        while true
        do
            case "$1" in
                -h|--help)
                    usage_min_get
                    log
                    log "Get the current minimum frequency setting of your CPU"
                    log
                    help_copyright
                    break
                    ;;
                *)
                    invalid_argument "$1" "min get"
                    usage_min_get
                    bail
                    ;;
            esac
        done
    else
        backend min_get
    fi
}

usage_min_set ()
{
    log "usage: cpufreqctl min set [-h] VALUE[0;100]"
}

help_min_set ()
{
    log "min set parameters:"
    log "    VALUE  the minimum frequency in the range [0;100]"
    log "           the value is automatically clamped to the nearest allowed CPU"
    log "           frequency"
}

min_set ()
{
    if [ $# -lt 1 ]
    then
        no_arguments
        usage_min_set
        bail
    fi

    while true
    do
        case "$1" in
            -h|--help)
                usage_min_set
                log
                log "Set the minimum frequency setting of your CPU"
                log
                help_min_set
                log
                help_copyright
                break
                ;;
            *[!0-9]*) # not a number
                invalid_argument "$1" "min set"
                usage_min_set
                bail
                ;;
            *) # must be a number now
                if [ "$1" -lt 0 ] || [ "$1" -gt 100 ]
                then
                    out_of_range "$1" "[0;100]"
                    usage_min_set
                    bail
                fi

                backend min_set "$1"
                break
                ;;
        esac
    done
}

usage_max ()
{
    log "usage: cpufreqctl max {get,set}"
}

help_max ()
{
    log "max parameters:"
    log "    get [-h]        get the current maximum frequency (in %, [0;100])"
    log "    set [-h] VALUE  set the maximum frequency to a value in [0;100]"
    log "                    the value is automatically clamped to the nearest allowed"
    log "                    CPU frequency"
}

max()
{
    if [ $# -lt 1 ]
    then
        no_arguments
        usage_max
        bail
    fi

    while true
    do
        case "$1" in
            -h|--help)
                usage_max
                log
                log "Control the maximum frequency setting of your CPU"
                log
                help_max
                log
                help_copyright
                break
                ;;
            get)
                shift
                max_get "$@"
                break
                ;;
            set)
                shift
                max_set "$@"
                break
                ;;
            *)
                invalid_argument "$1" "max"
                usage_max
                bail
                ;;
        esac
    done
}

usage_max_get ()
{
    log "usage: cpufreqctl max get [-h]"
}

max_get ()
{
    if [ $# -gt 0 ]
    then
        while true
        do
            case "$1" in
                -h|--help)
                    usage_max_get
                    log
                    log "Get the current maximum frequency setting of your CPU"
                    log
                    help_copyright
                    break
                    ;;
                *)
                    invalid_argument "$1" "max get"
                    usage_max_get
                    bail
                    ;;
            esac
        done
    else
        backend max_get
    fi
}

usage_max_set ()
{
    log "usage: cpufreqctl max set [-h] VALUE[0;100]"
}

help_max_set ()
{
    log "max set parameters:"
    log "    VALUE  the maximum frequency in the range [0;100]"
    log "           the value is automatically clamped to the nearest allowed CPU"
    log "           frequency"
}

max_set ()
{
    if [ $# -lt 1 ]
    then
        no_arguments
        usage_max_set
        bail
    fi

    while true
    do
        case "$1" in
            -h|--help)
                usage_max_set
                log
                log "Set the maximum frequency setting of your CPU"
                log
                help_max_set
                log
                help_copyright
                break
                ;;
            *[!0-9]*) # not a number
                invalid_argument "$1" "max set"
                usage_max_set
                bail
                ;;
            *) # must be a number now
                if [ "$1" -lt 0 ] || [ "$1" -gt 100 ]
                then
                    out_of_range "$1" "[0;100]"
                    usage_max_set
                    bail
                fi

                backend max_set "$1"
                break
                ;;
        esac
    done
}

usage_reset ()
{
    log "usage: cpufreqctl reset [-h]"
}

reset ()
{
    if [ $# -gt 0 ]
    then
        while true
        do
            case "$1" in
                -h|--help)
                    usage_reset
                    log
                    log "Reset the settings of the current backend"
                    log
                    help_copyright
                    break
                    ;;
                *)
                    invalid_argument "$1" "reset"
                    usage_reset
                    bail
                    ;;
            esac
        done
    else
        backend reset
    fi
}

usage_info ()
{
    log "usage: cpufreqctl info [-h] {frequencies,current}"
}

help_info ()
{
    log "info parameters:"
    log "    frequencies [-h]  show the available frequencies and ranges"
    log "    current [-h]      show the current average frequency of the CPU"
}

info ()
{
    if [ $# -lt 1 ]
    then
        no_arguments
        usage_info
        bail
    fi

    while true
    do
        case "$1" in
            -h|--help)
                usage_info
                log
                log "Show information on your CPU"
                log
                help_info
                log
                help_copyright
                break
                ;;
            frequencies)
                shift
                info_frequencies "$@"
                break
                ;;
            current)
                shift
                info_current "$@"
                break
                ;;
            *)
                invalid_argument "$1" "info"
                usage_info
                bail
                ;;
        esac
    done
}

usage_info_frequencies ()
{
    log "usage: cpufreqctl info frequencies [-h]"
}

help_info_frequencies ()
{
    log "info frequencies output:"
    log "    There are two modes in which the CPU frequency can be configured,"
    log "    depending on the scaling driver in the Linux kernel."
    log
    log "    mode: continuous"
    log "        The continuous mode reports minimum and maximum values. The"
    log "        values are percentages of the maximum possible frequency."
    log "        Any value between the minimum and maximum is allowed. E.g.:"
    log "            min: 20"
    log "            max: 100"
    log "    mode: discrete"
    log "        The discrete mode reports all allowed value as a space"
    log "        separated list of frequency percentages of the maximum"
    log "        allowed frequency. E.g:"
    log "            frequencies: 61 78 100"
}

info_frequencies ()
{
    if [ $# -lt 1 ]
    then
        backend info_frequencies
    else
        while true
        do
            case "$1" in
                -h|--help)
                    usage_info_frequencies
                    log
                    log "Show the frequency scaling capabilities of your CPU"
                    log
                    help_info_frequencies
                    log
                    help_copyright
                    break
                    ;;
                *)
                    invalid_argument "$1" "info frequencies"
                    usage_info_frequencies
                    bail
                    ;;
            esac
        done
    fi
}

usage_info_current ()
{
    log "usage: cpufreqctl info current [-h]"
}

info_current ()
{
    if [ $# -lt 1 ]
    then
        backend info_current
    else
        while true
        do
            case "$1" in
                -h|--help)
                    usage_info_current
                    log
                    log "Show the current average frequency of your CPU"
                    log
                    help_copyright
                    break
                    ;;
                *)
                    invalid_argument "$1" "info current"
                    usage_info_current
                    bail
                    ;;
            esac
        done
    fi
}

usage_backends ()
{
    log "usage: cpufreqctl backends [-h] {list,current}"
}

help_backends ()
{
    log "backends parameters:"
    log "    list [-h]     show a complete list of available backends"
    log "    current [-h]  show the currently used backend"
}

backends ()
{
    if [ $# -lt 1 ]
    then
        no_arguments
        usage_backends
        bail
    fi

    while true
    do
        case "$1" in
            -h|--help)
                usage_backends
                log
                log "Show information on the backends available for several different CPU architectures"
                log
                help_backends
                log
                help_copyright
                break
                ;;
            list)
                shift
                backends_list "$@"
                break
                ;;
            current)
                shift
                backend_select
                backends_current "$@"
                break
                ;;
            *)
                invalid_argument "$1" "backends"
                usage_backends
                bail
                ;;
        esac
    done
}

usage_backends_list ()
{
    log "usage: cpufreqctl backends list [-h]"
}

backends_list ()
{
    if [ $# -gt 0 ]
    then
        while true
        do
            case "$1" in
                -h|--help)
                    usage_backends_list
                    log
                    log "Show the backends available for several different CPU architectures"
                    log
                    help_copyright
                    break
                    ;;
                *)
                    invalid_argument "$1" "backends list"
                    usage_backends_list
                    bail
                    ;;
            esac
        done
    else
        case "${FORMAT}" in
            human)
                log "\e[1mbackend names (\e[1;32msupported\e[0;1m / \e[1;31munsupported\e[0;1m)\e[0m"
                if [ "${PRODUCTION}" = "no" ]
                then
                    if fake_supported
                    then
                        FAKE="\e[1;32mfake\e[0m"
                    else
                        FAKE="\e[1;31mfake\e[0m"
                    fi
                    log "${FAKE}          a fake backend used for development purposes"
                fi

                if cpufreq_supported
                then
                    CPUFREQ="\e[1;32mcpufreq\e[0m"
                else
                    CPUFREQ="\e[1;31mcpufreq\e[0m"
                fi
                log "${CPUFREQ}       a general purpose backend for most modern CPUs (including AMD"
                log "              Ryzen)"

                if intel_pstate_supported
                then
                    INTEL_PSTATE="\e[1;32mintel_pstate\e[0m"
                else
                    INTEL_PSTATE="\e[1;31mintel_pstate\e[0m"
                fi
                log "${INTEL_PSTATE}  a backend for Intel Core i CPUs of at least the second generation"
                log "              (2xxx model number)"
                ;;
            json)
                echo '{'

                if [ "${PRODUCTION}" = "no" ]
                then
                    if fake_supported
                    then
                        FAKE="true"
                    else
                        FAKE="false"
                    fi
                    echo "  \"fake\": ${FAKE},"
                fi

                if cpufreq_supported
                then
                    CPUFREQ="true"
                else
                    CPUFREQ="false"
                fi
                echo "  \"cpufreq\": ${CPUFREQ},"

                if intel_pstate_supported
                then
                    INTEL_PSTATE="true"
                else
                    INTEL_PSTATE="false"
                fi
                echo "  \"intel_pstate\": ${INTEL_PSTATE}"

                echo '}'
                ;;
            *)
                internal_error backends_list
                bail
                ;;
        esac
    fi
}

usage_backends_current ()
{
    log "usage: cpufreqctl backends current [-h]"
}

backends_current ()
{
    if [ $# -gt 0 ]
    then
        while true
        do
            case "$1" in
                -h|--help)
                    usage_backends_current
                    log
                    log "Show the currently used backend"
                    log
                    help_copyright
                    break
                    ;;
                *)
                    invalid_argument "$1" "backends current"
                    usage_backends_current
                    bail
                    ;;
            esac
        done
    else
        case "${FORMAT}" in
            human)
                log "${CPUFREQCTL_BACKEND}"
                ;;
            json)
                echo "\"${CPUFREQCTL_BACKEND}\""
                ;;
            *)
                internal_error backends_current
                bail
                ;;
        esac
    fi
}

usage_main ()
{
    log "usage: cpufreqctl [-fhV] {turbo,min,max,reset,info,backends}"
}

help_main ()
{
    log "optional arguments:"
    log "    -b, --backend BACKEND  specify the backend to be used, one of:"
    log "                           [automatic, fake, cpufreq, intel_pstate]"
    log "                           the 'fake' backend is not available in production"
    log "                           (default automatic)"
    log "    -f, --format FORMAT    specify the output format, one of [human, json]"
    log "                           (default human)"
    log "    -h, --help             show this help message and exit"
    log "    -V, --version          show the version of this program and exit"
    log
    log "available actions:"
    log "    turbo [-h] {get,set}             control the turbo boost setting of your CPU"
    log "    min [-h] {get,set}               control the minimum cpu frequency setting"
    log "                                     of your CPU"
    log "    max [-h] {get,set}               control the maximum cpu frequency setting"
    log "                                     of your CPU"
    log "    reset [-h]                       reset the settings of the current backend"
    log "    info [-h] {frequencies,current}  show information on your CPU"
    log "    backends [-h] {list,current}     show information on the available backends"
    log
    help_turbo
    log
    help_min
    log
    help_max
    log
    help_info
    log
    help_backends
    log
    log "examples:"
    log "    Set the maximum frequency to 50%"
    log "        cpufreqctl max set 50"
    log
    log "    Get the turbo boost state"
    log "        cpufreqctl turbo get"
    log
    log "    Find out about the supported frequencies and ranges of your CPU model"
    log "        cpufreqctl info frequencies"
}

main()
{
    if [ $# -lt 1 ]
    then
        no_arguments
        usage_main
        bail
    fi

    FORMAT="human"
    CPUFREQCTL_BACKEND="automatic"

    while true
    do
        case "$1" in
            -h|--help)
                usage_main
                log
                log "Control the frequency setting of your CPU"
                log
                help_main
                log
                help_copyright
                break
                ;;
            -V|--version)
                log "version: ${VERSION}"
                break
                ;;
            -f|--format)
                shift
                case "$1" in
                    human)
                        FORMAT="human"
                        ;;
                    json)
                        FORMAT="json"
                        ;;
                    *)
                        invalid_argument "--format $1"
                        usage_main
                        bail
                        ;;
                esac
                shift
                ;;
            -b|--backend)
                shift
                case "$1" in
                    automatic|fake|cpufreq|intel_pstate)
                        CPUFREQCTL_BACKEND="$1"
                        ;;
                    *)
                        invalid_backend "$1"
                        bail
                        ;;
                esac
                shift
                ;;
            turbo)
                shift
                backend_select
                turbo "$@"
                break
                ;;
            min)
                shift
                backend_select
                min "$@"
                break
                ;;
            max)
                shift
                backend_select
                max "$@"
                break
                ;;
            reset)
                shift
                backend_select
                reset "$@"
                break
                ;;
            info)
                shift
                backend_select
                info "$@"
                break
                ;;
            backends)
                shift
                backends "$@"
                break
                ;;
            *)
                invalid_argument "$1"
                usage_main
                bail
                ;;
        esac
    done
}

main "$@"
