#!/usr/bin/env bash
VERSION="0.0.2"

# with tool
# usage: with <program>
# opens an interactive shell instance that automatically prefixes all subsequent commands with program name

# bind TAB to completion function within the script
bind -x '"\C-i": "with_completion"' &> /dev/null

print_version()
{
  echo "with, version $VERSION"
  exit 0
}

print_options()
{
  echo "  -h, --help   : Display command help"
  echo "  -v, --version: Display the currently installed version of with"
}

print_usage()
{
  print """
    USAGE:
      with <prefix>

    Prefix can be any string with a valid executable.
  """
  exit 0
}

#add options here, such as -h, -v
declare -a prefix
prefix=( "$@" )

case ${prefix[*]} in
  "" )
    echo "Missing arguments."
    echo "usage: with <program>"
    exit 1;;
  "-v"|"--version")
    print_version;;
  "-h"|"--help")
    print_help;;
  -*|--*)
    echo "Unrecognised option:" ${prefix[*]}
    print_options
    exit 1;;
esac

pmpt=${prefix[*]}

setup()
{

  # source bash completions
  [ -f /etc/bash_completion ] && source /etc/bash_completion

  BASH_COMPLETION_DEFAULT_DIR=/usr/share/bash-completion/completions
  for completion_file in $BASH_COMPLETION_DEFAULT_DIR/* $BASH_COMPLETION_COMPAT_DIR/*
  do
    . "$completion_file" &> /dev/null
  done

  # initialise history file
  touch /tmp/with_history

  # set up colour codes
  __blk="$(tput setaf 0)"
  __red="$(tput setaf 1)"
  __grn="$(tput setaf 2)"
  __yel="$(tput setaf 3)"
  __blu="$(tput setaf 4)"
  __mag="$(tput setaf 5)"
  __cyn="$(tput setaf 6)"
  __wht="$(tput setaf 7)"

  __bold_blk="$__bold$__blk"
  __bold_red="$__bold$__red"
  __bold_grn="$__bold$__grn"
  __bold_yel="$__bold$__yel"
  __bold_blu="$__bold$__blu"
  __bold_mag="$__bold$__mag"
  __bold_cyn="$__bold$__cyn"
  __bold_wht="$__bold$__wht"

  __on_blk="$(tput setab 0)"
  __on_red="$(tput setab 1)"
  __on_grn="$(tput setab 2)"
  __on_yel="$(tput setab 3)"
  __on_blu="$(tput setab 4)"
  __on_mag="$(tput setab 5)"
  __on_cyn="$(tput setab 6)"
  __on_wht="$(tput setab 7)"

  # color reset
  __nc="$(tput sgr0)"

  __blk() { echo -n "$__blk$*$__nc"; }
  __red() { echo -n "$__red$*$__nc"; }
  __grn() { echo -n "$__grn$*$__nc"; }
  __yel() { echo -n "$__yel$*$__nc"; }
  __blu() { echo -n "$__blu$*$__nc"; }
  __mag() { echo -n "$__mag$*$__nc"; }
  __cyn() { echo -n "$__cyn$*$__nc"; }
  __wht() { echo -n "$__wht$*$__nc"; }

  __bold_blk() { echo -n "$__bold_blk$*$__nc"; }
  __bold_red() { echo -n "$__bold_red$*$__nc"; }
  __bold_grn() { echo -n "$__bold_grn$*$__nc"; }
  __bold_yel() { echo -n "$__bold_yel$*$__nc"; }
  __bold_blu() { echo -n "$__bold_blu$*$__nc"; }
  __bold_mag() { echo -n "$__bold_mag$*$__nc"; }
  __bold_cyn() { echo -n "$__bold_cyn$*$__nc"; }
  __bold_wht() { echo -n "$__bold_wht$*$__nc"; }

  __on_blk() { echo -n "$__on_blk$*$__nc"; }
  __on_red() { echo -n "$__on_red$*$__nc"; }
  __on_grn() { echo -n "$__on_grn$*$__nc"; }
  __on_yel() { echo -n "$__on_yel$*$__nc"; }
  __on_blu() { echo -n "$__on_blu$*$__nc"; }
  __on_mag() { echo -n "$__on_mag$*$__nc"; }
  __on_cyn() { echo -n "$__on_cyn$*$__nc"; }
  __on_wht() { echo -n "$__on_wht$*$__nc"; }
}

__print_prompt() {
  __prefix="${prefix[*]}" print_prompt "$@"
}

print_prompt() {

  # TODO: change name to correct
  hashdollar() {
    (( UID )) && echo '$' \
              || echo '#'
  }

  colorise_prompt() {
    local to_be_replaced=(blk red grn yel blu mag cyn wht)

    local SED_COMMAND_LINE=('sed' '-E')

    for color in "${to_be_replaced[@]}"; do
      SED_COMMAND_LINE+=(
        '-e' "s/%on_$color%/$(eval echo "\$__on_$color")/g"
        '-e' "s/%bold_$color%/$(eval echo "\$__bold_$color")/g"
        '-e' "s/%$color%/$(eval echo "\$__$color")/g"
        )
    done

    "${SED_COMMAND_LINE[@]}" -e "s/%nc%/$__nc/g"
  }
  local __escaped_prefix=$(echo -n "$__prefix" | sed -e 's/\./\\./g' -e 's/\//\\\//g')
  echo -n "$*" | colorise_prompt | sed -E -e "s/%prefix%/$__escaped_prefix/g" \
                                          -e "s/\\$/$(hashdollar)/g"
}

with_completion()
{
  # print readline's prompt for visual separation
  if [ "$#" -eq 0 ]; then
      echo "$(__print_prompt "$PROMPT_FORMAT")$READLINE_LINE"
  fi

  # remove part after readline cursor from completion line
  local completion_line completion_word
  completion_line="${READLINE_LINE:0:READLINE_POINT}"
  completion_word="${completion_line##* }"

  # set completion cursor according to pmpt length
  COMP_POINT=$((${#pmpt}+${#completion_line}+1))
  COMP_WORDBREAKS="\n\"'><=;|&(:"
  COMP_LINE="$pmpt $completion_line"
  COMP_WORDS=($COMP_LINE)

  # TODO: the purpose of these variables is still unclear
  # COMP_TYPE=63
  # COMP_KEY=9

  # get index of word to be completed
  local whitespaces_count escaped_whitespaces_count
  whitespaces_count=$(echo "$COMP_LINE" | grep -o ' ' | wc -l)
  escaped_whitespaces_count=$(echo "$COMP_LINE" | grep -o '\\ ' | wc -l)
  COMP_CWORD=$((whitespaces_count-escaped_whitespaces_count))

  # get sourced completion command
  local program_name complete_command
  program_name=${COMP_WORDS[0]}
  program_name=$(basename "$program_name")
  complete_command=$(complete -p | grep " ${program_name}$")

  COMPREPLY=()

  # execute appropriate complete actions
  if [[ "$complete_command" =~  \ -F\  ]]
  then
    local complete_function
    complete_function=$(awk '{for(i=1;i<=NF;i++) if ($i=="-F") print $(i+1)}' <(echo "$complete_command"))

    # generate completions
    $complete_function
  else
    # construct compgen command
    local compgen_command
    compgen_command=$(echo "$complete_command" | sed 's/^complete/compgen/g')
    compgen_command="${compgen_command//$program_name/$completion_word}"

    # generate completions
    COMPREPLY=($($compgen_command))
  fi

  # get commmon prefix of available completions
  local completions_prefix readline_prefix readline_suffix
  completions_prefix=$(printf "%s\n" "${COMPREPLY[@]}" | \
    sed -e '$!{N;s/^\(.*\).*\n\1.*$/\1\n\1/;D;}' | xargs)
  readline_prefix="${READLINE_LINE:0:READLINE_POINT}"
  readline_suffix="${READLINE_LINE:READLINE_POINT}"
  # remove the word to be completed
  readline_prefix=$(sed s/'\w*$'// <(echo "$readline_prefix") | xargs)

  READLINE_LINE=""
  if [[ "$readline_prefix" != "" ]]; then
    READLINE_LINE="$readline_prefix "
  fi

  READLINE_LINE="$READLINE_LINE$completions_prefix"
  # adjust readline cursor position
  READLINE_POINT=$((${#READLINE_LINE}+1))

  if [[ "$readline_suffix" != "" ]]; then
    READLINE_LINE="$READLINE_LINE $readline_suffix"
  fi

  local completions_count display_all
  completions_count=${#COMPREPLY[@]}
  display_all="y"
  if [[ $completions_count -eq 1 ]]; then
    READLINE_LINE=$(echo "$READLINE_LINE" | xargs)
    READLINE_LINE="$READLINE_LINE "
    return
  elif [[ $completions_count -gt 80 ]]; then
    echo -en "Display all $completions_count possibilities? (y or n) "
    read -N 1 display_all
    echo "$display_all"
  fi

  if [[ "$display_all" = "y" ]]; then
    for completion in "${COMPREPLY[@]}"; do echo "$completion"; done | column
  fi
}

finish()
{
  # save history to bash history
  if [ -f ~/.bash_history ]; then
    cat /tmp/with_history >> ~/.bash_history
  fi
  rm /tmp/with_history
}

drop_with()
{
  if [ ${#prefix[@]} -gt 1 ]
  then
    prefix=( "${prefix[@]:0:${#prefix[@]}-1}" )
  else
    exit 0
  fi
}

add_with()
{
  # separate into white space
  # FIXME: foo "bar baz" should add two elements not one
  IFS=' ' read -r -a parse_array <<< "$@"
  prefix=( "${prefix[@]}" "${parse_array[@]}" )
}

run_with()
{
  while IFS="" read -r -e -d $'\n' -p "$(__print_prompt "$PROMPT_FORMAT")" options; do
    history -s "$options" > /dev/null 2>&1

    curr_command="$(echo "$options" | { read -r first rest ; echo "$first" ; })"
    case $curr_command in
      "" )
        # null case: run prefix
        ${prefix[*]} ;;
      !* )
        # replace current command
        drop_with
        parsed=${options#"!"}
        add_with "$parsed" ;;
      -* )
        # remove with
        parsed=${options#"-"}
        if [ -z "$parsed" ]; then
          drop_with
        else
          for ((x=0; x<$((parsed)); x++)) {
            drop_with
          }
        fi
        pmpt="${prefix[*]}" ;;
      +* )
        # nesting withs
        parsed=${options#"+"}
        add_with "$parsed"
        pmpt="${prefix[*]}" ;;
      :* )
        # shell command
        parsed=${options#":"}
        if [ "$parsed" = "q" ]; then
          exit 0
        fi
        IFS=' ' read -r -a parsed_array <<< "$parsed"
        echo "${parsed_array[@]}" >> /tmp/with_history
        eval "${parsed_array[@]}" ;;
      * )
        # prepend prefix to command
        echo "${prefix[*]} ${options}" >> /tmp/with_history
        eval "${prefix[*]} ${options}"
    esac
  done
}

main()
{
  trap finish exit
  [ "$PROMPT_FORMAT" ] || PROMPT_FORMAT+='%yel%$%nc% ' \
                        PROMPT_FORMAT+='%cyn%%prefix%%nc% ' \
                        PROMPT_FORMAT+='%wht%>%nc% '

  HISTCONTROL=ignoreboth

  # run script setup
  setup

  if [ "$1" == "" ]; then
    print_usage
  elif ! type "$1" > /dev/null 2>&1; then
    echo "error: \"$1\" is not a valid executable"
    exit 1
  fi

  while true ; do
    run_with
  done
}

main "$@"
