#!/bin/bash
# Copyright 2025 Arm Limited and/or its affiliates.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.

# Calling this script with one argument is equal to launching it in
# non-interactive mode. "$#" gives the number of positional arguments.
[ "$#" -eq 1 ] && is_script_interactive=1 || is_script_interactive=0

if [ $is_script_interactive -eq 1 ]; then
    RESET='\e[0m'
    RED='\e[31m'
    GREEN='\e[32m'
    YELLOW='\e[33m'
    BLUE='\e[34m'
fi

INFO="${BLUE}[INFO]${RESET}"
WARNING="${YELLOW}[WARNING]${RESET}"
ERROR="${RED}[ERROR]${RESET}"
SUCCESS="${GREEN}[SUCCESS]${RESET}"

# This list of imperative verbs was compiled from the entire list of Executorch
# commits. It should be fairly exhaustive, but add more verbs if you find one
# that's missing.
VERBS="Add|Fix|Update|Refactor|Improve|Remove|Change|Implement|Create|Modify|"\
"Enable|Integrate|Make|Support|Deprecate|Extend|Enhance|Convert|Rewrite|Unify|"\
"Optimize|Expand|Reorganize|Adjust|Streamline|Clarify|Introduce|Document|"\
"Polish|Standardize|Revise|Simplify|Restore|Resolve|Replace|Suppress|Migrate|"\
"Generate|Delete|Exclude|Register|Include|Upgrade|Validate|Verify|Refine|"\
"Reimplement|Patch|Sync|Revert|Fixup|Enhance|Append|Annotate|Disable|Emit|"\
"Handle|Ignore|Interpret|Instantiate|Invoke|Limit|Load|Modify|Permit|Print|"\
"Profile|Recalculate|Reconstruct|Redefine|Redesign|Reevaluate|Relocate|Remap|"\
"Render|Reposition|Request|Revert|Sanitize|Specify|Strengthen|Stub|Substitute|"\
"Tag|Tweak|Unify|Unlock|Unset|Use|Validate|Verify|Rename"

# Remote branch
REMOTE=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null)
if [ $is_script_interactive -eq 0 ]; then
    # Just use the one commit
    COMMITS=$(git rev-list HEAD -n 1)
elif [ -z "$REMOTE" ]; then
    echo -e "${WARNING} Could not find upstream branch to compare to."
    echo "Please specify the number of commits you are pushing."
    echo -n "Enter number of commits to check (default 1): " > /dev/tty
    read NUM_COMMITS < /dev/tty
    NUM_COMMITS=${NUM_COMMITS:-1} # Default to 1 if empty
    RANGE=$(git rev-list HEAD -n "$NUM_COMMITS")
    COMMITS=${RANGE}
elif [ "$(git rev-parse --abbrev-ref HEAD)" == "HEAD" ]; then
    echo -e "${WARNING} You're in a detached HEAD state."
    echo "Please specify the number of commits you are pushing."
    echo -n "Enter number of commits to check (default 1): " > /dev/tty
    read NUM_COMMITS < /dev/tty
    NUM_COMMITS=${NUM_COMMITS:-1} # Default to 1 if empty
    RANGE=$(git rev-list HEAD -n "$NUM_COMMITS")
    COMMITS=${RANGE}
else
    # Determine commits to check
    RANGE="$REMOTE..HEAD"
    COMMITS=$(git rev-list "$RANGE")
fi

if [ -z "$COMMITS" ]; then
    echo -e "${INFO} No new commits to check."
    exit 0
fi

for COMMIT in ${COMMITS}; do
    # If commit header contains WIP, everything is ok
    git rev-list --format=%s --max-count=1 ${COMMIT} | grep -q WIP && \
         continue

    echo -e "${INFO} Checking commit ${COMMIT}"

    # lintrunner on latest patches.
    echo -e "${INFO} Lintrunner"
    MYPYPATH=./src/ lintrunner --revision ${COMMIT}^1
    if [[ $? != 0 ]]; then
        echo -e "${ERROR} Failed linting"
        FAILED=1
    else
        echo -e "${SUCCESS} Lintrunner OK"
    fi

    # Check license headers
    # We do a simple check of if all committed headers contain
    # "$current_year Arm". This does not guarantee OK in ci but should be ok
    # most of the time.
    echo -e "${INFO} License check"

    current_year=$(date +%Y)
    failed_license_check=false
    commit_files=$(git diff-tree --no-commit-id --name-only \
        --diff-filter=ACMR ${COMMIT} -r)
    for commited_file in $commit_files; do
        head $commited_file | grep -q "$current_year Arm"
        if [[ $? != 0 ]]; then
            echo -e "${ERROR} Header in $commited_file did not contain"\
            "'$current_year Arm'"
            failed_license_check=true
        else
            echo -e "${SUCCESS} $commited_file passed license check"
        fi
    done

    if [[ $failed_license_check == true ]]; then
        FAILED=1
    else
        echo -e "${SUCCESS} All files passed license check"
    fi

    # Check commit message
    echo -e "${INFO} Checking commit message"
    COMMIT_MSG=$(git log -1 --format=%B "$COMMIT")

    SUBJECT=$(echo "$COMMIT_MSG" | head -n1)
    BODY=$(echo "$COMMIT_MSG" | tail -n +2)

    # Check subject length (72 chars)
    SUBJECT_MAX_LEN=72
    if [ ${#SUBJECT} -gt ${SUBJECT_MAX_LEN} ]; then
        echo -e "${ERROR} Subject exceeds ${SUBJECT_MAX_LEN} characters:"\
            "'${SUBJECT}'" >&2

        FAILED=1
    else
        echo -e "${SUCCESS} Commit message subject OK"
    fi

    # Check body line length (72 chars)
    BODY_MAX_LEN=72
    line_number=2 # Subject + 1 empty line
    failed_body_check=false
    while IFS= read -r line; do
        if [ ${#line} -gt ${BODY_MAX_LEN} ]; then
            echo -e "${ERROR} Line ${line_number} in body exceeds"\
                "${BODY_MAX_LEN} characters: '$line'" >&2

            failed_body_check=true
        fi

        ((line_number++))
    done <<< "$BODY"

    if [[ $failed_body_check == true ]]; then
        FAILED=1
    else
        echo -e "${SUCCESS} Commit message body OK"
    fi

    # Check for Signed-off-by
    if ! echo "$COMMIT_MSG" | grep -qE "^Signed-off-by: "; then
        echo -e "${ERROR} Commit message must contain a 'Signed-off-by'"\
            "footer." >&2

        FAILED=1
    fi

    # Check subject format, should start with 'Arm backend: ' and be
    # imperative mood.
    if [[ ! "$SUBJECT" =~ ^"Arm backend":\ (${VERBS}) ]]; then
        echo -e "${WARNING} Subject should start with 'Arm backend: '"\
            "followed by an imperative verb." >&2

        if [ $is_script_interactive -eq 1 ]; then
            echo -n "There are warnings in your commit message. Do you want to"\
                "ignore the warning (y/N): " > /dev/tty

            read USER_INPUT < /dev/tty

            # Check user input for warnings
            if [[ ! "$USER_INPUT" =~ ^[Yy]$ ]]; then
                FAILED=1
            fi
        fi
    fi

    # Op test checks
    op_test_files=$(echo $commit_files | grep -oE 'backends/arm/test/ops/\S+')
    if [ "$op_test_files" ]; then

        # Check that the tested op and target is parsed correctly from the test name
        test_names=$(grep -h "def test_" $op_test_files | cut -d"(" -f1 | cut -d" " -f2)
        python ./backends/arm/scripts/parse_test_names.py $test_names
        if [ $? -ne 0 ]; then
            echo -e "${ERROR} Failed op test name check." >&2
            FAILED=1
        fi
    fi

    echo "" # Newline to visually separate commit processing
done

if [[ $FAILED ]]; then
    echo -e "${INFO} Fix your commit message errors with"\
        "'git commit --amend' or 'git commit --fixup=<SHA>'"

    exit 1
else
    echo -e "${SUCCESS} All checks passed"
fi

exit 0
