#!/usr/bin/bash

# heos-plplay  Copyright 2025-2026  Norman Carver

# Script program to Play a particular HEOS Playlist item on a HEOS player/group.
#
# A player is specified with:
# (1) a (sub)string of the player's HEOS name, or
# (2) its IP num (i.e., host portion of its dotted quad IP address).
#
# With the -g option, a group is specified:
# (1) a (sub)string of the group's HEOS name, or
# (2) its Group ID (GID).
#
# Sends commands to the default HEOS master device, but can select alternative
# using the MASTER_IPNUM argument option.


# Source ID (SID) for Playlists (from HEOS Protocol doc):
# (Set to empty string to cause it to be determined from Music Sources.)
sid_playlists=1025


function print_usage()
{
    echo    "Play a particular HEOS Playlists item on a HEOS Player/Group." >&2
    echo -e "(Item's contents replace player/group Queue and play is started.)\n" >&2
    echo    "Usage: heos-plplay [-g] [-a] [-v] PLAYER PLAYLISTS_ITEM [MASTER_IPNUM]" >&2
    echo    "where:" >&2
    echo    "  PLAYER -- a HEOS Player's Name (substring) or its IPNUM;" >&2
    echo    "  PLAYLISTS_ITEM -- the 1-based index number of an item in the Playlists list;" >&2
    echo    "  MASTER_IPNUM -- use a non-default HEOS master device;" >&2
    echo -e "  (IPNUM means the last quad of an IP address only: i.e., 0--255.)\n" >&2
    echo    "Options:" >&2
    echo    "  -g -- group: PLAYER is a group Name or GID;" >&2
    echo    "  -a -- add: playlist contents added to the Queue, and play is started;" >&2
    echo    "  -v -- verbose: print out name of PLAYLISTS_ITEM when started;" >&2
}


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

# Process supplied options:
pgopt=--player
aid=4  #replace queue
verbose=false
pidopt=""
debugopt=""
while [[ "$1" == -* ]]; do
    if [[ "$1" == -g ]]; then
        pgopt=--group
    elif [[ "$1" == -a ]]; then
        aid=1  #add to queue
    elif [[ "$1" == -v ]]; then
        verbose=true
    elif [[ "$1" == --pid=?* ]]; then
        pidopt=$1
    elif [[ "$1" == -- ]]; then
        shift;break
    elif [[ "$1" == --debug ]]; then
        debugopt=--debug
    else
        echo "Error: invalid option '$1'" >&2
        echo "(use '--' to signal end of options with -* arguments)" >&2
        exit 1 
    fi
    shift
done

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

player=$1
itemnum=$2
if [[ $# == 3 ]]; then
    master=${3}
else
    master=""
fi
heosdir=$(dirname "$0")


# heosutil-run-command error checking function (one json output):
# args: 1) command result variable; 2) error message; [3) error action]
# (default action on error is 'exit 1', use $3 of 'return 1' to continue execution)
function error_check_run_command()
{
    cmdresult=${!1}
    if [[ ($? != 0) || ("$cmdresult" != *'"result": "success"'*) ]]; then
        if [[ "$cmdresult" == Error:* || ($debugopt == --debug && "$cmdresult" == ERROR:*) ]]; then
            echo "$cmdresult" >&2
        fi
        if [[ -n "$2" ]]; then echo "$2" >&2; fi
        if [[ $# == 3 ]]; then $3; else exit 1; fi
    else
        return 0
    fi
}


# heosutil-run-command error checking function (two json outputs):
# args: 1) command result variable; 2) error message; [3) error action]
# (default action on error is 'exit 1', use $3 of 'return 1' to continue execution)
function error_check_run_command_2json()
{
    cmdresult=${!1}
    if [[ ($? != 0) || ("$cmdresult" != *'"result": "success"'*'"result": "success"'*) ]]; then
        if [[ "$cmdresult" == Error:* || ($debugopt == --debug && "$cmdresult" == ERROR:*) ]]; then
            echo "$cmdresult" >&2
        fi
        if [[ -n "$2" ]]; then echo "$2" >&2; fi
        if [[ $# == 3 ]]; then $3; else exit 1; fi
    else
        return 0
    fi
}

if [[ ! ("$itemnum" =~ ^[0-9]+$ && "$itemnum" -ge 1) ]]; then
    echo "Error: PLAYLISTS_ITEM must be an integer >= 1 ('$itemnum')!" >&2
    exit 1
fi


# Determine Playlists SID from Music Sources if necessary:
if [[ -z "$sid_playlists" ]]; then
    # Get Music Sources info so can get Plalists SID:
    sources=$("$heosdir"/heosutils/heosutil-run-command "heos://browse/get_music_sources" $master)
    error_check_run_command sources "Error: failed getting Music Sources listing!"

    sid=${sources#*\{\"name\": \"Playlists\"*\"sid\":\ }
    sid_playlists=${sid%%[,\}]*}
fi

# Get Playlists item (action result in 2nd JSON object):
itemindex=$((itemnum-1))
browse=$("$heosdir"/heosutils/heosutil-run-command --numjson=2 "heos://browse/browse?sid=${sid_playlists}&range=$itemindex,$itemindex" $master)
error_check_run_command_2json browse "Error: failed getting Playlists info!"

# Use grep to get item json:
itemjson=$(grep -Eo '\{[^}]*"container":[^}]+\}' <<<"$browse")
if [[ -z "$itemjson" ]]; then
    echo "Error: no PLAYLISTS_ITEM '$itemnum'!" >&2
    exit 1
fi

# Get PLAYLISTS_ITEM CID:
cid=${itemjson#*\"cid\":\ \"}
cid=${cid%%\"[,\}]*}

# Start PLAYLISTS_ITEM playing (action result in 2nd JSON object):
result=$("$heosdir"/heosutils/heosutil-run-command  ${pgopt}="$player" $pidopt --numjson=2 "heos://browse/add_to_queue?pid=\${pid}&sid=${sid_playlists}&cid=${cid}&aid=${aid}" $master)
error_check_run_command_2json result "Error: failed playing PLAYLISTS_ITEM ('$itemnum') on PLAYER ('$player')!"

# Check if need to print out PLAYLISTS_ITEM:
if $verbose; then
    name=${itemjson#*\"name\":\ \"}
    name=${name%%\"[,\}]*}
    case $aid in
        1) echo "Adding Playlist '$name' to Queue and playing..." ;;
        4) echo "Replacing Queue with Playlist '$name' and playing..." ;;
    esac
else
    case $aid in
        1) echo "Adding Playlist item #${itemnum} to Queue and playing..." ;;
        4) echo "Replacing Queue with Playlist item #${itemnum} and playing..." ;;
    esac
fi

#EOF
