#!/bin/bash

### Usage info
function show_help {
cat << EOF
Usage: ${0##*/} [-hvl] [-e/d TEST] [TEST/TESTDIR]
Without arguments the script runs all enabled tests.
When a test name is given then run this test.
When a directory is given it runs all tests in that directory.

-h         Display this help and exit
-e/d TEST  Enable/Diasble the specified test.
-l         List all tests and their status.
-r         Run all not enabled tests.
-v         Verbose mode. Can be used multiple times for increased
           verbotisty.
-c         Use C++ compiler/runtime system.
-i         Use the rail interpreter.
-a         Generate an AST file with the c++ rail compiler and read
           it into the haskell compiler. When using -ca the haskell
           compiler generates the AST and the cpp-compiler reads that AST.
-o FILE    Write expected outputs and actual outputs to FILE.exp, 
           FILE.exp.err, FILE and FILE.err.
           Don't do the actual output comparision here.
EOF
}

### Function for reading in-/output files
function readtest {
  unset STDIN
  unset STDOUT
  unset STDERR

  FILE=$1
  i=0
  # 0=STDIN, 1=STDOUT, 2=STDERR
  mode=0

  while read -r line; do
    if [ "$line" = "#" ]; then
      # Next test case OR the stdout/stderr section of a test case.
      if [ $mode -gt 0 ]; then
        # Next test case.
        i=$(($i + 1))
        mode=0
        continue
      fi

      # Else $mode is 0. This means we are now reading the
      # stdout/stderr section of a test case. It consists
      # of two sections (for stdout and stderr), delimited
      # by a line containg a single percent symbol (%). The second
      # section (for stderr) and its leading "percent symbol line"
      # are optional for backward compatibility.
      mode=1
      continue
    elif [ "$line" = "%" ]; then
      # Now comes the stderr section.
      mode=2
      continue
    fi

    # Else this is a normal input/output line.
    case "$mode" in
      0)
        STDIN[$i]="${STDIN[$i]}${line}"
        ;;
      1)
        STDOUT[$i]="${STDOUT[$i]}${line}"
        ;;
      2)
        STDERR[$i]="${STDERR[$i]}${line}"
        ;;
    esac
   done < "$FILE"

   UNIT_TESTCASES=$(($i + 1))
}

### Function to get the correct test name for a file.
function get_name {
  filename="${1##*/}"
  filename="${filename%%.*}"
  echo "$filename"
}

### Get the filename to a given test name
function get_filename {
  name="$1"
  echo "$TESTDIR/$name.rail"
}

### Function to run a single test
function run_one {
  dontrun=false
  filename=$(get_name "$1")
  unset compilefail
  if [ -f "$TESTDIR/$filename$EXT" ]
    then
      readtest "$TESTDIR/$filename$EXT"
    else
      fail=$(($fail + 1))
      echo -e "`$red`ERROR`$NC` testing: \"$filename.rail\". $EXT-file is missing."
      return
  fi
  if [[ -n "$ast" ]]; then 
    return
  fi
  if [[ -z $cpp && -z $interpreter ]]; then
    # Run Haskell compiler and llvm-linker.
    errormsg=$(dist/build/RailCompiler/RailCompiler -c -i "$1" -o "$TMPDIR/$filename.ll" 2>&1) \
      && errormsg2=$(llvm-link "$TMPDIR/$filename.ll" src/RailCompiler/*.ll 2>&1 > "$TMPDIR/$filename") \
      && chmod +x "$TMPDIR/$filename" || compilefail=true
  elif [ -n "$cpp" ]; then
    # Run C++ compiler.
    errormsg=$($CPPCOMPILER -q -i "$1" -o "$TMPDIR/$filename.class" 2>&1) || compilefail=true
  fi
  if [ -n "$compilefail" ]; then
    TOTAL_TESTCASES=$(($TOTAL_TESTCASES + 1))
    if [ -n "$fileoutput" ]; then
      echo "$filename" >> "${fileoutput}.tests"
      echo "$errormsg" >> "${fileoutput}.err"
      echo "${STDERR[0]}" >> "${fileoutput}.exp.err"
    elif [[ "$errormsg$errormsg2" == "${STDERR[0]}" ]]; then
      [ $verbose -gt 0 ] && echo -en "`$green`Passed`$NC` expected fail \"$filename.rail\"."
      if [ $verbose -gt 1 ]; then
        echo "  The error message was: \"$errormsg$errormsg2\""
      else
        [ $verbose -gt 0 ] && echo -ne "\n"
      fi
    else
      fail=$(($fail + 1))
      echo -e "`$red`ERROR`$NC` compiling/linking \"$filename.rail\" with error: \"$errormsg$errormsg2\""
    fi
    return
  fi
  # Create temporary files for stdout and stderr.
  stdoutfile=$(mktemp -t swp14_ci_stdout.XXXXX)
  if [ $? -gt 0 ]; then
    echo -e "`$red`ERROR`$NC` testing: \"$filename.rail\". Could not create temporary file for stdout."
    fail=$(($fail + 1))
    return
  fi

  stderrfile=$(mktemp -t swp14_ci_stderr.XXXXX)
  if [ $? -gt 0 ]; then
    echo -e "`$red`ERROR`$NC` testing: \"$filename.rail\". Could not create temporary file for stderr."
    fail=$(($fail + 1))
    return
  fi

  for i in $(seq 0 $(($UNIT_TESTCASES - 1))); do
    TOTAL_TESTCASES=$(($TOTAL_TESTCASES + 1))

    # Execute the test!
    echo -ne "${STDIN[$i]}" | run "$filename" 1>"$stdoutfile" 2>"$stderrfile"

    # Read stdout and stderr, while converting all actual newlines to \n.
    # Really ugly: bash command substitution eats trailing newlines so we
    # need to add a terminating character and then remove it again.
    stdout=$(cat "$stdoutfile"; echo x)
    stdout=${stdout%x}
    stdout=${stdout//$'\n'/\\n}

    stderr=$(cat "$stderrfile"; echo x)
    stderr=${stderr%x}
    stderr=${stderr//$'\n'/\\n}
    if [ -n "$fileoutput" ]; then
      echo "$filename - Input: \"${STDIN[$i]}\"" >> "${fileoutput}.tests"
      echo "$stdout" >> "$fileoutput"
      echo "$stderr" >> "${fileoutput}.err"
      echo "${STDOUT[$i]}" >> "${fileoutput}.exp"
      echo "${STDERR[$i]}" >> "${fileoutput}.exp.err"
    elif [[ "$stdout" == "${STDOUT[$i]}" && "$stderr" == "${STDERR[$i]}" ]]; then
      [ $verbose -gt 0 ] && echo -n "`$green`Passed`$NC` \"$filename.rail\" with input \"${STDIN[$i]}\""
	if [ $verbose -gt 1 ]; then
          echo "  Got output: \"$stdout\". Stderr: \"$stderr\"."
        else
          [ $verbose -gt 0 ] && echo -ne "\n"
        fi
    else
      fail=$(($fail + 1))
      echo "`$red`ERROR`$NC` testing \"$filename.rail\" with input \"${STDIN[$i]}\"!" \
        "Expected \"${STDOUT[$i]}\" on stdout, got \"$stdout\";" \
        "expected \"${STDERR[$i]}\" on stderr, got \"$stderr\"."
    fi
  done
}

### Function to compile and run all .rail files
function run_all {
  for f in "$TESTDIR"/*.rail; do
    if [ "$reverse" = true ]; then
      if [ ! -f "$TESTDIR/run/$(get_name "$f").rail" ]; then 
        run_one "$f"
      fi
    else
      run_one "$f"
    fi
  done
}

### Function to correctly call the LLVM/java interpreter/rail interpreter
function run {
  # On some platforms, the LLVM IR interpreter is not called "lli", but
  # something like "lli-x.y", where x.y is the LLVM version -- there may be
  # multiple such binaries for different LLVM versions.
  # Instead of trying to find the right version, we currently assume that
  # such platforms use binfmt_misc to execute LLVM IR files directly (e. g. Ubuntu).
  if [ -n "$cpp" ]; then
    java -cp "$TMPDIR/" "$@"
  elif [ -n "$interpreter" ]; then
    "$INTERPRETER" "$TESTDIR/$@.rail"
  else
    if command -v lli >/dev/null; then
        lli "$TMPDIR/$@"
    else
        "$TMPDIR/$@"
    fi
  fi
}


### Directory magic, so our cwd is the project home directory.
OLDDIR=$(pwd)
unset CDPATH
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
cd "$DIR/.."

### Define Terminal Colours
red="eval tput setaf 1; tput bold"
green="eval tput setaf 2; tput bold"
NC="tput sgr 0" # No Color

### Parse commandline options.
verbose=0
test=""    # The test to run.
enable=""  # The test to enable.
disable="" # The test to disable.

OPTIND=1
while getopts "hviclre:ad:o:" opt; do
  case "$opt" in
    h)
      show_help
      exit 0
      ;;
    v)
      verbose=$(($verbose + 1))
      ;;
    l)
      list=true
      ;;
    i)
      interpreter=true
      ;;
    c)
      cpp=true
      ;;
    a)
      ast=true
      ;;
    r)
      reverse=true
      ;;
    e)
      enable=$OPTARG
      ;;
    d)
      disable=$OPTARG
      ;;
    o)
      fileoutput=$OPTARG
      ;;
    '?')
      show_help >&2
      exit 1
      ;;
  esac
done
shift "$((OPTIND-1))" # Shift off the options and optional --.
test="$1"
# Set fileoutput to absolute path.
if [ -n "$fileoutput" ]; then 
  fileoutput="$OLDDIR/$fileoutput"
  if [ -f "$fileoutput" ]; then
    echo "Outpput file exists. Removing."
    rm "$fileoutput"{,.exp,.err,.exp.err}
  fi
fi

# -r implies -v
[[ -n $reverse && $verbose -eq 0 ]] && verbose=1

### Checking for incompatible options.
count=0
[[ -n $interpreter ]] && count=$(($count + 1))
[[ -n $cpp ]] && count=$(($count + 1))
[[ -n $list ]] && count=$(($count + 1))
[[ -n "$disable" ]] && count=$(($count + 1))
[[ -n "$enable" ]] && count=$(($count + 1))
if (( $count > 1 )); then
  echo "Only specify one of -l, -e, -d, -c, -i."
  exit 1
fi


### Main function.
TOTAL_TESTCASES=0
CPPCOMPILER=dist/build/RailCompiler/cppRail
INTERPRETER=dist/build/RailCompiler/rail_interpreter

if [ "$reverse" = true ]; then
  TESTDIR="integration-tests"
else
  TESTDIR="integration-tests/run"
fi
EXT=".io"
if [ -n "$disable" ];then
  rm "$TESTDIR"/"$disable".{rail,io}
  exit 0
fi
if [ -n "$enable" ];then
  ln -s -t "$TESTDIR" ../$enable.{rail,io}
  exit 0
fi
if [ -n "$list" ]; then
  echo -ne "`$green`Tests to run:`$NC`\n\n"
  for file in "$TESTDIR"/*.rail;do
    echo $(get_name $file)
  done
  echo -ne "\n\n`$red`Disabled tests:`$NC`\n\n"
  for file in "$TESTDIR"/../*.rail;do
    if [ ! -f "$TESTDIR"/`basename "$file"` ];then
      echo $(get_name $file)
    fi
  done
  exit 0
fi

export TMPDIR=tests/tmp
mkdir -p $TMPDIR

fail=0
if [ -n "$test" ]; then
  if [ -d "$test" ]; then # Use that directory as TESTDIR
    TESTDIR="$OLDDIR/$test"
    run_all
  else
    if [ "${test##*.}" == "rail" ]; then
      # Set the TESTDIR to the directory the .rail file is in.
      test="$OLDDIR"/"$test"
      TESTDIR=${test%/*}
    else
      TESTDIR="integration-tests"
      test=$(get_filename "$test") # Find the path to the specified test
    fi
  fi
  if [ -f "$test" ]; then
    run_one "$test"
  else
    echo "`$red`ERROR:`$NC` Test $test not found."
  fi
else
  run_all
fi

rm -rf tests/tmp

echo
echo "RAN $TOTAL_TESTCASES TESTCASES IN TOTAL."
if [ -n "$fileoutput" ]; then 
  echo "Written outputs to $fileoutput."
  exit 0
fi
if [ ! $fail -eq 0 ];then
  echo "`$red`FAILED`$NC` $fail test cases."
  exit 1
fi
echo "All testcases `$green`PASSED`$NC`."

### DEBUGGING:
function debugprint {
echo "STDIN"
for e in "${STDIN[@]}";do
  echo "$e"
done

echo "STDOUT"
for e in "${STDOUT[@]}";do
  echo "$e"
done

echo "STDERR"
for e in "${STDERR[@]}";do
  echo "$e"
done
}

#debugprint


# vim:ts=2 sw=2 et
