#!/bin/bash

set -euxo pipefail

# Define variables
HOST_CPU="$(uname -m)"
JOBS="$(nproc)"

# Functions
function _time {
  /bin/time -f '=> [%E]' ${@}
}

function _setup {
  if ! meson setup "${BUILD_DIR}"; then
    cat "${BUILD_DIR}/meson-logs/meson-log.txt"
    return 1
  fi
}

function _build {
  rm -f build.log
  if ! _time ninja -C "${BUILD_DIR}" -v -j "${JOBS}" >& build.log; then
    cat build.log
    return 1
  fi
  head -4 build.log
  tail -3 build.log
}

function _test {
  if ! _time meson test -C "${BUILD_DIR}" \
    -q --no-rebuild --print-errorlogs; then
    return 1
  fi
}

function _run_test {
  if ! _setup; then
    return 1
  fi

  if ! _build; then
    return 2
  fi

  if ! _test; then
    return 3
  fi
}

function _run_test_and_get_result {
  local exit_status=0
  local result="skipped"

  if [ "${#}" -lt 1 ]; then
    echo "Argument error" 2>&1
    exit 1
  fi
  local cond="${1}"

  if [ "${cond}" = "include" ] || [ "${cond}" = "pend" ]; then
    if _run_test; then
      if [ "${cond}" = "include" ]; then
        result="ok"
      else
        exit_status=1
        result="pending - passed unexpectedly"
      fi
    else
      if [ "${cond}" = "include" ]; then
        exit_status=1
        result="not ok"
      else
        result="pending - failed expectedly"
      fi
    fi
  fi

  # Use global variables to pass the return values as a limitation of Bash.
  result_buf="${result}"
  return "${exit_status}"
}

# Print system info.
cat /proc/cpuinfo
cat /proc/meminfo

# Set PATH for commands installed by `pip install`.
PATH="${PATH}:${HOME}/.local/bin"

# Install additional packages.
pip3 install -U meson

# Run test.

# Configuration
#
# If the value is true, the CI returns the exit status zero even if the tests
# fail as a compromised way.
IGNORE_EXIT_STATUS=

# Define the testing matrix.
# "include": Include the test in the matrix. Run the test.
# "exclude": Exclude the test. The test is not executed.
#            It can be used to save the total running time in CI.
# "pend"   : Run the test, but if the test fails, it passes as exit status zero.
#            If the test passes unexpectedly, it fails as exit status non-zero.
#
# Set the default behavior for each test.
MATRIX_DEFAULT_GCC_DEFAULT="include"
MATRIX_DEFAULT_GCC_O2="include"
MATRIX_DEFAULT_GCC_RPM="exclude"
MATRIX_DEFAULT_CLANG_DEFAULT="include"
MATRIX_DEFAULT_CLANG_O2="include"
MATRIX_DEFAULT_CLANG_RPM="exclude"
# Set the CPU specific behavior for each test optionally.
# This configuration is prioritized than the default behavior.

# End of configuration

# Generate the current CPU specific test conditions.
# If there is no CPU specific variable, set the default value.
TEST_COND_GCC_DEFAULT=$(eval echo \
  "\${MATRIX_${HOST_CPU}_GCC_DEFAULT:-${MATRIX_DEFAULT_GCC_DEFAULT}}")
TEST_COND_GCC_O2=$(eval echo \
  "\${MATRIX_${HOST_CPU}_GCC_O2:-${MATRIX_DEFAULT_GCC_O2}}")
TEST_COND_GCC_RPM=$(eval echo \
  "\${MATRIX_${HOST_CPU}_GCC_RPM:-${MATRIX_DEFAULT_GCC_RPM}}")
TEST_COND_CLANG_DEFAULT=$(eval echo \
  "\${MATRIX_${HOST_CPU}_CLANG_DEFAULT:-${MATRIX_DEFAULT_CLANG_DEFAULT}}")
TEST_COND_CLANG_O2=$(eval echo \
  "\${MATRIX_${HOST_CPU}_CLANG_O2:-${MATRIX_DEFAULT_CLANG_O2}}")
TEST_COND_CLANG_RPM=$(eval echo \
  "\${MATRIX_${HOST_CPU}_CLANG_RPM:-${MATRIX_DEFAULT_CLANG_RPM}}")

exit_status=0
# A temporary buffer to store the return value from a function.
result_buf=

# Print compiler versions.
gcc --version
g++ --version
clang --version
clang++ --version

echo "== Tests on gcc in a default status =="
if ! BUILD_DIR="build/gcc" CC="gcc" CXX="g++" \
  _run_test_and_get_result "${TEST_COND_GCC_DEFAULT}"; then
  exit_status=1
fi
result_gcc="${result_buf}"

echo "== Tests on clang in a default status =="
if ! BUILD_DIR="build/clang" CC="clang" CXX="clang++" \
  CFLAGS="--rtlib=compiler-rt" CXXFLAGS="--rtlib=compiler-rt" \
  _run_test_and_get_result "${TEST_COND_CLANG_DEFAULT}"; then
  exit_status=1
fi
result_clang="${result_buf}"

echo "== Tests on gcc with O2 flag =="
if ! BUILD_DIR="build/gcc-O2" CC="gcc" CXX="g++" \
  CFLAGS="-O2" CXXFLAGS="-O2" \
  _run_test_and_get_result "${TEST_COND_GCC_O2}"; then
  exit_status=1
fi
result_gcc_O2="${result_buf}"

echo "== Tests on clang with O2 flag =="
if ! BUILD_DIR="build/clang-O2" CC="clang" CXX="clang++" \
  CFLAGS="--rtlib=compiler-rt -O2" CXXFLAGS="--rtlib=compiler-rt -O2" \
  _run_test_and_get_result "${TEST_COND_CLANG_O2}"; then
  exit_status=1
fi
result_clang_O2="${result_buf}"

# This is an advanced test.
echo "== Tests on gcc with flags used in RPM package build =="
if ! BUILD_DIR="build/gcc-rpm" CC="gcc" CXX="g++" \
  CFLAGS="${CI_GCC_RPM_CFLAGS-}" CXXFLAGS="${CI_GCC_RPM_CXXFLAGS-}" \
  LDFLAGS="${CI_GCC_RPM_LDFLAGS-}" \
  _run_test_and_get_result "${TEST_COND_GCC_RPM}"; then
  exit_status=1
fi
result_gcc_rpm="${result_buf}"

# This is an advanced test.
echo "== Tests on clang with flags used in RPM package build =="
if ! BUILD_DIR="build/clang-rpm" CC="clang" CXX="clang++" \
  CFLAGS="${CI_CLANG_RPM_CFLAGS-}" CXXFLAGS="${CI_CLANG_RPM_CXXFLAGS-}" \
  LDFLAGS="${CI_CLANG_RPM_LDFLAGS-}" \
  _run_test_and_get_result "${TEST_COND_CLANG_RPM}"; then
  exit_status=1
fi
result_clang_rpm="${result_buf}"

# Print results.
cat <<EOF
== Results ==
Exit status: ${exit_status}
${result_gcc}: gcc without flags
${result_gcc_O2}: gcc with -O2 flag
${result_gcc_rpm}: gcc with RPM build flags
${result_clang}: clang without flags
${result_clang_O2}: clang with -O2 flag
${result_clang_rpm}: clang with RPM build flags
EOF

if [ "${IGNORE_EXIT_STATUS}" = true ]; then
  echo "warning: Exiting always with exit status zero is not ideal."
  exit 0
else
  exit ${exit_status}
fi
