#!/usr/bin/bash

# heos-run-command  Copyright 2025  Norman Carver

# Script program to Send a HEOS Command to a HEOS controller.
# Uses the C socket program heosutil-send-command-topaddr, but
# this is the program level that determines the IP address
# for the HEOS controller.
#
# This program can also complete HEOS commands that involve a
# particular HEOS player, determining the Player ID (PID) of
# the player, and replacing '${pid}' in the command.
#
# By default, sends commands to the HEOS controller listed below, but
# can select alternative using the CONTROLLER_IPNUM argument option.


# LAN settings:
network=192.168.0.  #LAN network prefix
controllernum=151   #default HEOS controller host 


function print_usage()
{
    echo "Usage: heos-run-command [--player=PLAYER_NAME_OR_IPNUM [--group]] [--prettify] [--numjson=N] COMMAND [CONTROLLER_IPNUM]" >&2
    echo "(IPNUM means the last quad of an IP address only: i.e., 0--255)" >&2
}


if [[ "$1" == --help ]]; then
    print_usage
    exit 0
fi

# Options default:
player=false
group=false
pretty=false
numjson=1
errors=/dev/null

# Process supplied options:
if [[ "$1" == -D ]]; then
    errors=/dev/stderr
    shift
fi

if [[ "$1" =~ ^--player= ]]; then
    player=true
    playername=${1#--player=}
    shift

    if [[ "$1" = --group ]]; then
        group=true
        shift
    fi
fi

if [[ "$1" == --prettify ]]; then
    pretty=true
    shift
fi

if [[ "$1" =~ ^--numjson= ]]; then
    numjson=${1#--numjson=}
    shift
fi


# Process command arguments:
if [[ $# == 0 || $# -gt 2 ]]; then
    print_usage
    exit 1
fi

command=$1
if [[ $# == 2 ]]; then
    controllernum=${2}
    if [[ ! ("$controllernum" =~ ^[0-9]+$ && "$controllernum" -gt 0 && "$controllernum" -lt 255) ]]; then
        echo "Error: CONTROLLER_IPNUM must be 1--254 (is '$controllernum')" >&2
        exit 1
    fi
fi
controller=${network}${controllernum}
heosdir=$(dirname "$0")


# Validate controller:
result=$("$heosdir"/heosutil-send-command-toipaddr "heos://system/heart_beat" "$controller" 2>"$errors")
if [[ ($? != 0) || ("$result" != *'"result": "success"'*) ]]; then
    echo "Error: HEOS CONTROLLER invalid or not responding ('$controller')" >&2
    exit 1
fi


# If --player option supplied, get PID or GID::
if $player; then

    # not --group option, get player_id (PID):
    if ! $group; then
        players=$("$heosdir"/heosutil-send-command-toipaddr "heos://player/get_players" "$controller" 2>"$errors")
        if [[ ($? != 0) || ("$players" != *'"result": "success"'*) ]]; then
            echo "Error: failed getting HEOS Players list" >&2
            exit 1
        fi

        if [[ "$playername" =~ ^[0-9]+$ ]]; then
            playeripaddr=${network}${playername}
            matches=$(grep -Eio '\{[^{}]+"ip": "'"${playeripaddr}"'"[^{}]+\}' <<<"$players")
            if [[ $? != 0 ]]; then
                echo "Error: invalid player IPNUM: '$playername'" >&2
                exit 1
            fi
            matches=${matches#*\"pid\":\ }
            pid=${matches%%,*}
        else
            matches=$(grep -Eio '\{"name": "[^"]*'"${playername}"'[^"]*"[^{}]+\}' <<<"$players")
            if [[ $? != 0 ]]; then
                echo "Error: invalid player name: '$playername'" >&2
                exit 1
            fi
            matches=${matches#*\"pid\":\ }
            pid=${matches%%,*}
        fi

        # Complete command with ${pid}:
        eval command=$command

    else

        # --group option, get group_id (GID):
        groups=$("$heosdir"/heosutil-send-command-toipaddr "heos://group/get_groups" "$controller" 2>"$errors")
        if [[ ($? != 0) || ("$groups" != *'"result": "success"'*) ]]; then
            echo "Error: failed getting HEOS Groups list" >&2
            exit 1
        fi

        matches=$(grep -Eio '\{"name": "[^"]*'"${playername}"'[^"]*", "gid": -?[0-9]+' <<<"$groups")
        if [[ $? != 0 ]]; then
            echo "Error: invalid group name: '$playername'" >&2
            exit 1
        fi

        gid=$(grep -Eo -m1 '"gid": [0-9-]+' <<<"$matches")
        gid=${gid#\"gid\": }
        pid=$gid  #since a GID is identical to leader's PID, so this allows easy use of "player" commands

        # Complete command containing ${pid} or ${gid}:
        eval command=$command
    fi
fi


# Run command:
if ! $pretty; then
    "$heosdir"/heosutil-send-command-toipaddr "$command" "$controller" "$numjson" 2>"$errors"
    if [[ $? != 0 ]]; then
        echo "Error: HEOS CLI command ('$command') failed (should not happen, network issues?)" >&2
        exit 1
    fi

else
    result=$("$heosdir"/heosutil-send-command-toipaddr "heos://system/prettify_json_response?enable=on" "$controller" 2>"$errors")
    if [[ ($? != 0) || ("$result" != *'"result": "success"'*) ]]; then
        echo "ERROR: failed enabling JSON prettify mode" >&2
        exit 1
    fi
    "$heosdir"/heosutil-send-command-toipaddr "$command" "$controller" "$numjson" 2>"$errors"
    cmdstatus=$?
    result=$("$heosdir"/heosutil-send-command-toipaddr "heos://system/prettify_json_response?enable=off" "$controller" 2>"$errors")
    prettystatus=$?
    if [[ $cmdstatus != 0 ]]; then
        echo "Error: HEOS CLI command ('$command') failed (should not happen, network issues?)" >&2
        exit 1
    fi
    if [[ ($prettystatus != 0) || ("$result" != *'"result": "success"'*) ]]; then
        echo "ERROR: failed disabling JSON prettify mode" >&2
        exit 1
    fi
fi

exit 0

#EOF
