#!/usr/bin/env bash

# This script generates a menu from the given list of items. Choosing an item runs the command for that item.
# Usage: 
#   change_my_password="Change my password%passwd"
#   list_users="List all users%cut -d: -f1 /etc/passwd"
#   exit="kill -2 $$"
#   menu "User management options:" "$change_my_password" "$list_users" "$exit"
# Use up/down arrow keys to select a menu item, then press Enter. Or cancel with ESC.

. colors

if [ ${#} -lt 2 ]; then
	main-menu
	exit 0
fi

description=$1
shift

# Parse args. Each arg is "name%command"
declare -a items
# declare -a descriptions # not displayed so presently disabled
declare -a commands
while [ ${#} -gt 0 ]; do
	IFS='%' read -r -a option <<< "$1"
    items+=("${option[0]}")
    #descriptions+=("$(option[1])") # not displayed so presently disabled
    commands+=("${option[1]}")
    shift
done

selected=0
displayed=0

# Calculate the position of the menu
menu_x=$((0))
menu_y=$(fathom-cursor -y)

# Calculate the number of rows that the menu will occupy
num_rows=${#items[@]}

# Calculate the maximum length of the item names
# Todo: abstract this out into a max-length function in another script. I gave up on trying to find an elegant way to pass a list of strings with spaces. Even separating them with newlines didn't work.
max_name_length=0

# Find the longest line
for line in "${items[@]}"; do
    line_length=${#line}
    if [ $line_length -gt $max_name_length ]; then
        max_name_length=$line_length
    fi
done

# Make sure the menu items don't wrap, multiline menu items are not supported
window_width=$(fathom-terminal -w)
if [ $max_name_length -gt $window_width ]; then
	max_name_length=$window_width
fi

# Define the function to display the menu
display_menu() {
  # Get the dimensions of the terminal window (again each time in case window is resized)
  window_width=$(fathom-terminal -w)
  window_height=$(fathom-terminal -h)
  
  # Recalculate the position of the menu
  menu_y=$(fathom-cursor -y)
  
  # Move the cursor to the correct position
  if [ $displayed -eq 0 ]; then
    # Don't move the cursor or clear the screen the first time
    displayed=1
  else
    if [ $((menu_y + num_rows)) -ge $window_height ]; then
    	menu_y=$((window_height))
    fi
    move-cursor $menu_x $((menu_y - num_rows)) 
  fi
  
  # Print the items and commands
  for i in "${!items[@]}"; do
  	# Truncate the command to the width of the screen # Todo: come up with a better solution that allows the user to see the full command.
  	max_command_length=$(( $window_width - $max_name_length - 3 )) # -3 is for the > and the space between item and command
  	truncated_command=$(echo "${commands[$i]}" | cut -c -$max_command_length)
    if [ $i -eq $selected ]; then
      printf "${CYAN}> %-${max_name_length}s${RESET} ${GREY}%s${RESET}\n" "${items[$i]}" "$truncated_command"
    else
    	# Print the menu item plus extra spaces to cover up its command, since it's not selected
      printf "  %-${max_name_length}s %*s\n" "${items[$i]}" "${#truncated_command}"
    fi
  done
}

# Define the function to handle key presses
handle_key() {
  key=$(await-keypress)
  case "$key" in
    up)
      selected=$((selected - 1))
      if [ $selected -lt 0 ]; then
        selected=$((num_rows - 1))
      fi
      ;;
    down)
      selected=$((selected + 1))
      if [ $selected -ge $num_rows ]; then
        selected=0
      fi
      ;;
    enter)
      cursor-blink on
      command="${commands[$selected]}"
      printf "\n"
      eval $command 
      exit 0
      ;;
    escape) 
      cursor-blink on
      echo ESC
      exit
      ;;
    esac
}

# Catch Ctrl-C and restore the cursor blink, so the user doesn't get stuck with an invisible cursor
handle_ctrl_c() {
  trap 'cursor-blink on; exit' INT
}

handle_ctrl_c

test_menu() {
  assert_equal "$(./menu "Please choose" "item1 item2 item3" "ls cat command3" -v)" "Please choose\n1) item1\n2) item2\n3) item3\n" "menu -v output differs"
  assert_equal "$(./menu "Please choose" "item1 item2 item3" "ls cat command3")" "1) item1\n2) item2\n3) item3\n" "menu output differs"
}

cursor-blink off

# Display the menu's description
if [ -n "$description" ]; then
	printf "$description\n"
fi

# Menu loop
while true; do
  display_menu
  handle_key $key
done