#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.

# Make sure that bash version meets the pre-requisite

if [[ -z "${BASH_VERSINFO[0]}" ]] \
   || [[ "${BASH_VERSINFO[0]}" -lt 3 ]] \
   || [[ "${BASH_VERSINFO[0]}" -eq 3 && "${BASH_VERSINFO[1]}" -lt 2 ]]; then
  echo "bash v3.2+ is required. Sorry."
  exit 1
fi

this="${BASH_SOURCE-$0}"
BINDIR=$(cd -P -- "$(dirname -- "${this}")" >/dev/null && pwd -P)
#shellcheck disable=SC2034
QATESTMODE=false
STARTINGDIR=$(pwd)

# shellcheck disable=SC2317
## @description  disable function
## @stability    stable
## @audience     private
## @replaceable  no
function add_vote_table_v2
{
  true
}

# shellcheck disable=SC2317
## @description  disable function
## @stability    stable
## @audience     private
## @replaceable  no
function add_footer_table
{
  true
}

# shellcheck disable=SC2317
## @description  disable function
## @stability    stable
## @audience     private
## @replaceable  no
function big_console_header
{
  true
}

# shellcheck disable=SC2317
## @description  disable function
## @stability    stable
## @audience     private
## @replaceable  no
function add_test
{
  true
}

# shellcheck disable=SC2317
## @description  disable function
## @stability    stable
## @audience     private
## @replaceable  no
function bugsystem_finalreport
{
  true
}

## @description  Clean the filesystem as appropriate and then exit
## @audience     private
## @stability    evolving
## @replaceable  no
## @param        runresult
function cleanup_and_exit
{
  local result=$1

  if [[ ${PATCH_DIR} =~ ^/tmp/yetus
    && -d ${PATCH_DIR} ]]; then
    rm -rf "${PATCH_DIR}"
  fi

  # shellcheck disable=SC2086
  exit ${result}
}

## @description  Setup the default global variables
## @audience     public
## @stability    stable
## @replaceable  no
function setup_defaults
{
  common_defaults
  REPORTONLY=false
}

# shellcheck disable=SC2317
## @description  Print the usage information
## @audience     public
## @stability    stable
## @replaceable  no
function yetus_usage
{
  echo "smart-apply-patch.sh [OPTIONS] patch"
  echo ""
  echo "Where:"
  echo "  patch is a file, URL, or bugsystem-compatible location of the patch file"
  echo ""
  echo "Options:"
  echo ""
  yetus_add_option "--committer" "Apply patches like a boss."
  yetus_add_option "--debug" "If set, then output some extra stuff to stderr"
  yetus_add_option "--dry-run" "Check for patch viability without applying"
  yetus_add_option "--gpg-sign" "GPG sign the commit using gpg keys"
  yetus_add_option "--ignore-unknown-options=<bool>" "Continue despite unknown options (default: ${IGNORE_UNKNOWN_OPTIONS})"
  yetus_add_option "--list-plugins" "List all installed plug-ins and then exit"
  yetus_add_option "--modulelist=<list>" "Specify additional modules to test (comma delimited)"
  yetus_add_option "--offline" "Avoid connecting to the Internet"
  yetus_add_option "--patch-dir=<dir>" "The directory for working and output files (default '/tmp/yetus-(random))"
  yetus_add_option "--personality=<file>" "The personality file to load"
  yetus_add_option "--plugins=<list>" "Specify which plug-ins to add/delete (comma delimited; use 'all' for all found)"
  yetus_add_option "--project=<name>" "The short name for project currently using test-patch (default 'yetus')"
  yetus_add_option "--skip-system-plugins" "Do not load plugins from ${BINDIR}/plugins.d"
  yetus_add_option "--user-plugins=<dir>" "A directory of user provided plugins. (default ${USER_PLUGIN_DIR})"
  yetus_add_option "--version" "Print release version information and exit"
  yetus_generic_columnprinter "${YETUS_OPTION_USAGE[@]}"
  yetus_reset_usage

  echo ""
  echo "Shell binary overrides:"
  yetus_add_option "--awk-cmd=<cmd>" "The 'awk' command to use (default 'awk')"
  yetus_add_option "--curl-cmd=<cmd>" "The 'curl' command to use (default 'curl')"
  yetus_add_option "--diff-cmd=<cmd>" "The GNU-compatible 'diff' command to use (default 'diff')"
  yetus_add_option "--file-cmd=<cmd>" "The 'file' command to use (default 'file')"
  yetus_add_option "--git-cmd=<cmd>" "The 'git' command to use (default 'git')"
  yetus_add_option "--grep-cmd=<cmd>" "The 'grep' command to use (default 'grep')"
  yetus_add_option "--patch-cmd=<cmd>" "The 'patch' command to use (default 'patch')"
  yetus_add_option "--sed-cmd=<cmd>" "The 'sed' command to use (default 'sed')"
  yetus_generic_columnprinter "${YETUS_OPTION_USAGE[@]}"
  yetus_reset_usage

  echo ""
  echo "Patch reporting:"
  yetus_add_option "--build-tool=<tool>" "Override the build tool"
  yetus_add_option "--changedfilesreport=<name>" "List of files that this patch modifies"
  yetus_add_option "--changedmodulesreport=<name>" "List of modules that this patch modifies"
  yetus_add_option "--changedunionreport=<name>" "Union of modules that this patch modifies"
  yetus_add_option "--report-only" "Do not try to apply at all; just report on the patch"
  yetus_generic_columnprinter "${YETUS_OPTION_USAGE[@]}"
  yetus_reset_usage

  echo ""
  importplugins

  #shellcheck disable=SC2034
  BUILDTOOLS=()
  #shellcheck disable=SC2034
  TESTTYPES=()
  #shellcheck disable=SC2034
  TESTFORMATS=()

  for plugin in "${BUGSYSTEMS[@]}"; do
    if declare -f "${plugin}_usage" >/dev/null 2>&1; then
      echo ""
      echo "${plugin} plugin usage options:"
      "${plugin}_usage"
      yetus_generic_columnprinter "${YETUS_OPTION_USAGE[@]}"
      yetus_reset_usage
    fi
  done
}

## @description  Interpret the command line parameters
## @audience     private
## @stability    stable
## @replaceable  no
## @param        $@
## @return       May exit on failure
function parse_args
{
  local i

  common_args "$@"

  check_basedir_repo

  for i in "$@"; do
    case ${i} in
      --build-tool=*)
        delete_parameter "${i}"
        BUILDTOOL=${i#*=}
      ;;
      --committer)
        delete_parameter "${i}"
        COMMITMODE=true
      ;;
      --gpg-sign)
        delete_parameter "${i}"
        GPGSIGN=true
      ;;
      --dry-run)
        delete_parameter "${i}"
        PATCH_DRYRUNMODE=true
      ;;
      --changedfilesreport=*)
        delete_parameter "${i}"
        FILEREPORT=${i#*=}
      ;;
      --changedmodulesreport=*)
        delete_parameter "${i}"
        MODULEREPORT=${i#*=}
      ;;
      --changedunionreport=*)
        delete_parameter "${i}"
        UNIONREPORT=${i#*=}
      ;;
      --report-only)
        delete_parameter "${i}"
        REPORTONLY=true
      ;;
      --*)
        ## PATCH_OR_ISSUE can't be a --.  So this is probably
        ## a plugin thing.
        continue
      ;;
      *)
        PATCH_OR_ISSUE=${i#*=}
      ;;
    esac
  done

  if [[ ! -d ${PATCH_DIR} ]]; then
   if ! mkdir -p "${PATCH_DIR}"; then
      yetus_error "ERROR: Unable to create ${PATCH_DIR}"
      cleanup_and_exit 1
    fi
  fi

  # we need absolute dir for ${BASEDIR}
  cd "${STARTINGDIR}" || cleanup_and_exit 1
  BASEDIR=$(yetus_abs "${BASEDIR}")
}

## @description  generate reports on the given patch file
## @replaceable  no
## @audience     private
## @stability    evolving
function patch_reports
{
  declare i

  if [[ -z "${FILEREPORT}" ]] && [[ -z "${MODULEREPORT}" ]] && [[ -z "${UNIONREPORT}" ]]; then
    return
  fi

  find_changed_files

  if [[ -n "${FILEREPORT}" ]]; then
    printf "%b\n" "${CHANGED_FILES[@]}" > "${FILEREPORT}"
  fi

  if [[ -n "${MODULEREPORT}" ]] || [[ -n "${UNIONREPORT}" ]]; then
    if [[ -z "${BUILDTOOL}" ]]; then
      guess_build_tool
    fi

    unset -f "${BUILDTOOL}_reorder_modules"

    find_changed_modules

    if [[ -n "${MODULEREPORT}" ]]; then
      printf "%b\n" "${CHANGED_MODULES[@]}" > "${MODULEREPORT}"
    fi

    if [[ -n "${UNIONREPORT}" ]]; then
      cat "${CHANGED_UNION_MODULES}" > "${UNIONREPORT}"
    fi
  fi
}

# shellcheck disable=SC2317
## @description  git am dryrun
## @replaceable  no
## @audience     private
## @stability    evolving
function gitam_dryrun
{

  # there is no dryrun method for git-am, so just
  # use apply instead.
  gitapply_dryrun "$@"

  if [[ ${PATCH_METHOD} = "gitapply" ]]; then
    PATCH_METHOD="gitam"
  fi
}

# shellcheck disable=SC2317
## @description  git am signoff
## @replaceable  no
## @audience     private
## @stability    evolving
function gitam_apply
{
  declare patchfile=$1
  declare gpg=$2

  if [[ ${gpg} = true ]]; then
    EXTRA_ARGS+=("-S")
  fi

  echo "Applying the patch:"
  yetus_run_and_redirect "${PATCH_DIR}/apply-patch-git-am.log" \
    "${GIT}" am --signoff "${EXTRA_ARGS[@]}" --whitespace=fix "-p${PATCH_LEVEL}" "${patchfile}"
  RESULT=$?
  "${GREP}" -v "^Checking" "${PATCH_DIR}/apply-patch-git-am.log"

  # fallback
  if [[ ${RESULT} -gt 0 && ${PATCH_SYSTEM} == 'jira' ]]; then
    echo "Use git apply and commit with the information from jira."
    gitapply_and_commit "${patchfile}"
  fi
}

# shellcheck disable=SC2317
## @description  get author and summary from jira and commit it.
##               if the author and the summary contains " or *,
##               the function does not work correctly because
##               the characters are used for delimiters.
## @replaceable  no
## @audience     private
## @stability    evolving
function gitapply_and_commit
{
  declare patchfile=$1
  declare jsontmpfile
  declare assigneeline
  declare assigneefile
  declare name
  declare email
  declare author
  declare summary

  yetus_debug "gitapply_and_commit: fetching ${JIRA_URL}/rest/api/2/issue/${PATCH_OR_ISSUE}"
  if ! jira_http_fetch "rest/api/2/issue/${PATCH_OR_ISSUE}" "${PATCH_DIR}/issue"; then
    yetus_debug "gitapply_and_commit: not a JIRA."
    return 1
  fi

  jsontmpfile="${PATCH_DIR}/jsontmpfile"
  # cannot set " as delimiter for cut command in script, so replace " with *
  tr ',' '\n' < "${PATCH_DIR}/issue" | "${SED}" 's/\"/*/g' > "${jsontmpfile}"

  assigneeline=$("${GREP}" -n -E '^\*assignee\*:' "${jsontmpfile}" | cut -f1 -d":")
  assigneefile="${PATCH_DIR}/assigneefile"
  tail -n +"${assigneeline}" "${jsontmpfile}" | head -n 20 > "${assigneefile}"

  name=$("${GREP}" "displayName" "${assigneefile}" | cut -f4 -d"*")
  email=$("${GREP}" "emailAddress" "${assigneefile}" | cut -f4 -d"*" \
    | "${SED}" 's/ at /@/g' | "${SED}" 's/ dot /./g')
  author="${name} <${email}>"
  summary=$("${GREP}" -E '^\*summary\*:' "${jsontmpfile}" | cut -f4 -d"*")
  gitapply_apply "${patchfile}"
  "${GIT}" add --all
  echo "Committing with author: ${author}, summary: ${summary}"
  yetus_run_and_redirect "${PATCH_DIR}/apply-patch-git-am-fallback.log" \
    "${GIT}" commit "${EXTRA_ARGS[@]}" --signoff -m "${PATCH_OR_ISSUE}. ${summary}" \
    --author="${author}"
}

## @description import core library routines
## @audience private
## @stability evolving
function import_core
{
  declare filename

  for filename in "${BINDIR}/core.d"/*; do
    # shellcheck disable=SC1091
    # shellcheck source=SCRIPTDIR/core.d/01-common.sh
    . "${filename}"
  done
}

## @description setup the parameter tracker for param errors
## @audience    private
## @stability   evolving
function setup_parameter_tracker
{
  declare i

  for i in "${USER_PARAMS[@]}"; do
    if [[ "${i}" =~ ^-- ]]; then
      i=${i%=*}
      PARAMETER_TRACKER+=("${i}")
    fi
  done
}


# robots will change USER_PARAMS so must
# do this before importing other code
setup_parameter_tracker

import_core

yetus_set_trap_handler generic_signal_handler HUP INT QUIT TERM

setup_defaults

parse_args "$@"

importplugins
yetus_debug "Removing TESTTYPES and TESTFORMATS from installed plug-in list"
#shellcheck disable=SC2034
TESTTYPES=()
#shellcheck disable=SC2034
TESTFORMATS=()

parse_args_plugins "$@"

if [[ "${#PARAMETER_TRACKER}" -gt 0 ]]; then
  yetus_error "ERROR: Unprocessed flag(s): ${PARAMETER_TRACKER[*]}"
  if [[ "${IGNORE_UNKNOWN_OPTIONS}" == false ]]; then
    cleanup_and_exit 1
  fi
fi

plugins_initialize

locate_patch

if [[ "${REPORTONLY}" = true ]]; then
  INPUT_APPLIED_FILE="${INPUT_PATCH_FILE}"
  patch_reports
  cleanup_and_exit 0
fi

if [[ ${COMMITMODE} = true ]]; then
  status=$("${GIT}" status --porcelain)
  if [[ "$status" != "" ]] ; then
    yetus_error "ERROR: Can't use --committer option in a workspace that contains the following modifications:"
    yetus_error "${status}"
    cleanup_and_exit 1
  fi
  PATCH_METHODS=("gitam" "${PATCH_METHODS[@]}")
fi

if ! dryrun_both_files; then
  yetus_error "ERROR: Aborting! ${PATCH_OR_ISSUE} cannot be verified."
  cleanup_and_exit "${RESULT}"
fi

patch_file_hinter "${INPUT_APPLIED_FILE}"

if [[ "${INPUT_APPLIED_FILE}" ==  "${INPUT_DIFF_FILE}" ]]; then
  yetus_error "WARNING: "Used diff version of patch file. Binary files and potentially other changes not applied. Please rebase and squash commits if necessary.""
fi

pushd "${BASEDIR}" >/dev/null || exit 1

if [[ ${PATCH_DRYRUNMODE} == false ]]; then
  patchfile_apply_driver "${INPUT_APPLIED_FILE}" "${GPGSIGN}"
  RESULT=$?
fi

if [[ ${COMMITMODE} = true
   && ${PATCH_METHOD} != "gitam" ]]; then
  yetus_debug "Running git add -A"
  git add -A
fi

patch_reports

popd >/dev/null || exit 1

cleanup_and_exit "${RESULT}"
