#!/usr/bin/env bash
# author: joe.zheng
# version: 23.11.18

DEMO_SPEED="${DEMO_SPEED:-4}"    # input speed [0 - 9]
DEMO_PS="${DEMO_PS:-\u@\h:\w$ }" # PS1 for demo

DEMO_WAIT="on"                   # wait on each command
DEMO_DELAY="on"                  # delay to simulate user input
DEMO_DELAY_VALUE=( 0.50 0.25 0.12 0.10 0.07 0.05 0.03 0.02 0.01 0.005 )

# show message only
function msg {
  :
}

function demo_speed {
  DEMO_SPEED="${1:-$DEMO_SPEED}"

  local count="${#DEMO_DELAY_VALUE[@]}"
  if (( $DEMO_SPEED > $count )); then
    DEMO_SPEED="$(( count - 1 ))"
  fi
}

# simulate user input
function demo_input {
  local s="$*"
  if [[ $DEMO_DELAY == 'on' ]]; then
    local d="${DEMO_DELAY_VALUE[$DEMO_SPEED]}"
    for ((i=0; i<${#s}; i++)); do
      local c="${s:$i:1}"
      echo -n "$c"
      sleep $d
      if [[ $c == ' ' || $c == ',' || $c == '.' ]]; then
        sleep $d
      elif [[ $((RANDOM % 5 )) == 1 ]]; then
        sleep $d
      fi
    done
  else
    echo -n "$s"
  fi
}

function demo_prompt {
  local p=$(PS1="$DEMO_PS" $BASH --norc -i </dev/null 2>&1)
  echo -n "${p%exit}"
}

# hook every command
function demo_hook {
  [[ $DEMO_MODE == 'off' ]] && return

  local cmd=${BASH_COMMAND%% *}
  if [[ $cmd == demo_* ]]; then
    return # skip demo_* commands
  elif [[ $cmd == "msg" ]]; then
    local arg=$(eval echo "${BASH_COMMAND#* }") # unquote the string
    demo_prompt && demo_input "# $arg"
  else
    demo_prompt && demo_input "$BASH_COMMAND"
  fi
  if [[ $DEMO_WAIT == 'on' ]]; then
    read
  else
    echo
  fi
}

function demo_eval {
  demo_prompt && demo_input "$*"
  if [[ $DEMO_WAIT == 'on' ]]; then
    read
  else
    echo
  fi
  local old_mode="$DEMO_MODE"
  DEMO_MODE="off"
  eval "$*"
  DEMO_MODE="$old_mode"
}

function demo_ctl {
  local opt="$1"
  if [[ $opt == 'init' ]]; then
    DEMO_MODE='on'
    DEMO_WAIT='on'
    demo_speed
    trap demo_hook DEBUG
  elif [[ $opt == 'on' ]]; then
    DEMO_MODE='on'
  elif [[ $opt == 'off' ]]; then
    DEMO_MODE='off'
  elif [[ $opt == 'wait' ]]; then
    if [[ $2 == 'off' ]]; then
      DEMO_WAIT='off'
    else
      DEMO_WAIT='on'
    fi
  elif [[ $opt == 'delay' ]]; then
    if [[ $2 == 'off' ]]; then
      DEMO_DELAY='off'
    else
      DEMO_DELAY='on'
    fi
  elif [[ $opt == 'speed' ]]; then
    demo_speed $2
  else
    echo "no such option: $opt"
    exit 1
  fi
}

function demo_init {
  demo_ctl init
}

function demo_self {
  local self="$(basename ${BASH_SOURCE[0]})"
  cat <<EOF
# $self enables you to script your demo without typing as you present.

# Using $self is incredibly easy!
# Add the following lines to your demo (bash) script:

PATH=\$(dirname \$0):\$PATH
source $(basename ${BASH_SOURCE[0]})

# You can configure it using environment variables, e.g.:

DEMO_SPEED=$DEMO_SPEED
DEMO_PS='$DEMO_PS'

# Start the demo mode with the command:

demo_init

# You can display information to users using the 'msg' function
# and adjust demo options using the 'demo_ctl' function.
# Here are some examples, check the source code for more details.

msg "Display a message to the user"  # Press ENTER to continue
demo_ctl wait off                    # Turn off waiting on each command

# The self-demo will start now. Press ENTER to continue...
EOF
  read

  local speed="$DEMO_SPEED"

  # configure the demo first
  DEMO_PS="> demo\$ "
  # start the demo
  demo_init

  msg "Message to the user, press ENTER to continue"
  msg "Message with a variable \$self: $self"

  msg "Run simple command directly"
  echo "Run the 'echo' command"
  pwd

  msg "Evaluate and execute complex command with 'demo_eval'"
  demo_eval 'for i in 1 2 3; do echo $i; done'

  msg "Turn off the demo mode with 'demo_ctl off'"
  demo_ctl off
  echo "The script continues to run, but you can't see the messages and the commands"
  msg "It's a secret, you can't see it"
  pwd
  echo "Enable the demo again with 'demo_ctl on'"
  demo_ctl on

  msg "Turn off the input delay with 'demo_ctl delay off'"
  demo_ctl delay off
  msg "This message is displayed instantly"
  msg "Enable the input delay again with 'demo_ctl delay on'"
  demo_ctl delay on

  msg "Adjust the input speed with 'demo_ctl speed LEVEL'"
  msg "The valid range for LEVEL is [0 - 9]"
  demo_ctl off
  for s in 0 3 6 9; do
    demo_ctl on
    demo_ctl speed $s
    msg "speed: $s"
    demo_ctl off
  done
  demo_ctl on
  msg "back to the default speed: $speed"
  demo_ctl speed $speed

  msg "You can create a subshell for interactive operation. Run 'exit' to quit"
  (exec $BASH)
  msg "You can also SSH to a remote server"
  msg "Disable waiting for confirmation on each command with 'demo_ctl wait off'"
  demo_ctl wait off
  msg "No waiting for user confirmation"
  msg "Continue to the end"
}

# if it is not sourced by other script show the self test demo
if [[ ${BASH_SOURCE[0]} == $0 ]]; then
  demo_self
fi
