#!/bin/bash
#
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
# SPDX-License-Identifier: LicenseRef-NVIDIA-Proprietary
#
# Direct PPCC register wrapper for real hardware.
#
# DMS batch convention:
#   ppcc-wrapper GET <target> [slot] [param-id] <leaf>...
#   ppcc-wrapper SET <target> [slot] [param-id] <leaf=value>...
#
# Multi-param SET convention:
#   ppcc-wrapper SET <target> <slot> <param-id> value=10 <slot> <param-id> value=11
#   ppcc-wrapper SET <target> <slot> param:0=10 param:1=11
source "$(dirname "$0")/_lib.sh"
trace "$@"

MLXREG_BIN="${PPCC_MLXREG_BIN:-mlxreg}"
LOCAL_PORT="${PPCC_LOCAL_PORT:-1}"
PNAT="${PPCC_PNAT:-0}"
LP_MSB="${PPCC_LP_MSB:-0}"
TARGET_APP="${PPCC_TARGET_APP:-0}"
TEXT_MAX_WORDS=55

# PPCC cmd_type opcodes
PPCC_CMD_GET_ALGO_INFO=0x0
PPCC_CMD_SET_ALGO_CONFIG=0x1
PPCC_CMD_DISABLE_ALGO=0x2
PPCC_CMD_GET_ALGO_STATUS=0x3
PPCC_CMD_GET_NUM_PARAMS=0x4
PPCC_CMD_GET_PARAM_INFO=0x5
PPCC_CMD_GET_PARAM_VALUE=0x6
PPCC_CMD_SET_PARAM_VALUE=0x8
PPCC_CMD_GET_BULK_PARAMS=0xA
PPCC_CMD_SET_BULK_PARAMS=0xB
PPCC_CMD_GET_BULK_COUNTERS=0xC
PPCC_CMD_CLEAR_COUNTERS=0xD
PPCC_CMD_GET_COUNTER_INFO=0xF
PPCC_CMD_GET_ALGO_ARRAY=0x10

# YANG-defined field bounds
SL_BITMASK_MAX=65535
COUNTER_GROUP_ID_MAX=255

die() {
    echo "Error: $*" >&2
    exit 1
}

is_uint() {
    [[ "${1:-}" =~ ^[0-9]+$ ]]
}

to_int() {
    local raw="${1:-0}"
    raw="${raw%%[[:space:]]*}"
    raw="${raw%,}"
    if [[ "$raw" =~ ^0[xX][0-9a-fA-F]+$ ]]; then
        echo $((16#${raw:2}))
    elif [[ "$raw" =~ ^[0-9]+$ ]]; then
        echo "$raw"
    else
        echo 0
    fi
}

bool_to_bit() {
    case "$1" in
        true|True|TRUE|1) echo 1 ;;
        false|False|FALSE|0) echo 0 ;;
        *) die "invalid boolean '$1'" ;;
    esac
}

bit_to_bool() {
    [[ "$(to_int "$1")" == "1" ]] && echo true || echo false
}

field_value() {
    local field="$1"
    awk -v want="$field" '
        function trim(s) { sub(/^[[:space:]]+/, "", s); sub(/[[:space:]]+$/, "", s); return s }
        {
            line=$0
            sub(/^[[:space:]]+/, "", line)
            if (line ~ "^" want "[[:space:]]*[|:]") {
                sub("^[^|:]*[|:][[:space:]]*", "", line)
                print trim(line)
                exit
            }
        }'
}

text_string() {
    local output="$1"
    local bytes="" idx value byte
    while IFS= read -r line; do
        [[ "$line" =~ text\[([0-9]+)\][[:space:]]*[\|:][[:space:]]*([^[:space:]]+) ]] || continue
        value=$(to_int "${BASH_REMATCH[2]}")
        for shift_bits in 24 16 8 0; do
            byte=$(( (value >> shift_bits) & 0xff ))
            [[ $byte -eq 0 ]] && continue
            printf -v bytes '%s\\x%02x' "$bytes" "$byte"
        done
    done <<<"$output"
    printf '%b' "$bytes"
}

text_word() {
    local wanted="$1"
    awk -v wanted="$wanted" '
        function trim(s) { sub(/^[[:space:]]+/, "", s); sub(/[[:space:]]+$/, "", s); return s }
        {
            line=$0
            sub(/^[[:space:]]+/, "", line)
            if (match(line, /^text\[([0-9]+)\][[:space:]]*[|:][[:space:]]*([^[:space:]]+)/, m) && m[1] == wanted) {
                print trim(m[2])
                exit
            }
        }'
}

indexes() {
    local slot="${1:-0}"
    local param="${2:-0}"
    echo "local_port=${LOCAL_PORT},pnat=${PNAT},lp_msb=${LP_MSB},algo_slot=${slot},algo_param_index=${param},target_app=${TARGET_APP}"
}

run_ppcc() {
    local cmd_type="$1"; shift
    local slot="${1:-0}"; shift
    local param="${1:-0}"; shift
    local set_fields="cmd_type=${cmd_type}"
    local err out rc
    err=$(mktemp /tmp/dms-ppcc.XXXXXX)

    for field in "$@"; do
        set_fields="${set_fields},${field}"
    done

    [[ -n "${DMS_BACKEND_TRACE:-}" ]] && \
        echo "$(date -Iseconds)|ppcc-wrapper.mlxreg|slot=${slot}|param=${param}|${set_fields}" >> "$DMS_BACKEND_TRACE"

    out=$("$MLXREG_BIN" -d "$BDF" -y --set "$set_fields" \
        --reg_name PPCC --indexes "$(indexes "$slot" "$param")" 2>"$err")
    rc=$?
    if [[ $rc -ne 0 ]]; then
        echo "Error: mlxreg PPCC cmd_type=${cmd_type} failed (exit=${rc})" >&2
        if [[ -s "$err" ]]; then
            cat "$err" >&2
        elif [[ -n "$out" ]]; then
            printf '%s\n' "$out" >&2
        fi
        rm -f "$err"
        return "$rc"
    fi

    rm -f "$err"
    printf '%s\n' "$out"
}

get_algo_array_word() {
    local slot="${1:-0}" output word
    output=$(run_ppcc $PPCC_CMD_GET_ALGO_ARRAY "$slot" 0) || return 1
    word=$(printf '%s\n' "$output" | text_word "$slot")
    to_int "${word:-0}"
}

get_algo_info() {
    run_ppcc $PPCC_CMD_GET_ALGO_INFO "$1" 0
}

get_algo_status() {
    run_ppcc $PPCC_CMD_GET_ALGO_STATUS "$1" 0
}

get_num_params() {
    local output
    output=$(run_ppcc $PPCC_CMD_GET_NUM_PARAMS "$1" 0) || return 1
    to_int "$(printf '%s\n' "$output" | field_value value)"
}

get_param_info() {
    run_ppcc $PPCC_CMD_GET_PARAM_INFO "$1" "$2"
}

get_param_value() {
    local output
    output=$(run_ppcc $PPCC_CMD_GET_PARAM_VALUE "$1" "$2") || return 1
    to_int "$(printf '%s\n' "$output" | field_value value)"
}

get_bulk_param_value() {
    local slot="$1" param="$2" output word
    output=$(run_ppcc $PPCC_CMD_GET_BULK_PARAMS "$slot" 0) || return 1
    word=$(printf '%s\n' "$output" | text_word "$param")
    to_int "${word:-0}"
}

get_bulk_counter_output() {
    run_ppcc $PPCC_CMD_GET_BULK_COUNTERS "$1" 0
}

get_bulk_counter_value() {
    local slot="$1" counter="$2" output word
    output=$(get_bulk_counter_output "$slot") || return 1
    word=$(printf '%s\n' "$output" | text_word "$counter")
    to_int "${word:-0}"
}

get_counter_count() {
    local slot="$1" output text_len count
    output=$(get_bulk_counter_output "$slot" 2>/dev/null) || { echo 0; return 0; }
    text_len=$(to_int "$(printf '%s\n' "$output" | field_value text_length)")
    if [[ "$text_len" != "0" ]]; then
        echo $((text_len / 4))
        return 0
    fi
    count=$(awk '
        BEGIN { n = 0 }
        /^[[:space:]]*text\[[0-9]+\][[:space:]]*[|:]/ { n++ }
        END { print n }
    ' <<<"$output")
    echo "${count:-0}"
}

get_counter_info() {
    run_ppcc $PPCC_CMD_GET_COUNTER_INFO "$1" "$2"
}

# ppcc_verify_bool output field target_bit requested label
ppcc_verify_bool() {
    local actual
    actual=$(bit_to_bool "$(printf '%s\n' "$1" | field_value "$2")")
    [[ "$actual" == "$(bit_to_bool "$3")" ]] || \
        die "PPCC ${5} readback mismatch: requested ${4}, read ${actual}"
}

# ppcc_verify_int output field target label
ppcc_verify_int() {
    local actual
    actual=$(to_int "$(printf '%s\n' "$1" | field_value "$2")")
    [[ "$actual" == "$3" ]] || \
        die "PPCC ${4} readback mismatch: requested ${3}, read ${actual}"
}

set_algo_config() {
    local slot="$1"; shift
    local new_enabled="" new_counters="" new_tracing="" new_sl="" new_cgid="" new_cgpol=""
    local status

    for arg in "$@"; do
        local key="${arg%%=*}" value="${arg#*=}"
        case "$key" in
            enabled)              new_enabled="$value" ;;
            counters-enabled)     new_counters="$value" ;;
            tracing-enabled)      new_tracing="$value" ;;
            sl-bitmask)           new_sl="$value" ;;
            counter-group-id)     new_cgid="$value" ;;
            counter-group-policy) new_cgpol="$value" ;;
            *) die "unsupported algo config key '${key}'" ;;
        esac
    done

    status=$(get_algo_status "$slot") || return 1

    local cur_enabled cur_counter cur_trace cur_sl cur_cgid cur_cfg_cg
    cur_enabled=$(to_int "$(printf '%s\n' "$status" | field_value value)")
    cur_counter=$(to_int "$(printf '%s\n' "$status" | field_value counter_en)")
    cur_trace=$(to_int "$(printf '%s\n' "$status" | field_value trace_en)")
    cur_sl=$(to_int "$(printf '%s\n' "$status" | field_value sl_bitmask)")
    cur_cgid=$(to_int "$(printf '%s\n' "$status" | field_value counter_group_id)")
    cur_cfg_cg=$(to_int "$(printf '%s\n' "$status" | field_value cfg_counter_group_en)")

    local target_enabled target_counter target_trace target_sl target_cgid target_cfg_cg
    [[ -n "$new_enabled" ]]  && target_enabled=$(bool_to_bit "$new_enabled")   || target_enabled="$cur_enabled"
    [[ -n "$new_counters" ]] && target_counter=$(bool_to_bit "$new_counters")  || target_counter="$cur_counter"
    [[ -n "$new_tracing" ]]  && target_trace=$(bool_to_bit "$new_tracing")     || target_trace="$cur_trace"
    if [[ -n "$new_sl" ]]; then
        [[ "$new_sl" =~ ^(0[xX][0-9a-fA-F]+|[0-9]+)$ ]] || die "invalid sl-bitmask value '${new_sl}'"
        target_sl=$(to_int "$new_sl")
        (( target_sl >= 0 && target_sl <= $SL_BITMASK_MAX )) || die "sl-bitmask ${target_sl} out of range [0,${SL_BITMASK_MAX}]"
    else
        target_sl="$cur_sl"
    fi
    if [[ -n "$new_cgid" ]]; then
        [[ "$new_cgid" =~ ^(0[xX][0-9a-fA-F]+|[0-9]+)$ ]] || die "invalid counter-group-id value '${new_cgid}'"
        target_cgid=$(to_int "$new_cgid")
        (( target_cgid >= 0 && target_cgid <= $COUNTER_GROUP_ID_MAX )) || die "counter-group-id ${target_cgid} out of range [0,${COUNTER_GROUP_ID_MAX}]"
    else
        target_cgid="$cur_cgid"
    fi
    if [[ -n "$new_cgpol" ]]; then
        case "$new_cgpol" in
            algorithm-managed|0) target_cfg_cg=0 ;;
            user-configured|1)   target_cfg_cg=1 ;;
            *) die "invalid counter-group-policy '${new_cgpol}'" ;;
        esac
    elif [[ -n "$new_cgid" ]]; then
        # Setting counter-group-id requires user-configured mode and counter_en=1;
        # without both, the HW silently ignores the counter_group_id write.
        [[ -n "$new_counters" && "$(bool_to_bit "$new_counters")" == "0" ]] && \
            die "counter-group-id requires counters-enabled=true"
        target_cfg_cg=1
        [[ -z "$new_counters" ]] && target_counter=1
    else
        target_cfg_cg="$cur_cfg_cg"
    fi

    if [[ "$target_enabled" == "0" ]]; then
        if [[ -n "$new_sl" || -n "$new_cgid" || -n "$new_cgpol" ]]; then
            die "sl-bitmask, counter-group-id, and counter-group-policy can only be set while the slot is enabled"
        fi
    fi

    if [[ -n "$new_enabled" && "$target_enabled" == "0" ]]; then
        # User is explicitly disabling the slot — use CMD_DISABLE_ALGO and clear
        # trace/counter (they are inactive while disabled).
        target_counter=0
        target_trace=0
        run_ppcc $PPCC_CMD_DISABLE_ALGO "$slot" 0 "counter_en=0" >/dev/null || return 1
    else
        run_ppcc $PPCC_CMD_SET_ALGO_CONFIG "$slot" 0 \
            "counter_en=${target_counter}" \
            "trace_en=${target_trace}" \
            "sl_bitmask=${target_sl}" \
            "counter_group_id=${target_cgid}" \
            "cfg_counter_group_en=${target_cfg_cg}" \
            >/dev/null || return 1
    fi

    local verify
    verify=$(get_algo_status "$slot") || return 1

    [[ -n "$new_enabled" ]]  && ppcc_verify_bool "$verify" value          "$target_enabled" "$new_enabled"  enable
    [[ -n "$new_counters" ]] && ppcc_verify_bool "$verify" counter_en     "$target_counter" "$new_counters" counter_en
    [[ -n "$new_tracing" ]]  && ppcc_verify_bool "$verify" trace_en       "$target_trace"   "$new_tracing"  trace_en
    [[ -n "$new_sl" ]]       && ppcc_verify_int  "$verify" sl_bitmask     "$target_sl"      sl_bitmask
    [[ -n "$new_cgid" ]]     && ppcc_verify_int  "$verify" counter_group_id "$target_cgid"  counter_group_id
    [[ -n "$new_cgpol" ]]    && ppcc_verify_int  "$verify" cfg_counter_group_en "$target_cfg_cg" cfg_counter_group_en

    [[ -n "$new_enabled" ]]  && echo "enabled=$(bit_to_bool "$target_enabled")"
    [[ -n "$new_counters" ]] && echo "counters-enabled=$(bit_to_bool "$target_counter")"
    [[ -n "$new_tracing" ]]  && echo "tracing-enabled=$(bit_to_bool "$target_trace")"
    if [[ "$target_enabled" != "0" ]]; then
        [[ -n "$new_sl" ]]   && echo "sl-bitmask=${target_sl}"
        [[ -n "$new_cgid" ]] && echo "counter-group-id=${target_cgid}"
        if [[ -n "$new_cgpol" ]]; then
            [[ "$target_cfg_cg" == "1" ]] && echo "counter-group-policy=user-configured" \
                                          || echo "counter-group-policy=algorithm-managed"
        fi
    fi
}

set_one_param() {
    local slot="$1" param="$2" value="$3" readback
    run_ppcc $PPCC_CMD_SET_PARAM_VALUE "$slot" "$param" "value=${value}" >/dev/null || return 1
    readback=$(get_param_value "$slot" "$param") || return 1
    if [[ "$readback" != "$value" ]]; then
        die "PPCC param readback mismatch: slot=${slot} param=${param} requested=${value} read=${readback}"
    fi
}

set_params_bulk_preserving_existing() {
    local slot="$1"; shift
    local max_idx=-1 id value output word output_after readback count=0
    local -a values=()
    local -a req_ids=()
    local -a req_values=()
    local -a fields=()

    while [[ $# -gt 0 ]]; do
        id="$1"; value="$2"; shift 2
        is_uint "$id" || die "invalid param id '${id}'"
        is_uint "$value" || die "invalid param value '${value}'"
        if (( id >= TEXT_MAX_WORDS )); then
            die "param id ${id} exceeds PPCC bulk text capacity ${TEXT_MAX_WORDS}"
        fi
        (( id > max_idx )) && max_idx=$id
        req_ids+=("$id")
        req_values+=("$value")
        count=$((count + 1))
    done

    (( count > 0 )) || return 0
    (( count > 1 )) || die "bulk param SET requires more than one parameter"

    if output=$(run_ppcc $PPCC_CMD_GET_BULK_PARAMS "$slot" 0 2>/dev/null); then
        for i in $(seq 0 "$max_idx"); do
            word=$(printf '%s\n' "$output" | text_word "$i")
            [[ -n "$word" ]] || die "PPCC bulk get missing text[$i] before bulk set"
            values[$i]=$(to_int "$word")
        done

        for i in "${!req_ids[@]}"; do
            values[${req_ids[$i]}]="${req_values[$i]}"
        done

        for i in $(seq 0 "$max_idx"); do
            printf -v word '0x%08x' "${values[$i]}"
            fields+=("text[$i]=$word")
        done
        fields+=("text_length=$(((max_idx + 1) * 4))")

        run_ppcc $PPCC_CMD_SET_BULK_PARAMS "$slot" 0 "${fields[@]}" >/dev/null || return 1

        output_after=$(run_ppcc $PPCC_CMD_GET_BULK_PARAMS "$slot" 0) || return 1
        for i in "${!req_ids[@]}"; do
            id="${req_ids[$i]}"
            value="${req_values[$i]}"
            word=$(printf '%s\n' "$output_after" | text_word "$id")
            [[ -n "$word" ]] || die "PPCC bulk get missing text[$id] after bulk set"
            readback=$(to_int "$word")
            if [[ "$readback" != "$value" ]]; then
                die "PPCC bulk param readback mismatch: slot=${slot} param=${id} requested=${value} read=${readback}"
            fi
            echo "param:${id}=${value}"
        done
    else
        for i in "${!req_ids[@]}"; do
            set_one_param "$slot" "${req_ids[$i]}" "${req_values[$i]}" || return 1
            echo "param:${req_ids[$i]}=${req_values[$i]}"
        done
    fi
}

permission_name() {
    case "$(to_int "$1")" in
        1) echo "read-write" ;;
        2) echo "read-only-clearable" ;;
        *) echo "read-only" ;;
    esac
}

OP="${1:-}"; [[ -n "$OP" ]] || die "missing operation"
shift
TARGET="${1:-}"; [[ -n "$TARGET" ]] || die "missing target"
shift
BDF=$(bdf_from_target "$TARGET")

DEFAULT_SLOT=""
DEFAULT_PARAM=""
while [[ $# -gt 0 && "$1" =~ ^[0-9]+$ ]]; do
    if [[ -z "$DEFAULT_SLOT" ]]; then
        DEFAULT_SLOT="$1"
    else
        DEFAULT_PARAM="$1"
    fi
    shift
done
DEFAULT_SLOT="${DEFAULT_SLOT:-0}"

case "$OP" in
    GET)
        SLOT="${DEFAULT_SLOT:-0}"
        PARAM_ID="${DEFAULT_PARAM:-}"
        for _pre in "$@"; do
            [[ "$_pre" == param:* ]] && { PARAM_ID="${_pre#param:}"; break; }
        done
        ALGO_ARRAY_WORD=""
        ALGO_INFO=""
        ALGO_STATUS=""
        PARAM_INFO=""
        PARAM_VALUE=""
        COUNTER_INFO=""
        NUM_PARAMS=""

        for PARAM in "$@"; do
            case "$PARAM" in
                id)
                    echo "id=${SLOT}" ;;
                role)
                    echo "role=rp" ;;
                algo-id|major-version|minor-version)
                    [[ -n "$ALGO_ARRAY_WORD" ]] || ALGO_ARRAY_WORD=$(get_algo_array_word "$SLOT")
                    case "$PARAM" in
                        algo-id) echo "algo-id=$(( (ALGO_ARRAY_WORD >> 16) & 0xffff ))" ;;
                        major-version) echo "major-version=$(( (ALGO_ARRAY_WORD >> 8) & 0xff ))" ;;
                        minor-version) echo "minor-version=$(( ALGO_ARRAY_WORD & 0xff ))" ;;
                    esac ;;
                name)
                    if [[ -n "$PARAM_ID" ]]; then
                        PARAM_INFO="${PARAM_INFO:-$(get_param_info "$SLOT" "$PARAM_ID")}" || exit 1
                        NAME=$(text_string "$PARAM_INFO")
                    else
                        ALGO_INFO="${ALGO_INFO:-$(get_algo_info "$SLOT")}" || exit 1
                        NAME=$(text_string "$ALGO_INFO")
                    fi
                    echo "name=${NAME%%,*}" ;;
                enabled|counters-enabled)
                    ALGO_STATUS="${ALGO_STATUS:-$(get_algo_status "$SLOT")}" || exit 1
                    if [[ "$PARAM" == "enabled" ]]; then
                        echo "enabled=$(bit_to_bool "$(printf '%s\n' "$ALGO_STATUS" | field_value value)")"
                    else
                        echo "counters-enabled=$(bit_to_bool "$(printf '%s\n' "$ALGO_STATUS" | field_value counter_en)")"
                    fi ;;
                tracing-enabled)
                    ALGO_STATUS="${ALGO_STATUS:-$(get_algo_status "$SLOT")}" || exit 1
                    echo "tracing-enabled=$(bit_to_bool "$(printf '%s\n' "$ALGO_STATUS" | field_value trace_en)")" ;;
                sl-bitmask)
                    ALGO_STATUS="${ALGO_STATUS:-$(get_algo_status "$SLOT")}" || exit 1
                    echo "sl-bitmask=$(to_int "$(printf '%s\n' "$ALGO_STATUS" | field_value sl_bitmask)")" ;;
                counter-group-id)
                    ALGO_STATUS="${ALGO_STATUS:-$(get_algo_status "$SLOT")}" || exit 1
                    echo "counter-group-id=$(to_int "$(printf '%s\n' "$ALGO_STATUS" | field_value counter_group_id)")" ;;
                counter-group-policy)
                    ALGO_STATUS="${ALGO_STATUS:-$(get_algo_status "$SLOT")}" || exit 1
                    cfg_cg=$(to_int "$(printf '%s\n' "$ALGO_STATUS" | field_value cfg_counter_group_en)")
                    [[ "$cfg_cg" == "1" ]] && echo "counter-group-policy=user-configured" \
                                           || echo "counter-group-policy=algorithm-managed" ;;
                num-params)
                    NUM_PARAMS="${NUM_PARAMS:-$(get_num_params "$SLOT")}" || exit 1
                    echo "num-params=${NUM_PARAMS}" ;;
                num-counters)
                    echo "num-counters=$(get_counter_count "$SLOT")" ;;
                param-id)
                    echo "param-id=${PARAM_ID:-0}" ;;
                param:*)
                    PARAM_ID="${PARAM#param:}"
                    is_uint "$PARAM_ID" || die "invalid param id '${PARAM_ID}'"
                    PARAM_VALUE="$(get_bulk_param_value "$SLOT" "$PARAM_ID")" || exit 1
                    echo "param:${PARAM_ID}=${PARAM_VALUE}" ;;
                value)
                    [[ -n "$PARAM_ID" ]] || die "value GET requires param-id key"
                    PARAM_VALUE="${PARAM_VALUE:-$(get_bulk_param_value "$SLOT" "$PARAM_ID")}" || exit 1
                    echo "value=${PARAM_VALUE}" ;;
                param-name)
                    [[ -n "$PARAM_ID" ]] || die "param-name GET requires param-id key"
                    PARAM_INFO="${PARAM_INFO:-$(get_param_info "$SLOT" "$PARAM_ID")}" || exit 1
                    NAME=$(text_string "$PARAM_INFO")
                    echo "param-name=${NAME%%,*}" ;;
                default-value|min-value|max-value|permission)
                    [[ -n "$PARAM_ID" ]] || die "${PARAM} GET requires param-id key"
                    PARAM_INFO="${PARAM_INFO:-$(get_param_info "$SLOT" "$PARAM_ID")}" || exit 1
                    case "$PARAM" in
                        default-value) echo "default-value=$(to_int "$(printf '%s\n' "$PARAM_INFO" | field_value param_value1)")" ;;
                        min-value) echo "min-value=$(to_int "$(printf '%s\n' "$PARAM_INFO" | field_value param_value2)")" ;;
                        max-value) echo "max-value=$(to_int "$(printf '%s\n' "$PARAM_INFO" | field_value param_value3)")" ;;
                        permission) echo "permission=$(permission_name "$(printf '%s\n' "$PARAM_INFO" | field_value prm)")" ;;
                    esac ;;
                counter-id)
                    echo "counter-id=${PARAM_ID:-0}" ;;
                counter-value)
                    [[ -n "$PARAM_ID" ]] || die "counter-value GET requires counter-id key"
                    echo "counter-value=$(get_bulk_counter_value "$SLOT" "$PARAM_ID")" ;;
                counter-name|counter-max-value|counter-permission)
                    [[ -n "$PARAM_ID" ]] || die "${PARAM} GET requires counter-id key"
                    COUNTER_INFO="${COUNTER_INFO:-$(get_counter_info "$SLOT" "$PARAM_ID")}" || exit 1
                    case "$PARAM" in
                        counter-name)
                            NAME=$(text_string "$COUNTER_INFO")
                            echo "counter-name=${NAME%%,*}" ;;
                        counter-max-value)
                            echo "counter-max-value=$(to_int "$(printf '%s\n' "$COUNTER_INFO" | field_value param_value3)")" ;;
                        counter-permission)
                            echo "counter-permission=$(permission_name "$(printf '%s\n' "$COUNTER_INFO" | field_value prm)")" ;;
                    esac ;;
                *)
                    die "unsupported PPCC GET leaf '${PARAM}'" ;;
            esac
        done
        ;;

    SET)
        ENABLE_VAL=""
        COUNTER_VAL=""
        TRACE_VAL=""
        SL_BITMASK_VAL=""
        CG_ID_VAL=""
        CG_POLICY_VAL=""
        declare -a PARAM_SET_SLOTS=()
        declare -a PARAM_SET_IDS=()
        declare -a PARAM_SET_VALUES=()

        CUR_SLOT="$DEFAULT_SLOT"
        CUR_PARAM="$DEFAULT_PARAM"
        AUTO_PARAM="${DEFAULT_PARAM:-0}"
        while [[ $# -gt 0 ]]; do
            if is_uint "$1"; then
                CUR_SLOT="$1"; shift
                if [[ $# -gt 0 && "$1" =~ ^[0-9]+$ ]]; then
                    CUR_PARAM="$1"
                    AUTO_PARAM="$1"
                    shift
                else
                    CUR_PARAM=""
                fi
                continue
            fi

            PAIR="$1"; shift
            KEY="${PAIR%%=*}"
            VALUE="${PAIR#*=}"
            [[ "$KEY" != "$PAIR" ]] || die "invalid SET argument '${PAIR}'"

            case "$KEY" in
                enabled)
                    ENABLE_VAL="$VALUE" ;;
                counters-enabled)
                    COUNTER_VAL="$VALUE" ;;
                value)
                    local_param="${CUR_PARAM:-$AUTO_PARAM}"
                    PARAM_SET_SLOTS+=("$CUR_SLOT")
                    PARAM_SET_IDS+=("$local_param")
                    PARAM_SET_VALUES+=("$VALUE")
                    [[ -z "$CUR_PARAM" ]] && AUTO_PARAM=$((AUTO_PARAM + 1)) ;;
                param:*|value:*)
                    PARAM_ID="${KEY#*:}"
                    is_uint "$PARAM_ID" || die "invalid param id '${PARAM_ID}'"
                    PARAM_SET_SLOTS+=("$CUR_SLOT")
                    PARAM_SET_IDS+=("$PARAM_ID")
                    PARAM_SET_VALUES+=("$VALUE") ;;
                tracing-enabled)
                    TRACE_VAL="$VALUE" ;;
                sl-bitmask)
                    SL_BITMASK_VAL="$VALUE" ;;
                counter-group-id)
                    CG_ID_VAL="$VALUE" ;;
                counter-group-policy)
                    CG_POLICY_VAL="$VALUE" ;;
                clear-counters)
                    run_ppcc $PPCC_CMD_CLEAR_COUNTERS "$CUR_SLOT" 0 >/dev/null || exit 1
                    echo "cleared=true" ;;
                *)
                    die "unsupported PPCC SET leaf '${KEY}'" ;;
            esac
        done

        need_algo_config_update=""
        [[ -n "$ENABLE_VAL" || -n "$COUNTER_VAL" || -n "$TRACE_VAL" || -n "$SL_BITMASK_VAL" || -n "$CG_ID_VAL" || -n "$CG_POLICY_VAL" ]] && need_algo_config_update=1

        if [[ -n "$need_algo_config_update" ]]; then
            set_algo_config "$DEFAULT_SLOT" \
                ${ENABLE_VAL:+enabled="$ENABLE_VAL"} \
                ${COUNTER_VAL:+counters-enabled="$COUNTER_VAL"} \
                ${TRACE_VAL:+tracing-enabled="$TRACE_VAL"} \
                ${SL_BITMASK_VAL:+sl-bitmask="$SL_BITMASK_VAL"} \
                ${CG_ID_VAL:+counter-group-id="$CG_ID_VAL"} \
                ${CG_POLICY_VAL:+counter-group-policy="$CG_POLICY_VAL"} || exit 1
        fi

        declare -a SEEN_SLOTS=()
        for slot in "${PARAM_SET_SLOTS[@]}"; do
            seen=0
            for s in "${SEEN_SLOTS[@]}"; do
                [[ "$s" == "$slot" ]] && seen=1 && break
            done
            [[ $seen -eq 0 ]] && SEEN_SLOTS+=("$slot")
        done

        for slot in "${SEEN_SLOTS[@]}"; do
            declare -a slot_pairs=()
            count=0
            for i in "${!PARAM_SET_IDS[@]}"; do
                if [[ "${PARAM_SET_SLOTS[$i]}" == "$slot" ]]; then
                    slot_pairs+=("${PARAM_SET_IDS[$i]}" "${PARAM_SET_VALUES[$i]}")
                    count=$((count + 1))
                fi
            done

            if [[ $count -eq 1 ]]; then
                set_one_param "$slot" "${slot_pairs[0]}" "${slot_pairs[1]}" || exit 1
                echo "value=${slot_pairs[1]}"
            elif [[ $count -gt 1 ]]; then
                set_params_bulk_preserving_existing "$slot" "${slot_pairs[@]}" || exit 1
            fi
        done
        ;;

    *)
        die "unsupported operation '${OP}'" ;;
esac
