#!/usr/bin/env bash

#======== Based on (then updated) https://raw.githubusercontent.com/tanhauhau/Inquirer.sh/master/dist/inquirer.sh ========

# License from: https://github.com/kahkhang/Inquirer.sh/blob/master/LICENSE

# The MIT License (MIT)

# Copyright (c) 2017

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# store the current set options
OLD_SET=$-
set -e

arrow="$(echo -e '\xe2\x9d\xaf')"
checked="$(echo -e '\xe2\x97\x89')"
unchecked="$(echo -e '\xe2\x97\xaf')"
down_arrow=$(echo -e '\u23f7')
up_arrow=$(echo -e '\u23f6')

black="$(tput setaf 0)"
red="$(tput setaf 1)"
green="$(tput setaf 2)"
yellow="$(tput setaf 3)"
blue="$(tput setaf 4)"
magenta="$(tput setaf 5)"
cyan="$(tput setaf 6)"
white="$(tput setaf 7)"
bold="$(tput bold)"
normal="$(tput sgr0)"
dim=$'\e[2m'

print() {
  echo "$1"
  tput el
}

join() {
  local IFS=$'\n'
  local _join_list
  eval _join_list=( '"${'${1}'[@]}"' )
  local first=true
  for item in ${_join_list[@]}; do
    if [ "$first" = true ]; then
      printf "%s" "$item"
      first=false
    else
      printf "${2-, }%s" "$item"
    fi
  done
}

function gen_env_from_options() {
  local IFS=$'\n'
  local _indices
  local _env_names
  local _checkbox_selected
  eval _indices=( '"${'${1}'[@]}"' )
  eval _env_names=( '"${'${2}'[@]}"' )

  for i in $(gen_index ${#_env_names[@]}); do
    _checkbox_selected[$i]=false
  done

  for i in ${_indices[@]}; do
    _checkbox_selected[$i]=true
  done

  for i in $(gen_index ${#_env_names[@]}); do
    printf "%s=%s\n" "${_env_names[$i]}" "${_checkbox_selected[$i]}"
  done
}

on_default() {
  true;
}

on_keypress() {
  local OLD_IFS
  local IFS
  local key
  OLD_IFS=$IFS
  local on_up=${1:-on_default}
  local on_down=${2:-on_default}
  local on_space=${3:-on_default}
  local on_enter=${4:-on_default}
  local on_left=${5:-on_default}
  local on_right=${6:-on_default}
  local on_ascii=${7:-on_default}
  local on_backspace=${8:-on_default}
  _break_keypress=false
  while IFS="" read -rsn1 key; do
      case "$key" in
      $'\x1b')
          read -rsn1 key
          if [[ "$key" == "[" ]]; then
              read -rsn1 key
              case "$key" in
              'A') eval $on_up;;
              'B') eval $on_down;;
              'D') eval $on_left;;
              'C') eval $on_right;;
              esac
          fi
          ;;
      ' ') eval $on_space ' ';;
      [a-z0-9A-Z\!\#\$\&\+\,\-\.\/\;\=\?\@\[\]\^\_\{\}\~]) eval $on_ascii $key;;
      $'\x7f') eval $on_backspace $key;;
      '') eval $on_enter $key;;
      esac
      if [ $_break_keypress = true ]; then
        break
      fi
  done
  IFS=$OLD_IFS
}

gen_index() {
  local k=$1
  local l=0
  if [ $k -gt 0 ]; then
    for l in $(seq $k)
    do
       echo "$l-1" | bc
    done
  fi
}

cleanup() {
  # Reset character attributes, make cursor visible, and restore
  # previous screen contents (if possible).
  tput sgr0
  tput cnorm
  stty echo

  # Restore `set e` option to its orignal value
  if [[ $OLD_SET =~ e ]]
  then set -e
  else set +e
  fi
}

control_c() {
  cleanup
  exit $?
}

select_indices() {
  local _select_list
  local _select_indices
  local _select_selected=()
  eval _select_list=( '"${'${1}'[@]}"' )
  eval _select_indices=( '"${'${2}'[@]}"' )
  local _select_var_name=$3
  eval $_select_var_name\=\(\)
  for i in $(gen_index ${#_select_indices[@]}); do
    eval $_select_var_name\+\=\(\""${_select_list[${_select_indices[$i]}]}"\"\)
  done
}

print_checkbox_line_arrow() {
  if [ "${_checkbox_selected[$1]}" = true ]; then
    printf "${cyan}${arrow}${green}${checked}${normal} ${_checkbox_list[$1]} ${normal}"
  else
    printf "${cyan}${arrow}${normal}${unchecked} ${_checkbox_list[$1]} ${normal}"
  fi
}

print_checkbox_line() {
  if [ "${_checkbox_selected[$1]}" = true ]; then
    printf " ${green}${checked}${normal} ${_checkbox_list[$1]} ${normal}"
  else
    printf " ${normal}${unchecked} ${_checkbox_list[$1]} ${normal}"
  fi
}

# https://www.gnu.org/software/termutils/manual/termutils-2.0/html_chapter/tput_1.html
# http://linuxcommand.org/lc3_adv_tput.php
on_checkbox_input_up2() {
  #remove_checkbox_instructions
  tput cub "$(tput cols)"

  if [ "${_checkbox_selected[$_current_index]}" = true ]; then
    printf " ${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}"
  else
    printf " ${unchecked} ${_checkbox_list[$_current_index]} ${normal}"
  fi
  tput el

  if [ $_current_index = 0 ]; then
    _current_index=$((${#_checkbox_list[@]}-1))
    tput cud $((${#_checkbox_list[@]}-1))
    tput cub "$(tput cols)"
  else
    _current_index=$((_current_index-1))

    tput cuu1 # Up one line
    tput cub "$(tput cols)" # Back to beginning (does this work?)
    tput el # Clear to end of line
  fi

  if [ "${_checkbox_selected[$_current_index]}" = true ]; then
    printf "${cyan}${arrow}${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}"
  else
    printf "${cyan}${arrow}${normal}${unchecked} ${_checkbox_list[$_current_index]} ${normal}"
  fi
}

on_checkbox_input_up() {
  #remove_checkbox_instructions
  tput cub "$(tput cols)"
  if (( ${_current_row} > 0 )) || (( ${#_checkbox_list[@]} <= 5 )); then
    print_checkbox_line $_current_index
    tput el
    if [ $_current_index = 0 ]; then
      _current_index=$((${#_checkbox_list[@]}-1))
      _current_row=4
      tput cud $((${#_checkbox_list[@]}-1))
      tput cub "$(tput cols)"
    else
      _current_index=$((_current_index-1))
      _current_row=$((_current_row-1))
      tput cuu1
      tput cub "$(tput cols)"
      tput el
    fi
    print_checkbox_line_arrow $_current_index
  else
    if [ $_current_index = 0 ]; then
      _current_index=$((${#_checkbox_list[@]}-1))
      _current_row=4
      tput cuu 1
      tput cub "$(tput cols)"
      tput el
      printf "  ${cyan}${up_arrow}${normal}"
      for I in 4 3 2 1; do
        tput cud1
        tput cub "$(tput cols)"
        tput el
        print_checkbox_line $((_current_index-I))
      done
      tput cud1
      tput cub "$(tput cols)"
      tput el
      print_checkbox_line_arrow $((_current_index))
      tput cud1
      tput cub "$(tput cols)"
      tput el
      printf "  ${dim}${down_arrow}${normal}"
      tput cuu 1
    else
      _current_index=$((_current_index-1))
      tput cud 5
      tput cub "$(tput cols)"
      tput el
      printf "  ${cyan}${down_arrow}${normal}"
      for I in 4 3 2 1; do
        tput cuu1
        tput cub "$(tput cols)"
        tput el
        print_checkbox_line $((_current_index+I))
      done
      tput cuu1
      tput cub "$(tput cols)"
      tput el
      print_checkbox_line_arrow $((_current_index))
      if [ $_current_index = 0 ]; then
        tput cuu1
        tput cub "$(tput cols)"
        tput el
        printf "  ${dim}${up_arrow}${normal}"
        tput cud1
      fi
    fi
  fi
}

on_checkbox_input_down() {
  #remove_checkbox_instructions
  tput cub "$(tput cols)"
  if (( ${_current_row} < 4 )) || (( ${#_checkbox_list[@]} <= 5 )); then
    print_checkbox_line $_current_index
    tput el
    if [ $_current_index = $((${#_checkbox_list[@]}-1)) ]; then
      _current_index=0
      _current_row=0
      tput cuu $((${#_checkbox_list[@]}-1))
      tput cub "$(tput cols)"
    else
      _current_index=$((_current_index+1))
      _current_row=$((_current_row+1))
      tput cud1
      tput cub "$(tput cols)"
      tput el
    fi
    print_checkbox_line_arrow $_current_index
  else
    if [ $_current_index = $((${#_checkbox_list[@]}-1)) ]; then
      _current_index=0
      _current_row=0
      tput cuu 5
      tput cub "$(tput cols)"
      tput el
      printf "  ${dim}${up_arrow}${normal}"
      tput cud1
      tput cub "$(tput cols)"
      tput el
      print_checkbox_line_arrow $((_current_index))
      for I in 1 2 3 4; do
        tput cud1
        tput cub "$(tput cols)"
        tput el
        print_checkbox_line $((_current_index+I))
      done
      tput cud1
      tput cub "$(tput cols)"
      tput el
      printf "  ${cyan}${down_arrow}${normal}"
      tput cuu 5
    else
      _current_index=$((_current_index+1))
      tput cuu 5
      tput cub "$(tput cols)"
      tput el
      printf "  ${cyan}${up_arrow}${normal}"
      for I in 4 3 2 1; do
        tput cud1
        tput cub "$(tput cols)"
        tput el
        print_checkbox_line $((_current_index-I))
      done
      tput cud1
      tput cub "$(tput cols)"
      tput el
      print_checkbox_line_arrow $((_current_index))
      if [ $_current_index = $((${#_checkbox_list[@]}-1)) ]; then
        tput cud1
        tput cub "$(tput cols)"
        tput el
        printf "  ${dim}${down_arrow}${normal}"
        tput cuu1
      fi
    fi
  fi
}

on_checkbox_input_enter() {
  remove_checkbox_instructions
  local OLD_IFS
  OLD_IFS=$IFS
  _checkbox_selected_indices=()
  _checkbox_selected_options=()
  IFS=$'\n'

  for i in $(gen_index ${#_checkbox_list[@]}); do
    if [ "${_checkbox_selected[$i]}" = true ]; then
      _checkbox_selected_indices+=($i)
      _checkbox_selected_options+=("${_checkbox_list[$i]}")
    fi
  done

  if (( ${#_checkbox_list[@]} <= 5 )); then
    tput cud $((${#_checkbox_list[@]}-${_current_index}))
    tput cub "$(tput cols)"
    for i in $(seq $((${#_checkbox_list[@]}+1))); do
      tput el1
      tput el
      tput cuu1
    done
  else
    tput cud $((6-${_current_row}))
    tput cub "$(tput cols)"
    for i in $(seq 8); do
      tput el1
      tput el
      tput cuu1
    done
  fi
  tput cub "$(tput cols)"

  tput cuf $((${#prompt}+3))
  printf "${cyan}$(join _checkbox_selected_options)${normal}"
  tput el

  tput cud1
  tput cub "$(tput cols)"
  tput el

  _break_keypress=true
  IFS=$OLD_IFS
}

on_checkbox_input_space() {
  #remove_checkbox_instructions
  tput cub "$(tput cols)"
  tput el
  if [ "${_checkbox_selected[$_current_index]}" = true ]; then
    _checkbox_selected[$_current_index]=false
  else
    _checkbox_selected[$_current_index]=true
  fi

  print_checkbox_line_arrow $_current_index
}

remove_checkbox_instructions() {
  if [ $_first_keystroke = true ]; then
    tput cuu $((${_current_index}+1))
    tput cub "$(tput cols)"
    tput cuf $((${#prompt}+3))
    tput el
    tput cud $((${_current_index}+1))
    _first_keystroke=false
  fi
}

# for vim movements
on_checkbox_input_ascii() {
  local key=$1
  case $key in
    "j" ) on_checkbox_input_down;;
    "k" ) on_checkbox_input_up;;
  esac
}

_checkbox_input() {
  local i
  local j
  prompt=$1
  eval _checkbox_list=( '"${'${2}'[@]}"' )
  _current_index=0
  _current_row=0
  _first_keystroke=true

  trap control_c SIGINT EXIT

  stty -echo
  tput civis

  print "${normal}${green}?${normal} ${bold}${prompt}${normal} ${dim}(Press <space> to select, <enter> to finalize)${normal}"

  for i in $(gen_index ${#_checkbox_list[@]}); do
    _checkbox_selected[$i]=false
  done

  if [ -n "$3" ]; then
    eval _selected_indices=( '"${'${3}'[@]}"' )
    for i in ${_selected_indices[@]}; do
      _checkbox_selected[$i]=true
    done
  fi

  if (( ${#_checkbox_list[@]} > 5 )); then
    tput cub "$(tput cols)"
    print "  ${dim}${up_arrow}${normal}"
  fi

  for i in $(gen_index ${#_checkbox_list[@]}); do
    tput cub "$(tput cols)"
    if [ $i = 0 ]; then
      print_checkbox_line_arrow $i
    else
      print_checkbox_line $i
    fi
    print ""
    tput el
    if (( $i > 3 )) && (( ${#_checkbox_list[@]} > 5 )); then
      print "  ${cyan}${down_arrow}${normal}"
      break
    fi
  done

  for j in $(gen_index ${#_checkbox_list[@]}); do
    tput cuu1
    if (( $j > 4 )); then
      break
    fi
  done

  on_keypress on_checkbox_input_up on_checkbox_input_down on_checkbox_input_space on_checkbox_input_enter on_default on_default on_checkbox_input_ascii
}

checkbox_input() {
  _checkbox_input "$1" "$2"
  _checkbox_input_output_var_name=$3
  select_indices _checkbox_list _checkbox_selected_indices $_checkbox_input_output_var_name

  unset _checkbox_list
  unset _break_keypress
  unset _first_keystroke
  unset _current_index
  unset _checkbox_input_output_var_name
  unset _checkbox_selected_indices
  unset _checkbox_selected_options

  cleanup
}

checkbox_input_indices() {
  _checkbox_input "$1" "$2" "$3"
  _checkbox_input_output_var_name=$3

  eval $_checkbox_input_output_var_name\=\(\)
  for i in $(gen_index ${#_checkbox_selected_indices[@]}); do
    eval $_checkbox_input_output_var_name\+\=\(${_checkbox_selected_indices[$i]}\)
  done

  unset _checkbox_list
  unset _break_keypress
  unset _first_keystroke
  unset _current_index
  unset _checkbox_input_output_var_name
  unset _checkbox_selected_indices
  unset _checkbox_selected_options

  cleanup
}




on_list_input_up() {
  remove_list_instructions
  tput cub "$(tput cols)"

  printf "  ${_list_options[$_list_selected_index]}"
  tput el

  if [ $_list_selected_index = 0 ]; then
    _list_selected_index=$((${#_list_options[@]}-1))
    tput cud $((${#_list_options[@]}-1))
    tput cub "$(tput cols)"
  else
    _list_selected_index=$((_list_selected_index-1))

    tput cuu1
    tput cub "$(tput cols)"
    tput el
  fi

  printf "${cyan}${arrow} %s ${normal}" "${_list_options[$_list_selected_index]}"
}

on_list_input_down() {
  remove_list_instructions
  tput cub "$(tput cols)"

  printf "  ${_list_options[$_list_selected_index]}"
  tput el

  if [ $_list_selected_index = $((${#_list_options[@]}-1)) ]; then
    _list_selected_index=0
    tput cuu $((${#_list_options[@]}-1))
    tput cub "$(tput cols)"
  else
    _list_selected_index=$((_list_selected_index+1))
    tput cud1
    tput cub "$(tput cols)"
    tput el
  fi
  printf "${cyan}${arrow} %s ${normal}" "${_list_options[$_list_selected_index]}"
}

on_list_input_enter_space() {
  local OLD_IFS
  OLD_IFS=$IFS
  IFS=$'\n'

  tput cud $((${#_list_options[@]}-${_list_selected_index}))
  tput cub "$(tput cols)"

  for i in $(seq $((${#_list_options[@]}+1))); do
    tput el1
    tput el
    tput cuu1
  done
  tput cub "$(tput cols)"

  tput cuf $((${#prompt}+3))
  printf "${cyan}${_list_options[$_list_selected_index]}${normal}"
  tput el

  tput cud1
  tput cub "$(tput cols)"
  tput el

  _break_keypress=true
  IFS=$OLD_IFS
}

remove_list_instructions() {
  if [ $_first_keystroke = true ]; then
    tput cuu $((${_list_selected_index}+1))
    tput cub "$(tput cols)"
    tput cuf $((${#prompt}+3))
    tput el
    tput cud $((${_list_selected_index}+1))
    _first_keystroke=false
  fi
}

_list_input() {
  local i
  local j
  prompt=$1
  eval _list_options=( '"${'${2}'[@]}"' )

  _list_selected_index=0
  _first_keystroke=true

  trap control_c SIGINT EXIT

  stty -echo
  tput civis

  print "${normal}${green}?${normal} ${bold}${prompt}${normal} ${dim}(Use arrow keys)${normal}"

  for i in $(gen_index ${#_list_options[@]}); do
    tput cub "$(tput cols)"
    if [ $i = 0 ]; then
      print "${cyan}${arrow} ${_list_options[$i]} ${normal}"
    else
      print "  ${_list_options[$i]}"
    fi
    tput el
  done

  for j in $(gen_index ${#_list_options[@]}); do
    tput cuu1
  done

  on_keypress on_list_input_up on_list_input_down on_list_input_enter_space on_list_input_enter_space

}


list_input() {
  _list_input "$1" "$2"
  local var_name=$3
  eval $var_name=\'"${_list_options[$_list_selected_index]}"\'
  unset _list_selected_index
  unset _list_options
  unset _break_keypress
  unset _first_keystroke

  cleanup
}

list_input_index() {
  _list_input "$1" "$2"
  local var_name=$3
  eval $var_name=\'"$_list_selected_index"\'
  unset _list_selected_index
  unset _list_options
  unset _break_keypress
  unset _first_keystroke

  cleanup
}




on_text_input_left() {
  remove_regex_failed
  if [ $_current_pos -gt 0 ]; then
    tput cub1
    _current_pos=$(($_current_pos-1))
  fi
}

on_text_input_right() {
  remove_regex_failed
  if [ $_current_pos -lt ${#_text_input} ]; then
    tput cuf1
    _current_pos=$(($_current_pos+1))
  fi
}

on_text_input_enter() {
  remove_regex_failed

  if [[ "$_text_input" =~ $_text_input_regex && "$(eval $_text_input_validator "$_text_input")" = true ]]; then
    tput cub "$(tput cols)"
    tput cuf $((${#_read_prompt}-19))
    printf "${cyan}${_text_input}${normal}"
    tput el
    tput cud1
    tput cub "$(tput cols)"
    tput el
    eval $var_name=\'"${_text_input}"\'
    _break_keypress=true
  else
    _text_input_regex_failed=true
    tput civis
    tput cud1
    tput cub "$(tput cols)"
    tput el
    printf "${red}>>${normal} $_text_input_regex_failed_msg"
    tput cuu1
    tput cub "$(tput cols)"
    tput cuf $((${#_read_prompt}-19))
    tput el
    _text_input=""
    _current_pos=0
    tput cnorm
  fi
}

on_text_input_ascii() {
  remove_regex_failed
  local c=$1

  if [ "$c" = '' ]; then
    c=' '
  fi

  local rest="${_text_input:$_current_pos}"
  _text_input="${_text_input:0:$_current_pos}$c$rest"
  _current_pos=$(($_current_pos+1))

  tput civis
  printf "$c$rest"
  tput el
  if [ ${#rest} -gt 0 ]; then
    tput cub ${#rest}
  fi
  tput cnorm
}

on_text_input_backspace() {
  remove_regex_failed
  if [ $_current_pos -gt 0 ]; then
    local start="${_text_input:0:$(($_current_pos-1))}"
    local rest="${_text_input:$_current_pos}"
    _current_pos=$(($_current_pos-1))
    tput cub 1
    tput el
    tput sc
    printf "$rest"
    tput rc
    _text_input="$start$rest"
  fi
}

remove_regex_failed() {
  if [ $_text_input_regex_failed = true ]; then
    _text_input_regex_failed=false
    tput sc
    tput cud1
    tput el1
    tput el
    tput rc
  fi
}

text_input_default_validator() {
  echo true;
}

text_input() {
  local prompt=$1
  local var_name=$2
  local _text_input_regex="${3:-"\.+"}"
  local _text_input_regex_failed_msg=${4:-"Input validation failed"}
  local _text_input_validator=${5:-text_input_default_validator}
  local _read_prompt_start=$'\e[32m?\e[39m\e[1m'
  local _read_prompt_end=$'\e[22m'
  local _read_prompt="$( echo "$_read_prompt_start ${prompt} $_read_prompt_end")"
  local _current_pos=0
  local _text_input_regex_failed=false
  local _text_input=""
  printf "$_read_prompt"


  trap control_c SIGINT EXIT

  stty -echo
  tput cnorm

  on_keypress on_default on_default on_text_input_ascii on_text_input_enter on_text_input_left on_text_input_right on_text_input_ascii on_text_input_backspace
  eval $var_name=\'"${_text_input}"\'

  cleanup
}

# =============================================================

function menuFromCmd() {
	local mLOCALRESULT=$1
	local mRESULT=''
	read -r -a ARRAY <<< $3
	list_input "$2" ARRAY mRESULT
	eval $mLOCALRESULT="'$mRESULT'"
}

function menuFromArr() {
	local mLOCALRESULT=$1
	shift
	local PROMPT=$1
	shift
	local ARRAY=("$@")
	list_input "$PROMPT" ARRAY mRESULT
	eval $mLOCALRESULT="'$mRESULT'"
}

function multiFromArr() {
	local mLOCALRESULT=$1
	shift
	local PROMPT=$1
	shift
	local ARRAY=("$@")
	checkbox_input "$PROMPT" ARRAY mRESULT
	eval $mLOCALRESULT="'$mRESULT'"
}
