#!/bin/bash
set -euo pipefail


################################################################################
# Global variables & utility functions

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
readonly SCRIPT_DIR

# Currently the script only works if it is run in this directory.
# TODO Change this dirty workaround.
cd "$SCRIPT_DIR/.."

# Suffix that is appened on the Why3 excecutable name
SUFFIX=.opt

updateoracle=false
removeoutfile=true
debug=false
files=""
petiot=1
failed=""

function usage() {
  cat <<EOF
Usage: $(basename "$0") [OPTIONS...] <files>"

Run the check-ce tests on the given <files>.
<files> must be given without the '.mlw' suffix.
If <files> is empty, use all files from directory 'check-ce'.

Options:
  -h | -help | --help
    Prints this help text.

  --update-oracle
    Update the oracles during the run of tests suite.

  --keep-out-files
    Remove out files.

  --suffix <s>
    The suffix <s> is appended on the Why3 executable name, e.g. '.opt'.
    Default: $SUFFIX

  --debug
    Prints the why3 command used
EOF
}

################################################################################
# Parse options

while test $# != 0; do
  case "$1" in
    -h | -help | --help)
      usage
      exit 0
      ;;
    --update-oracle)
      updateoracle=true
      shift 1
      ;;
    --keep-out-files)
      removeoutfile=false
      shift 1
      ;;
    --suffix)
      SUFFIX="$2"
      shift 2
      ;;
    --debug)
      debug=true
      shift 1
      ;;
    "-"*)
      echo "unknown option: $1"
      usage
      exit 2
      ;;
    "petiot")
        files="$files "
        petiot=2
        shift 1
        ;;
    *)
	files="$files $1"
	if test "$petiot" = 1; then
            petiot=0
        fi
      shift 1
  esac
done

# If files is empty, use all files from directory 'check-ce'.
if test "$files" = "" ; then
  files="$SCRIPT_DIR/check-ce/*.mlw"
fi

readonly SUFFIX
readonly removeoutfile
readonly updateoracle
readonly files
readonly petiot

# Path to the why3 binary
WHY3_BIN="$SCRIPT_DIR"/../bin/why3"$SUFFIX"
whydata=$("$WHY3_BIN" --print-datadir)
whylib=$("$WHY3_BIN" --print-libdir)
readonly WHY3_BIN
readonly whydata
readonly whylib


colorize() {
  if command -v pygmentize &> /dev/null; then
    pygmentize -ldiff
  else
    cat
  fi
}

remove_solver_details () {
   sed -e "s|$whydata|WHY3DATA|g" -e "s|$whylib|WHY3LIB|g" \
   | sed 's/ ([0-9.]\+\.[0-9]\+s, / (/' \
#  | sed 's/ ([0-9.]\+\.[0-9]\+s, [0-9]\+ steps)\.$/\./' \
#  | sed 's/Prover result is: \(Timeout\.\|Unknown (\(unknown\|incomplete\|unknown + \(incomplete\|interrupted\)\))\.\|Out of memory.\|Step limit exceeded\.\|Step limit exceeded ([^)]*)\.\)$/Prover result is: Unknown or time\/memory\/step limit./'
}


record_time () {
    elapsed=$(date +%s%N)
}

# print elapsed time
elapsed_time () {
    t=$(date +%s%N)
    d=$(expr $t - $elapsed)
#    printf "d=${d}\n"
    l=$(expr length $d)
#    printf "l=${l}\n"
    lmn=$(expr $l - 9 || true)
    if [ "$lmn" = "0" ] ; then
	d=0"$d"
	lmn=1
    fi
#    printf "lmn=${lmn}\n"
    s=$(expr substr "${d}" 1 ${lmn} || true)
#    printf "s=${s}\n"
    c=$(expr substr "${d}" ${lmn} 2 || true)
#    printf "c=${c}\n"
    elapsed="$s"."$c"
#    printf "${elapsed}\n"
}




# $1 = prover
# $2 = dir
# $3 = filename
# $4 = true for WP, false for SP
# $5 = steps
# $6 = racsteps
run () {
  file_path="$2/$3"
  debug_vc_sp=""
  if $4; then
    f="${file_path}_$1_WP"
    oracle_file="$2/oracles/$3_$1_WP.oracle"
    printf "${file_path},WP,$1... "
  else
    f="${file_path}_$1_SP"
    oracle_file="$2/oracles/$3_$1_SP.oracle"
    debug_vc_sp=",vc_sp"
    printf "${file_path},SP,$1... "
  fi
  steps=$5
  racsteps=$6
  if [ "$3" = "strings" ] && [[ $1 == CVC* ]] ; then
      prover="$1,strings+counterexamples"
  else
    prover="$1,counterexamples"
  fi
  # one may add reduction_cont_size,stack_trace, in
  # the debug flags below to test cont_invariant in reduction engine
  # (note that stack trace may be more detailed with bytecode)
  record_time
  cmd="$WHY3_BIN prove -a split_vc -P $prover \
      --stepslimit=$steps --timelimit=5 \
      --rac-steplimit=$racsteps --rac-timelimit=2 \
      --check-ce --rac-prover=$1 --ce-log-verbosity=5 \
      --library=$2 ${file_path}.mlw \
      --debug=check_ce:categorization${debug_vc_sp}"
  record_time
  if $debug; then echo $cmd ; fi
  ($cmd || true) 2>&1 \
    | remove_solver_details \
	  > "$f.out"
  elapsed_time
  str_out=$(cat "$f.out")
  if [ -e "$oracle_file" ]; then
    str_oracle=$(cat "$oracle_file")
  else
    str_oracle=""
  fi
  if [ "$str_oracle" = "$str_out" ] ; then
    echo "OK (in ${elapsed}s)"
  else
    if $updateoracle; then
      echo "Updating oracle (in ${elapsed}s)"
      cp "$f.out" "${oracle_file}"
    else
      echo "FAILED"
      echo "diff is the following:"
	    (diff -u "$oracle_file" "$f.out" \
        || [ $? -eq 1 ])|colorize
      failed="$failed$f\n"
    fi
  fi
  if $removeoutfile; then
    rm "$f.out"
  fi
}



start=$(date +%s)
for file in $files; do
  filedir="$(realpath --relative-to="$PWD" $(dirname $file))"
  filebase=`basename $file .mlw`
    printf "# Running provers on $filedir/$filebase.mlw\n";
    altergosteps=10000
    altergostepsrac=$altergosteps
    skipae=no
    skipcvc4=no
    cvc4steps=150000
    cvc4stepsrac=$cvc4steps
    cvc5steps=150000
    cvc5stepsrac=$cvc5steps
    z3steps=3000000
    z3stepsrac=$z3steps
    case "$filebase" in
      "640_no_loc_failure")
          z3steps=500000
          z3stepsrac=$z3steps
          ;;
      "657")
          z3steps=500000
          z3stepsrac=$z3steps
          ;;
      "668_projection")
          z3steps=500000
          z3stepsrac=$z3steps
          ;;
      "703_reduce_term")
          altergosteps=1000
          altergostepsrac=$altergosteps
          z3steps=500000
          z3stepsrac=$z3steps
          ;;
      "algebraic_types_mono")
          altergosteps=1000
          altergostepsrac=$altergosteps
          ;;
      "algebraic_types_poly")
          skipae=yes
          altergosteps=1000
          altergostepsrac=$altergosteps
          ;;
      "anonymous3")
          altergosteps=1000
          altergostepsrac=$altergosteps
          ;;
      "anonymous5")
          z3steps=500000
          z3stepsrac=$z3steps
          ;;
      "array_records_poly")
          skipae=yes
          skipcvc4=yes
          ;;
      "array_records_mono")
          z3stepsrac=100
          z3stepsrac=$z3steps
          skipae=yes
          ;;
      "array_mono")
          skipae=yes
          z3steps=100000
          z3stepsrac=$z3steps
          ;;
      "bv32")
          z3steps=500000
          z3stepsrac=$z3steps
          ;;
      "bv32_mono")
          z3steps=100000
          z3stepsrac=$z3steps
          ;;
      "bv32_toBig")
          z3steps=100000
          z3stepsrac=$z3steps
          ;;
      "call-val-function")
          altergosteps=1000
          altergostepsrac=$altergosteps
          ;;
      "falseCE")
          skipae=yes
          altergosteps=7500
          altergostepsrac=$altergosteps
          ;;
      "float_div")
          skipae=yes
          altergosteps=1000
          altergostepsrac=$altergosteps
          ;;
      "floats")
          skipae=yes
          altergosteps=100
          altergostepsrac=$altergosteps
          cvc5stepsrac=10000
          z3steps=100000
          z3stepsrac=10000
          ;;
      "int_overflow")
          skipae=yes
          z3steps=400000
          z3stepsrac=$z3steps
          ;;
      "jlamp0_mono")
          skipae=yes
          ;;
      "jlamp0_poly")
          skipae=yes
          ;;
      "let_function_logic")
          altergosteps=1000
          altergostepsrac=$altergosteps
          ;;
      "lists")
          skipae=yes
          altergosteps=1000
          altergostepsrac=$altergosteps
          ;;
      "loop_inv_int_mono")
          skipae=yes
          ;;
      "loop_inv_real")
          z3steps=1000000
          z3stepsrac=$z3steps
          ;;
      "map_of_algebraic")
          z3steps=50000
          z3stepsrac=$z3steps
          ;;
      "maps_mono")
          altergosteps=1000
          altergostepsrac=$altergosteps
          cvc5steps=5000
          cvc5stepsrac=$cvc5steps
          ;;
      "maps_poly")
          cvc5steps=15000
          cvc5stepsrac=$cvc5steps
          ;;
      "strings")
          z3steps=3000
          z3stepsrac=$z3steps
          ;;
      "test_result_ce_value2")
          skipae=yes
          ;;
      "threshold")
          z3steps=500000
          z3stepsrac=$z3steps
          ;;
      "val_function")
          altergosteps=1000
          altergostepsrac=$altergosteps
          ;;
      "while_mono")
          skipae=yes
          ;;
      *)
          ;;
    esac
    if [ "$skipae" = "yes" ] ; then
          echo "Skipping unreproducible run of Alt-Ergo 2.6.0 on $filedir/$filebase.mlw";
    else
      run Alt-Ergo,2.6.0 $filedir $filebase true  $altergosteps $altergostepsrac
      run Alt-Ergo,2.6.0 $filedir $filebase false  $altergosteps $altergostepsrac
    fi
    if [ "$skipcvc4" = "yes" ] ; then
          echo "Skipping unreproducible run of CVC4 1.8 on $filedir/$filebase.mlw";
    else
        run CVC4,1.8       $filedir $filebase true  $cvc4steps $cvc4stepsrac
        run CVC4,1.8       $filedir $filebase false $cvc4steps $cvc4stepsrac
    fi
    run CVC5,1.0.5     $filedir $filebase true  $cvc5steps $cvc5stepsrac
    run CVC5,1.0.5     $filedir $filebase false $cvc5steps $cvc5stepsrac
    run Z3,4.8.10      $filedir $filebase true  $z3steps $z3stepsrac
    run Z3,4.8.10      $filedir $filebase false $z3steps $z3stepsrac
  # fi
done

if $updateoracle; then
  updatearg="--updateoracle"
else
  updatearg=""
fi

# Check the reproduction of the experiments by Petiot (2018).
# $1 = prover
# $2 = stepslimit
petiot () {
bench/check-ce/petiot2018/experiments.sh \
  --prover $1 --rac-prover $1 \
  --ce-prover $1,counterexamples --stepslimit $2 \
  $updatearg
if [ $? = 1 ]; then failed="${failed}Petiot experiments with $1.\n"; fi
}

if test "$petiot" -gt 0; then
    petiot Alt-Ergo,2.6.0  10000
    petiot Z3,4.8.10  500000
    petiot CVC4,1.8 10000
    petiot CVC5,1.0.5 10000
fi

end=$(date +%s)

runtime=$((end-start))
if [ "$failed" = "" ]; then
  echo "check-ce-bench: success (runtime = $runtime seconds)"
  exit 0
else
  printf "\ncheck-ce-bench: failed for the following files (runtime = $runtime seconds)\n$failed\n"
  exit 1
fi
