# shell-script-helper - associative arrays in POSIX shell (with the help of grep & sed)
# Copyright (c) 2023 Raphaël Halimi <raphael.halimi@gmail.com>
# Licence: GPL-3+
# vim: ft=sh

#
# Functions
#

# Check if array name, key name or value is valid
# ARGS: STRING TYPE
# TYPE: array, key, value
aa_check_string () {
  local STRING TYPE RC
  STRING="$1"
  TYPE="$2"
  RC=0
  debug_var STRING TYPE
  case "$TYPE" in
    array|key)
      if ! check_word "$STRING" ; then
        print_message err "aa_check_string: invalid $TYPE name '$STRING' (only letters, digits and underscores allowed)"
        RC=1
      fi
      ;;
    value)
      if printf %s "$STRING" | grep -E -q "," ; then
        print_message err "aa_check_string: invalid value '$STRING' (',' not allowed)"
        RC=1
      fi
      ;;
    *)
      print_message err "aa_check_string: unknown type '$TYPE'"
      RC=1
      ;;
  esac
  return $RC
}

# Dump whole array
# ARGS: ARRAY_NAME
# Example: aa_dump "AA"
aa_dump () {
  local ARRAY
  if [ $# -lt 1 ] ; then ARRAY="DEFAULT" ; else ARRAY="$1" ; fi
  print_message debug stderr "aa_dump: dumping array '$ARRAY'"
  eval "printf \"%s\n\" \"\$$ARRAY\""
}

# List keys in array, one per line
# ARGS: ARRAY_NAME
# Example: aa_key_list "AA"
aa_key_list () {
  local ARRAY
  if [ $# -lt 1 ] ; then ARRAY="DEFAULT" ; else ARRAY="$1" ; fi
  print_message debug stderr "aa_key_list: listing keys in array '$ARRAY'"
  aa_dump "$ARRAY" | cut -d = -f 1
}

# Get the value of a key
# ARGS: ARRAY_NAME KEY
# Example: aa_key_get "AA" "KEY"
aa_key_get () {
  local ARRAY KEY VALUE
  if [ $# -lt 2 ] ; then ARRAY="DEFAULT" ; else ARRAY="$1" ; shift ; fi
  KEY="$1"
  if [ -n "$KEY" ] ; then
    print_message debug stderr "aa_key_get: get value for key '$KEY' in array '$ARRAY'"
    VALUE="$(aa_dump "$ARRAY" | sed -n -E "/^$KEY=/ { s/^$KEY=// ; p ; q }")"
    if [ -n "$VALUE" ] ; then
      printf %s "$VALUE"
      return 0
    else
      # Don't print an error, the empty response and the return code are sufficient
      return 1
    fi
  else
    print_message err "aa_key_get: empty key"
    return 1
  fi
}

# Remove a key from array
# ARGS: ARRAY_NAME KEY
# Example: aa_key_remove "AA" "KEY"
aa_key_remove () {
  local ARRAY DUMP KEY
  if [ $# -lt 2 ] ; then ARRAY="DEFAULT" ; else ARRAY="$1" ; shift ; fi
  KEY="$1"
  if [ -n "$KEY" ] ; then
    DUMP="$(aa_dump "$ARRAY")"
    if printf %s "$DUMP" | grep -E -q "^$KEY=.*$" ; then
      print_message debug "aa_key_remove: remove key '$KEY' from array '$ARRAY'"
      eval "$ARRAY=\"$(printf %s "$DUMP" | grep -E -v "^$KEY=.*$")\""
      return 0
    else
      print_message err "aa_key_remove: key '$KEY' not found in array '$ARRAY'"
      return 1
    fi
  else
    print_message err "aa_key_remove: empty key"
    return 1
  fi
}

# Add or modify key/value pair in array
# ARGS: ARRAY_NAME KEY VALUE
# Example: aa_key_set "AA" "KEY" "VALUE"
aa_key_set () {
  local ARRAY DUMP KEY VALUE ERROR
  if [ $# -lt 3 ] ; then ARRAY="DEFAULT" ; else ARRAY="$1" ; shift ; fi
  KEY="$1"
  VALUE="$2"
  ERROR=0

  # Check items validity
  if [ -z "$KEY" ] ; then
    ERROR=1 ; print_message err "aa_key_set: empty key"
  elif ( [ "$ARRAY" != "DEFAULT" ] && ! aa_check_string "$ARRAY" array ) ; then
    ERROR=1
  elif ! aa_check_string "$KEY" key ; then
    ERROR=1
  elif ! aa_check_string "$VALUE" value ; then
    ERROR=1
  fi

  # Set key
  if [ $ERROR -eq 0 ] ; then
    print_message debug "aa_key_set: passed checks"
    DUMP="$(aa_dump "$ARRAY")"
    if [ -z "$DUMP" ] ; then
      print_message debug "aa_key_set: create array '$ARRAY'"
      print_message debug "aa_key_set: set '$KEY=$VALUE' in array $ARRAY"
      eval "$ARRAY=\"$(printf %s=%s "$KEY" "$VALUE")\""
    else
      if printf %s "$DUMP" | grep -E -q "^$KEY=.*$" ; then
        print_message debug "aa_key_set: update '$KEY=$VALUE' in array '$ARRAY'"
        eval "$ARRAY=\"$(printf %s "$DUMP" | sed -E "s,^($KEY=).*$,\1$VALUE,")\""
      else
        print_message debug "aa_key_set: set '$KEY=$VALUE' in array '$ARRAY'"
        eval "$ARRAY=\"$(printf "%s\n%s=%s" "$DUMP" "$KEY" "$VALUE")\""
      fi
    fi
  else
    return 1
  fi
}
