#!/usr/bin/env bash
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

if [ "${TESTPATCHDEBUG}" == "true" ] ; then
  set -x
fi

BASEDIR=$(pwd)
TASKNAME="SPOTBUGS_DIFF"
OP=""
TEMPDIR=${BASEDIR}/tmp
REPORTDIR=""
SUMMARYFILE=""
SUMMARYFILE_FULL=""
STDOUT="/dev/null"
MVNPASSTHRU=""
FINDBUGS_JAR=findbugs-diff-0.1.0-all.jar
FINDBUGS_JAR_URL=https://repo1.maven.org/maven2/me/andrz/findbugs/findbugs-diff/0.1.0/$FINDBUGS_JAR
# Priorities.HIGH_PRIORITY and Priorities.NORMAL_PRIORITY
SPOTBUGS_PRIORITY_THRESHOLD=2
# Scariest and scary
SPOTBUGS_RANK_THRESHOLD=9
SPOTBUGS_XML_NAME=spotbugsXml.xml
BUG_LIMIT_PER_PROJECT=5
FINDBUGS_DIFF_XML=findbugs-new.xml


cleanup_and_exit() {
  remove_file_if_present "${DIFF_DIR}/${FINDBUGS_JAR}"
  remove_file_if_present "${DIFF_DIR}/${FINDBUGS_JAR}.md5"
  exit "$1"
}


remove_file_if_present() {
  FILE_NAME=$1

  if [ -f "${FILE_NAME}" ]; then
    rm -f "${FILE_NAME}"
    echo "[TRACE] File [${FILE_NAME}] removed"
  fi
}

print_usage() {
  echo "Usage: $0 --taskname | (--op=pre|post|report --tempdir=<TEMP DIR> --reportdir=<REPORT DIR> --summaryfile=<SUMMARY FILE> --summaryfile-full=<FULL SUMMARY FILE>) [--verbose] [-D<VALUE>...] [-P<VALUE>...]"
  echo
}


parse_args() {
  for i in "$@"; do
    case $i in
    --taskname)
      echo ${TASKNAME}
      exit 0
      ;;
    --op=*)
      OP=${i#*=}
      ;;
    --tempdir=*)
      TEMPDIR=${i#*=}
      ;;
    --reportdir=*)
      REPORTDIR=${i#*=}
      ;;
    --summaryfile=*)
      SUMMARYFILE=${i#*=}
      ;;
    --summaryfile-full=*)
      SUMMARYFILE_FULL=${i#*=}
      ;;
    --verbose)
      STDOUT="/dev/stdout"
      ;;
    -D*)
      MVNPASSTHRU="${MVNPASSTHRU} $i"
      ;;
    -P*)
      MVNPASSTHRU="${MVNPASSTHRU} $i"
      ;;
    esac
  done
  if [[ "${TASKNAME}" == "" || "${OP}" == "" || "${TEMPDIR}" == "" || "${REPORTDIR}" == "" || "${SUMMARYFILE}" == "" || "${SUMMARYFILE_FULL}" == "" ]] ; then
    echo "Missing options"
    echo
    print_usage
    cleanup_and_exit 1
  fi
  if [[ "${OP}" != "pre" && "${OP}" != "post" && "${OP}" != "report" ]] ; then
    echo "Invalid operation"
    echo
    print_usage
    cleanup_and_exit 1
  fi
}


verify_and_save_spotbugs_output() {
  SPOTBUGS_OUTPUT_DIR=$1
  BRANCH_LABEL=$2
  REPORT_SUFFIX=$3

  echo "[TRACE] Verifying and saving SpotBugs output in ${BRANCH_LABEL}"
  echo "[TRACE] mvn clean compile test-compile spotbugs:check -Dspotbugs.failOnError=false -Dcheckstyle.failOnViolation=false -DskipTests ${MVNPASSTHRU} | tee ${REPORTDIR}/${TASKNAME}-${REPORT_SUFFIX}.txt >> ${STDOUT}"

  mvn clean compile test-compile spotbugs:check -Dspotbugs.failOnError=false -Dcheckstyle.failOnViolation=false -DskipTests ${MVNPASSTHRU} | tee "${REPORTDIR}/${TASKNAME}-${REPORT_SUFFIX}.txt" >> ${STDOUT}

  if [ ! -d "$SPOTBUGS_OUTPUT_DIR" ]; then
    mkdir -p "${SPOTBUGS_OUTPUT_DIR}"
  fi

  find . -name ${SPOTBUGS_XML_NAME} ! -path '*/test-patch/*' -exec rsync -avqR {} "${SPOTBUGS_OUTPUT_DIR}" ";"

  if [ -n "$(ls -A "${SPOTBUGS_OUTPUT_DIR}")" ] ; then
    echo "{color:green}+1{color} ${BRANCH_LABEL} produces SpotBugs output" >> "${TEMPDIR}/${TASKNAME}-${REPORT_SUFFIX}.txt"
  else
    echo "{color:red}-1{color} ${BRANCH_LABEL} does not produce SpotBugs output" >> "${TEMPDIR}/${TASKNAME}-${REPORT_SUFFIX}.txt"
  fi

  echo "[TRACE] SpotBugs output in ${BRANCH_LABEL} verified and saved"
}


create_missing_spotbugs_files() {
  PRE_DIR=$1
  POST_DIR=$2

  create_missing_xml "${PRE_DIR}" "pre" "post"
  create_missing_xml "${POST_DIR}" "post" "pre"
}

create_missing_xml() {
  sourceFolder=$1
  sourcePart=$2
  targetPart=$3

  sourceXmlFiles=()
  while IFS= read -r -d '' fn; do
    sourceXmlFiles+=("${fn}")
  done <   <(find "${sourceFolder}" -name ${SPOTBUGS_XML_NAME} -print0)

  for index in "${!sourceXmlFiles[@]}"; do
    targetXmlFile=${sourceXmlFiles[index]/${sourcePart}/${targetPart}}
    targetFolder=${targetXmlFile%${SPOTBUGS_XML_NAME}}

    if [ ! -d "${targetFolder}" ]; then
      echo "[TRACE] Folder [${targetFolder}] does not exist, creating"
      mkdir -p "${targetFolder}"
    fi

    if [ ! -f "${targetXmlFile}" ]; then
      echo "[TRACE] XML file [${targetXmlFile}] does not exist, creating"
      echo "<BugCollection />" > "${targetXmlFile}"
    fi
  done
}


download_and_check_findbugs_diff_jar() {
  DIFF_DIR=$1
  BASH_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

  mkdir -p "${DIFF_DIR}"

  echo "[TRACE] Downloading FindBugs diff JAR from ${FINDBUGS_JAR_URL}"

  curl -Ls ${FINDBUGS_JAR_URL} > "${DIFF_DIR}/${FINDBUGS_JAR}"

  echo "[TRACE] FindBugs diff JAR downloaded"

  if hash md5 2>/dev/null; then
    echo "[TRACE] Calculating md5 content using md5"
    md5Content="$(md5 -q "${DIFF_DIR}/${FINDBUGS_JAR}")"
  elif hash md5sum 2>/dev/null; then
    echo "[TRACE] Calculating md5 content using md5sum"
    md5Content="$(md5sum "${DIFF_DIR}/${FINDBUGS_JAR}" | awk '{ print $1 }')"
  else
    echo "[ERROR] Neither md5 nor md5sum are present, cannot check FindBugs diff JAR"
    summary_both "{color:red}-1{color} Neither md5 nor md5sum are present, cannot check FindBugs diff JAR."
    cleanup_and_exit 1
  fi
  echo "${md5Content}" > "${DIFF_DIR}/${FINDBUGS_JAR}.md5"

  jarMd5DiffCount=$(diff "${BASH_DIR}"/${FINDBUGS_JAR}.md5 "${DIFF_DIR}"/${FINDBUGS_JAR}.md5 | wc -l)

  if [ "${jarMd5DiffCount}" -gt "0" ]; then
    echo "[TRACE] md5 hash of FindBugs diff jar is $(cat "${DIFF_DIR}"/${FINDBUGS_JAR}.md5)"
    echo "[ERROR] FindBugs diff JAR has a weird MD5 sum, rejecting"
    summary_both "{color:red}-1{color} FindBugs diff JAR has a weird MD5 sum, rejecting."
    cleanup_and_exit 1
  fi

  echo "[TRACE] FindBugs diff JAR checked, is safe to use"

}


perform_spotbugs_diffs() {
  PRE_DIR=$1
  POST_DIR=$2
  DIFF_DIR=$3

  echo "[TRACE] Performing SpotBugs diffs"

  sourceXmlFiles=()
  while IFS= read -r -d '' fn; do
    preSpotbugsDiffs+=("${fn}")
  done <   <(find "${PRE_DIR}" -name ${SPOTBUGS_XML_NAME} -print0)

  postSpotbugsDiffs=()
  while IFS= read -r -d '' fn; do
    postSpotbugsDiffs+=("${fn}")
  done <   <(find "${POST_DIR}" -name ${SPOTBUGS_XML_NAME} -print0)

  for index in "${!preSpotbugsDiffs[@]}"; do
    preSpotbugsDir=${preSpotbugsDiffs[index]}
    diffDirPostfix=${preSpotbugsDir/*${TASKNAME}/${TASKNAME}}
    diffDirPostfix=${diffDirPostfix:18}
    diffDirPostfix=${diffDirPostfix%%/target/spotbugs/$SPOTBUGS_XML_NAME}

    check_minimum_file_size "${preSpotbugsDiffs[index]}"
    check_minimum_file_size "${postSpotbugsDiffs[index]}"

    java -jar -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR "${DIFF_DIR}"/${FINDBUGS_JAR} --outDir="${DIFF_DIR}/${diffDirPostfix}" "${preSpotbugsDiffs[index]}" "${postSpotbugsDiffs[index]}"
  done

  echo "[TRACE] SpotBugs diffs performed"
}


check_minimum_file_size() {
  FILE_NAME=$1

  actualSize=$(wc -c < "${FILE_NAME}")
  minimumSize=10
  if [ "${actualSize}" -le "${minimumSize}" ]; then
    echo "[ERROR] File [${FILE_NAME}] is below minimum size (has only [${actualSize}] bytes), cannot perform SpotBugs diff"
    cleanup_and_exit 1
  fi
}


check_spotbugs_diffs_and_create_reports() {
  DIFF_DIR=$1
  REPORT=()
  REPORT_FULL=()

  echo "[TRACE] Checking SpotBugs diffs and creating reports"

  belowThresholdCount=0
  totalCount=0
  while IFS= read -r -d '' fn; do
    componentDir=${fn/*${TASKNAME}/${TASKNAME}}
    componentDir=${componentDir:19}
    htmlFileName=${componentDir%%.xml}.html
    componentDir=${componentDir%%/$FINDBUGS_DIFF_XML}

    xmlLintXPathCompatible=$(xmllint | grep -ce '\-\-xpath')
    if [ "${xmlLintXPathCompatible}" -eq "0" ]; then
      echo "[TRACE] Old XMLLib present, calling 'xmllint --shell' to get bug instance counts"
      newBugTotalCount=$(xmllint --shell "${fn}" <<< 'xpath count(/BugCollection/BugInstance)' | grep -oE '[^ ]+$')
      newBugBelowThresholdCount=$(xmllint --shell "${fn}" <<< 'xpath count(/BugCollection/BugInstance[@priority <= "${SPOTBUGS_PRIORITY_THRESHOLD}" or @rank <= "${SPOTBUGS_RANK_THRESHOLD}"])' | grep -oE '[^ ]+$')
    else
      echo "[TRACE] New XMLLib present, calling 'xmllint --xpath' to get bug instance counts"
      newBugTotalCount=$(xmllint --xpath "count(/BugCollection/BugInstance)" "${fn}")
      newBugBelowThresholdCount=$(xmllint --xpath "count(/BugCollection/BugInstance[@priority <= ${SPOTBUGS_PRIORITY_THRESHOLD} or @rank <= ${SPOTBUGS_RANK_THRESHOLD}])" "${fn}")
    fi

    belowThresholdCount=$((belowThresholdCount + newBugBelowThresholdCount))
    totalCount=$((totalCount + newBugTotalCount))

    if [ "${newBugBelowThresholdCount}" -gt "0" ]; then
      echo "[ERROR] There are [${newBugBelowThresholdCount}] new bugs found below threshold in [${componentDir}]."
      REPORT_FULL+=("{color:red}-1{color} There are [${newBugBelowThresholdCount}] new bugs found below threshold in [${componentDir}] that must be fixed.")
      if [ "${newBugBelowThresholdCount}" -gt "${BUG_LIMIT_PER_PROJECT}" ]; then
        REPORT+=("{color:red}-1{color} There are [${newBugBelowThresholdCount}] new bugs found below threshold in [${componentDir}] that must be fixed, listing only the first [${BUG_LIMIT_PER_PROJECT}] ones.")
      else
        REPORT+=("{color:red}-1{color} There are [${newBugBelowThresholdCount}] new bugs found below threshold in [${componentDir}] that must be fixed.")
      fi
      echo "[DEBUG] You can find the SpotBugs diff here (look for the red and orange ones): ${htmlFileName}"
      REPORT+=("You can find the SpotBugs diff here (look for the red and orange ones): ${htmlFileName}")
      REPORT_FULL+=("You can find the SpotBugs diff here (look for the red and orange ones): ${htmlFileName}")
      REPORT_FULL+=("The most important SpotBugs errors are:")
      if [ "${newBugBelowThresholdCount}" -gt "${BUG_LIMIT_PER_PROJECT}" ]; then
        REPORT+=("The top [${BUG_LIMIT_PER_PROJECT}] most important SpotBugs errors are:")
      else
        REPORT+=("The most important SpotBugs errors are:")
      fi

      lineCount=0
      while IFS= read -r line; do
        REPORT_FULL+=( "${line}" );
        if [ "${lineCount}" -lt "${BUG_LIMIT_PER_PROJECT}" ]; then
          REPORT+=( "${line}" );
        fi
        lineCount=$((lineCount + 1))
      done < <(echo cat "/BugCollection/BugInstance[@priority <= ${SPOTBUGS_PRIORITY_THRESHOLD} or @rank <= ${SPOTBUGS_RANK_THRESHOLD}]/SourceLine/Message/text() | /BugCollection/BugInstance[@priority <= 2 or @rank <= 9]/LongMessage/text()" \
      | xmllint --shell "${fn}" | grep -v '\-\-\-\-\-\-\-' | grep -v '/ >' | sed -e 'N;s/\(.*\)\n\(.*\)/\2: \1/')
    elif [ "${newBugTotalCount}" -gt "0" ]; then
      echo "[WARN] There are [${newBugTotalCount}] new bugs found in [${componentDir}]."
      REPORT+=("{color:orange}0{color} There are [${newBugTotalCount}] new bugs found in [${componentDir}] that would be nice to have fixed.")
      REPORT_FULL+=("{color:orange}0{color} There are [${newBugTotalCount}] new bugs found in [${componentDir}] that would be nice to have fixed.")
      echo "[DEBUG] You can find the SpotBugs diff here: ${htmlFileName}"
      REPORT+=("You can find the SpotBugs diff here: ${htmlFileName}")
      REPORT_FULL+=("You can find the SpotBugs diff here: ${htmlFileName}")
    else
      echo "[DEBUG] There are no new bugs found in [${componentDir}]."
      REPORT+=("{color:green}+1{color} There are no new bugs found in [${componentDir}].")
      REPORT_FULL+=("{color:green}+1{color} There are no new bugs found in [${componentDir}].")
    fi

  done <   <(find "${DIFF_DIR}" -name ${FINDBUGS_DIFF_XML} -print0)

  if [ "${belowThresholdCount}" -gt "0" ]; then
    echo "[ERROR] There are [${belowThresholdCount}] new bugs found below threshold in total that must be fixed."
    summary_both "{color:red}-1{color} There are [${belowThresholdCount}] new bugs found below threshold in total that must be fixed."
  elif [ "${totalCount}" -gt "0" ]; then
    echo "[WARN] There are [${totalCount}] new bugs found in total that would be nice to have fixed."
    summary_both "{color:orange}0{color} There are [${totalCount}] new bugs found in total that would be nice to have fixed."
  else
    echo "[INFO] There are no new bugs found totally]."
    summary_both "{color:green}+1{color} There are no new bugs found in total."
  fi

  for line in "${REPORT[@]}" ; do
    summary ".    ${line}"
  done

  for line in "${REPORT_FULL[@]}" ; do
    summary_full ".    ${line}"
  done

  echo "[TRACE] SpotBugs diffs checked and reports created"
}


summary() {
  LINE=$1
  echo "${LINE}" >> "${SUMMARYFILE}"
}


summary_full() {
  LINE=$1
  echo "${LINE}" >> "${SUMMARYFILE_FULL}"
}


summary_both() {
  LINE=$1
  echo "${LINE}" >> "${SUMMARYFILE}"
  echo "${LINE}" >> "${SUMMARYFILE_FULL}"
}


parse_args "$@"

case ${OP} in
  pre)
    verify_and_save_spotbugs_output "${TEMPDIR}/${TASKNAME}/pre" HEAD clean
    ;;
  post)
    verify_and_save_spotbugs_output "${TEMPDIR}/${TASKNAME}/post" PATCH patch
    ;;
  report)
    create_missing_spotbugs_files "${TEMPDIR}/${TASKNAME}/pre" "${TEMPDIR}/${TASKNAME}/post"
    download_and_check_findbugs_diff_jar "${TEMPDIR}/${TASKNAME}/diff"
    perform_spotbugs_diffs "${TEMPDIR}/${TASKNAME}/pre" "${TEMPDIR}/${TASKNAME}/post" "${TEMPDIR}/${TASKNAME}/diff"
    check_spotbugs_diffs_and_create_reports "${TEMPDIR}/${TASKNAME}/diff"
    echo "[TRACE] Summary file size is $(wc -c "${SUMMARYFILE}" | awk '{print $1}') bytes"
    echo "[TRACE] Full summary file size is $(wc -c "${SUMMARYFILE_FULL}" | awk '{print $1}') bytes"
    ;;
esac

cleanup_and_exit 0
