#!/usr/bin/env ksh
#
# Usage: bin/style [all | directory_or_filename...]
#
# Run the source through `clang-format`. If invoked with `all` then all the
# src/cmd/ksh93 source files will be linted. If invoked with one or more path
# names they will be restyled. If the pathname is a directory all *.c and *.h
# files inside it will be restyled. Otherwise any uncommitted source files are
# restyled. If there is no uncommitted change then the files in the most
# recent commit are restyled.
#

# shellcheck disable=SC2207
# Note: Disable SC2207 warning for the entire file since setting IFS to just
# newline makes it safe to handle file names with spaces.
IFS=$'\n'

typeset all=no
typeset git_clang_format=no
typeset -a c_files=()
typeset -a files=()

# Deal with any CLI flags.
while [[ "${#}" -ne 0 ]]
do
    case "${1}" in
        --all | all )
            all=yes
            ;;
        * )
            break
            ;;
    esac
    shift
done

if [[ ${all} == yes && "${#}" -ne 0 ]]
then
    echo "Unexpected arguments: '${1}'" >&2
    exit 1
fi

if [[ ${all} == yes ]]
then
    if [[ "$(git status --porcelain --short --untracked-files=all)" != "" ]]
    then
        echo >&2
        echo You have uncommitted changes. Cowardly refusing to restyle the entire code base. >&2
        echo >&2
        exit
    fi

    files=( $(find src -name "*.h" -o -name "*.c") )
elif [[ "${#}" -ne 0 ]]
then
    for next_file in "$@"
    do
        if [[ -f ${next_file} ]]
        then
            files+=( "${next_file}" )
        elif [[ -d ${next_file} ]]
        then
            files+=( $(find "${next_file}" -name "*.h" -o -name "*.c") )
        fi
    done
else
    # We haven't been asked to reformat all the source or specific files. If there are
    # uncommitted changes reformat those using `git clang-format`. Else reformat the
    # files in the most recent commit. Select cached files, modified but not cached,
    # and untracked files.
    files=( $(git diff-index --cached HEAD --name-only) )
    files+=( $(git ls-files --exclude-standard --others --modified) )
    if [[ "${#files[@]}" -ne 0 ]]
    then
        # Pending changes so restyle just the regions modified.
        git_clang_format=yes
    else
        # No pending changes so restyle the files in the most recent commit.
        files=( $(git diff-tree --no-commit-id --name-only -r HEAD) )
    fi
fi

# Filter out non C source files.
for file in "${files[@]}"
do
    case "${file}" in
        *.c | *.h )
            if [[ -f "${file}" ]]
            then
                c_files+=( "${file}" )
            fi
            ;;
    esac
done

# Run the C reformatter if we have any C files
if [[ "${#c_files[@]}" -eq 0 ]]
then
    echo
    echo 'WARNING: No C files to restyle'
    echo
    exit 1
fi

if [[ ${git_clang_format} == yes ]]
then
    if command -v git-clang-format > /dev/null
    then
        echo
        echo ========================================
        echo Running git-clang-format
        echo ========================================
        git add "${c_files[@]}"
        git-clang-format
    else
        echo
        echo 'WARNING: Cannot find git-clang-format command'
        echo
        exit 1
    fi
else
    if command -v clang-format > /dev/null
    then
        echo
        echo ========================================
        echo Running clang-format
        echo ========================================
        for file in "${c_files[@]}"
        do
            cp "${file}" "${file}.new"  # preserves mode bits
            clang-format "${file}" >"${file}.new"
            if cmp -s "${file}" "${file}.new"
            then
                rm "${file}.new"
            else
                echo "${file} was NOT correctly formatted"
                mv "${file}.new" "${file}"
            fi
        done
    else
        echo
        echo 'WARNING: Cannot find clang-format command'
        echo
        exit 1
    fi
fi
