#!/usr/bin/env bash

################################################################################
# Formats python files that have been modified.
#
# Usage:
#     check/format-incremental [BASE_REVISION] [--apply] [--all]
#
# By default, the script analyzes python files that have changed relative to the
# base revision and determines whether they need to be formatted. If any changes
# are needed, it prints the diff and exits with code 1, otherwise it exits with
# code 0.
#
# With '--apply', reformats the files instead of printing the diff and exits
# with code 0.
#
# With '--all', analyzes all python files, instead of only changed files.
#
# You can specify a base git revision to compare against (i.e. to use when
# determining whether or not a file is considered to have "changed"). For
# example, you can compare against 'origin/main' or 'HEAD~1'.
#
# If you don't specify a base revision, the following defaults will be tried, in
# order, until one exists:
#
#     1. upstream/main
#     2. origin/main
#     3. main
#
# If none exists, the script fails.
################################################################################

# Get the working directory to the repo root.
thisdir="$(dirname "${BASH_SOURCE[0]}")" || exit $?
topdir="$(git -C "${thisdir}" rev-parse --show-toplevel)" || exit $?
cd "${topdir}" || exit $?


# Parse arguments.
only_print=1
only_changed=1
rev=""
for arg in "$@"; do
    if [[ "${arg}" == "--apply" ]]; then
        only_print=0
    elif [[ "${arg}" == "--all" ]]; then
        only_changed=0
    elif [ -z "${rev}" ]; then
        if ! git rev-parse --verify --quiet --no-revs "${arg}^{commit}"; then
            echo -e "\033[31mNo revision '${arg}'.\033[0m" >&2
            exit 1
        fi
        rev="${arg}"
    else
        echo -e "\033[31mToo many arguments. Expected [revision] [--apply] [--all].\033[0m" >&2
        exit 1
    fi
done

declare -a format_files=()
if (( only_changed == 1 )); then
    # Figure out which branch to compare against.
    if [ -z "${rev}" ]; then
        if [ "$(git cat-file -t upstream/main 2> /dev/null)" == "commit" ]; then
            rev=upstream/main
        elif [ "$(git cat-file -t origin/main 2> /dev/null)" == "commit" ]; then
            rev=origin/main
        elif [ "$(git cat-file -t main 2> /dev/null)" == "commit" ]; then
            rev=main
        else
            echo -e "\033[31mNo default revision found to compare against. Argument #1 must be what to diff against (e.g. 'origin/main' or 'HEAD~1').\033[0m" >&2
            exit 1
        fi
    fi
    base="$(git merge-base "${rev}" HEAD)"
    if [ "$(git rev-parse "${rev}")" == "${base}" ]; then
        echo -e "Comparing against revision '${rev}'." >&2
    else
        echo -e "Comparing against revision '${rev}' (merge base ${base})." >&2
        rev="${base}"
    fi

    # Get the modified, added and moved python files.
    IFS=$'\n' read -r -d '' -a format_files < \
        <(git diff --name-only --diff-filter=MAR "${rev}" -- \
        '*.py' ':(exclude)*_pb2.py')
else
    echo -e "Formatting all python files." >&2
    IFS=$'\n' read -r -d '' -a format_files < \
        <(git ls-files '*.py' ':(exclude)*_pb2.py')
fi

if (( ${#format_files[@]} == 0 )); then
    echo -e "\033[32mNo files to format.\033[0m"
    exit 0
fi

# Exclude __init__.py files from isort targets
declare -a isort_files=()
for f in "${format_files[@]}"; do
    if [[ "${f##*/}" != __init__.py ]]; then
        isort_files+=("${f}")
    fi
done

# Color the output if it goes to a terminal or GitHub Actions log
arg_color=()
if [[ -t 1 || "${CI}" == true ]]; then
    arg_color=("--color")
fi

ISORTVERSION="$(isort --version-number)"

echo "Sorting imports with isort... (version: $ISORTVERSION)"

args=("${arg_color[@]}")
if (( only_print == 1 )); then
    args+=("--check" "--diff")
fi

ISORTSTATUS=0
if (( "${#isort_files[@]}" )); then
    isort "${args[@]}" "${isort_files[@]}"
    ISORTSTATUS=$?
fi

BLACKVERSION="$(black --version | head -1)"

echo "Running the black formatter... (version: $BLACKVERSION)"

args=("${arg_color[@]}")
if (( only_print == 1 )); then
    args+=("--check" "--diff")
fi

black "${args[@]}" "${format_files[@]}"
BLACKSTATUS=$?

if (( BLACKSTATUS || ISORTSTATUS )); then
  exit 1
fi
exit 0
