#!/bin/sh

# Color codes ├──────────────────────────────────────────────────────────────────
none='\033[0m'; red='\033[31m'; green='\033[32m'; yellow='\033[33m'; magenta='\033[35m'; bold='\033[1m'

# Main ├────────────────────────────────────────────────────────────────────────

main() {
  kak_commands='
        set global autoreload yes
        set global autoinfo ""
        set global autocomplete ""
        try %{
            exec -save-regs / %{%s%\(\K[^)]+\)<ret>a<backspace><esc>i<backspace><backspace><c-u><esc><a-;>}
        } catch %{ exec gg }
        try %{
            eval -no-hooks -draft %{ edit -existing rc; delete-buffer }
            try %{ source rc } catch %{ echo -to-file stderr -end-of-line %val{error}; kill! 1 }
        }
        try %{ eval -no-hooks -draft %{ edit cmd; exec \%H"zy; delete-buffer } }
        hook global RuntimeError "\d+:\d+: (.+)" %{ echo -to-file stderr -end-of-line %val{hook_param_capture_1}; kill! 1 }
        exec -with-maps -with-hooks %reg{z}
        exec -with-hooks <c-l>
      '

  root="$PWD"
  testdir="$(dirname $0)"
  kak="$(realpath $testdir)/../src/kak"
  tmpdir="${TMPDIR:-/tmp}"
  work=$(mktemp -d $tmpdir/kak-tests.XXXXXXXX)
  session="kak-tests"
  if [ -n "$XDG_RUNTIME_DIR" ]; then
    session_path="${XDG_RUNTIME_DIR}/kakoune/$session"
  else
    session_path="${TMPDIR:-/tmp}/kakoune/${USER}/$session"
  fi
  trap "rm -Rf $work" EXIT

  number_tests=0
  number_failures=0
  for dir in $(find "${@:-$testdir}" -type d | sed 's|^\./||' | sort); do
    cd $root/$dir;
    if [ ! -f cmd ]; then
      echo "$dir"
      continue
    elif [ -x enabled ] && ! ./enabled >/dev/null 2>&1; then
      printf "${yellow}$dir (disabled)${none}\n"
      continue
    fi

    env_vars=$(ls -1 kak_* 2>/dev/null)
    number_tests=$(($number_tests + 1))
    mkdir -p $work/$dir
    cp in cmd rc env $work/$dir/ 2>/dev/null
    cd $work/$dir;

    mkfifo ui-in ui-out
    touch in; cp in out
    rm -f "$session_path"
    (
      if [ -f env ]; then
        . ./env
      fi
      exec $DEBUGGER $kak out -n -s "$session" -ui json -e "$kak_commands" >ui-out <ui-in
    ) &
    kakpid=$!

    failed=0
    exec 4<ui-out 3>ui-in

    if [ -f "${root}/${dir}/script" ]; then
      . "${root}/${dir}/script"
    else
      # At least wait for kak to initialize so we don't deadlock
      ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }'
    fi

    finished_commands |$kak -p "$session" 2>/dev/null
    cat <&4 >/dev/null

    wait $kakpid
    retval=$?

    exec 3>&- 4<&-

    if [ ! -e $root/$dir/error ]; then # failure not expected
      if [ $retval -ne 0 ]; then
        printf "${red}%s${none}\n" "$dir"
        echo "  Kakoune returned error $retval"
        [ -f stderr ] && printf "  ${yellow}%s${none}\n" "$(cat stderr)"
        failed=1
      else
        for file in out $env_vars; do
          if [ -f $root/$dir/$file ] && ! cmp -s $root/$dir/$file $file; then
            fail_ifn
            show_diff $root/$dir/$file $file
          fi
        done
        if [ $failed -ne 0 ] && [ -f debug ]; then
          printf "\n${yellow}debug buffer:${none}\n"
          cat debug
        fi
      fi
    else # failure expected
      if [ -f stderr ]; then
        sed -i -e 's/^[0-9]*:[0-9]*: //g' stderr
        if [ -s $root/$dir/error ] && ! cmp -s $root/$dir/error stderr; then
          printf "${yellow}%s${none}\n" "$dir"
          show_diff $root/$dir/error stderr
          failed=1
        fi
      elif [ $retval -eq 0 ]; then
        printf "${red}%s${none}\n" "$dir"
        echo "  Expected failure, but Kakoune returned 0"
        failed=1
      fi
    fi

    if [ $failed -eq 0 ]; then
      printf "${green}%s${none}\n" "$dir"
    else
      number_failures=$(($number_failures + 1))
    fi
  done

  if [ $number_failures -gt 0 ]; then
    color=$red
  else
    color=$green
  fi
  printf "\n${color}Summary: %s tests, %s failures${none}\n" $number_tests $number_failures
  exit $number_failures
}

# Utility ├─────────────────────────────────────────────────────────────────────

fail_ifn() {
  if [ $failed -eq 0 ]; then
    printf "${red}%s${none}\n" "$dir"
    failed=1
  fi
}

assert_eq() {
  if [ ! "$1" = "$2" ]; then
    fail_ifn
    if command -v git > /dev/null; then
        echo "$1" > expected
        echo "$2" > actual
        git --no-pager diff --color-words --no-index expected actual
    else
        printf "  ${red}- %s\n  ${green}+ %s${none}\n" "$1" "$2"
    fi
  fi
}

show_diff() {
  diff -u $1 $2 | while IFS='' read -r line; do
    first_character=$(printf '%s\n' "$line" | cut -b 1)
    case $first_character in
      +) color=$green ;;
      -) color=$red ;;
      @) color=$magenta ;;
      *) color=$none ;;
    esac
    printf "${color}%s${none}\n" "$line"
  done
}

finished_commands() {
  printf %s 'eval -client client0 %{
               eval -buffer *debug* write -force debug
            '
  for env_var in $env_vars; do
      case $env_var in
          kak_quoted_*) printf 'echo -to-file %s -end-of-line -quoting shell -- %s\n' "$env_var" "%val{${env_var##kak_quoted_}}" ;;
          kak_*) printf 'echo -to-file %s -end-of-line -- %s\n' "$env_var" "%val{${env_var##kak_}}" ;;
      esac
  done
  printf %s '
               write -force out
               quit!
             }
            '
}

# Script Assertions ├───────────────────────────────────────────────────────────

ui_in() {
  printf '%s\n' "$1" >&3
}

ui_out() {
  arg=$1
  shift
  case "$arg" in
  -ignore)
    skip_count=$1
    shift
    while [ $skip_count -gt 0 ]; do
      read -r event <&4
      skip_count=$(( skip_count - 1 ))
    done
    ;;
  -until)
    expected=$1
    shift
    while read -r event <&4; do
      [ "$event" = "$expected" ] && break
    done
    ;;
  -until-grep)
    pattern=$1
    shift
    while read -r event <&4; do
      if printf %s "$event" | grep "$pattern" >/dev/null; then
        if [ $# -ne 0 ]; then
            assert_eq "$1" "$event"
            shift
        fi
        break
      fi
    done
    ;;
  *)
    read -r event <&4
    assert_eq "$arg" "$event"
    ;;
  esac
}

main "$@"
