#!/usr/bin/env bash

# heos-servlist  Copyright 2025-2026  Norman Carver

# Script program to Search the local Music Server (DLNA server) for Artists and Albums.
#
# Sends commands to the default HEOS master device, but can select alternative
# using the MASTER_IPNUM argument option.


# SID of "Local Music" music source:
sid_localmusic=1024
# SID of local DLNA Server (leave empty to have looked up):
sid_server=826833094  #Lyrion/Logitech Music Server
# CID of Artists sub on server:
cid_artists="/a"
# Make artist search case-insensitive:
shopt -qs nocasematch


function print_usage()
{
    echo    "Search the Local Music Server (DLNA server) for relevant artists, select one," >&2
    echo -e "see artist's albums, select one, have its tracks listed.\n" >&2
    echo    "Usage: heos-servlist ARTIST_SUBSTRING [MASTER_IPNUM]" >&2
    echo    "where:" >&2
    echo    "  ARTIST_SUBSTRING -- artist name substring to search for (case insensitive);" >&2
    echo    "  MASTER_IPNUM -- use a non-default HEOS master device;" >&2
    echo    "  (IPNUM means the last quad of an IP address only: i.e., 0--255.)\n" >&2
}


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

# Catch invalid supplied options:
debugopt=""
while [[ "$1" == -* ]]; do
    if [[ "$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
done

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

artiststr=$1
if [[ $# == 2 ]]; then
    master=${2}
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 necessary, determine Local Music SID from Music Sources:
if [[ -z "$sid_localmusic" ]]; then
    # Get Music Sources info so can get Local_Music 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\": \"Local Music\"*\"sid\":\ }
    sid_localmusic=${sid%%,*}
fi

# If necessary, determine local DLNA Server SID by browsing "Local Music" music source:
if [[ -z "$sid_server" ]]; then
    browse=$("$heosdir"/heosutils/heosutil-run-command "heos://browse/browse?sid=${sid_localmusic}")
    error_check_run_command browse "Error: determining Local Music SID failed!"
    sid=$(grep -Eo '"sid": [^}]+' <<<"$browse")
    sid_server="${sid#*:\ }"
fi

# Browse Artists on the Server, looping groups of artists and searching:
done=false
cnt=0
header=false
begin=0
end=99
declare -a artists_arr
while ! $done; do
    # Browse next group of artists:
    browse=$("$heosdir"/heosutils/heosutil-run-command --numjson=2 "heos://browse/browse?sid=${sid_server}&cid=${cid_artists}&range=${begin},${end}" $master)
    error_check_run_command_2json browse "Error: Browsing Local Music Server for Artist failed!"

    # Check if at end of artists:
    returned=$(grep -Eo 'returned=[0-9]+' <<<"$browse")
    returned=${returned#*=}
    if [[ $returned == 0 ]]; then break; fi
    if [[ $returned -lt 100 ]]; then done=true; fi

    # Use grep to make line-based list of the artists:
    artists=$(grep -Eo '\{"container": [^}]+\}' <<<"$browse")

    # Find matching artists and reformat to output:
    { while read line; do
          name=${line##*\"name\":\ \"}
          name=${name%%\",*}
          if [[ "$name" =~ $artiststr ]]; then
              cnt=$((cnt + 1))
              cid=${line#*\"cid\":\ \"}
              cid=${cid%%\",*}
              artists_arr[$cnt]="${cid}__${name}"
              if ! $header; then
                  echo "Matching Artists:"
                  header=true
              fi
              printf "%2d) %s\n" $cnt "$name"
          fi
      done 
    } <<<"$artists"

    begin=$((begin + 100))
    end=$((end + 100))
done

if [[ $cnt == 0 ]]; then
    echo "NO matching artists!"
    exit 0
fi

# Query for artist to use:
if [[ $cnt -gt 1 ]]; then
    echo;echo -n "Select Artist number (Return to Quit): "
    read num
    if [[ -z "$num" ]]; then
        exit 0;
    fi
    if [[ ! "$num" =~ ^[0-9]+$ || $num -le 0 || $num -gt $cnt ]]; then
        echo "Error: invalid Artist number!" >&2
        exit 1
    fi
else
    num=1
fi

artistinfo=${artists_arr[$num]}
cid_artist=${artistinfo[0]%__*}
artist=${artistinfo[0]#*__}

# Browse artist albums:
browse=$("$heosdir"/heosutils/heosutil-run-command --numjson=2 "heos://browse/browse?sid=${sid_server}&cid=${cid_artist}" $master)
error_check_run_command_2json browse "Error: Browsing Local Music Server for Artist's Albums failed!"

# Use grep to make line-based list of the artist's albums:
albumslist=$(grep -Eo '\{"container": [^}]+\}' <<<"$browse")

# Check if no albums returned (bad CID??):
if [[ -z "$albumslist" ]]; then
    echo;echo "Error: NO Albums found for Artist ('$artist')!" >&2
    exit 1
fi

# Reformat and output albums:
echo;echo "Albums by \"$artist\":"
cnt=0
declare -a albums_arr
{ while read line; do
      cnt=$((cnt + 1))
      name=${line##*\"name\":\ \"}
      name=${name%%\",*}
      cid=${line#*\"cid\":\ \"}
      cid=${cid%%\",*}
      albums_arr[$cnt]="${cid}__${name}"
      printf "%2d) %s\n" $cnt "$name"
  done 
} <<<"$albumslist"

# Query for album to use:
if [[ $cnt -gt 1 ]]; then
    echo;echo -n "Select Album number (Return to Quit): "
    read num
    if [[ -z "$num" ]]; then
        exit 0;
    fi
    if [[ ! "$num" =~ ^[0-9]+$ || $num -le 0 || $num -gt $cnt ]]; then
        echo "Error: invalid Album number!" >&2
        exit 1
    fi
else
    num=1
fi

albuminfo=${albums_arr[$num]}
cid_album=${albuminfo[0]%__*}
album=${albuminfo[0]#*__}

# Browse album tracks (action result in 2nd JSON object):
browse=$("$heosdir"/heosutils/heosutil-run-command --numjson=2 "heos://browse/browse?sid=${sid_server}&cid=${cid_album}" $master)
error_check_run_command_2json browse "Error: Browsing Local Music Server for Artist's Album Tracks failed!"

# Use grep to make line-based list of the artist's albums:
trackslist=$(grep -Eo '\{"container": [^}]+\}' <<<"$browse")

# Check if no tracks returned (bad CID??):
if [[ -z "$trackslist" ]]; then
    echo;echo "Error: NO Tracks found for Album ('$album')!" >&2
    exit 1
fi

# Reformat and output tracks:
echo;echo "Tracks in \"$album\":"
cnt=0
{ while read line; do
      cnt=$((cnt + 1))
      name=${line##*\"name\":\ \"}
      name=${name%%\",*}
      printf "%2d) %s\n" $cnt "$name"
  done 
} <<<"$trackslist"

#EOF
