# Eryn Wells <eryn@erynwells.me>
# vim: set ft=zsh:

function _update-path-usage
{
    local -r function_name=$1

    local usage_string=$(cat <<EOF
Usage: $function_name <arguments> [<path>...]

Modify the target path-like variable by adding or removing paths provided as
positional arguments. Before adding, check that the target path variable doesn't
already contain the path and that the directory exists.

This function returns the number of paths that were skipped in the input. If all
paths were processed, it returns 0. It returns 255 if an error occurred while
processing arguments.

Arguments
---------

%B-e%b | %B--export%b | %B--no-export%b
    Export the variable after modification. The default is to export if the
    variable is modified.

%B-f%b | %B--force%b
    Unconditionally add the path, even if it doesn't exist.

%B-h%b | %B--help%b
    Print this message.

%B-p%b | %B--prepend%b
    Prepend paths to the target path variable, instead of appending. When
    prepending, directories will be added in reverse order so their precedence
    is preserved. That is, the first directory in the list will be at the start
    of the path.

%B-P%b | %B--path%b <path_variable_name>
    The name of a path-like variable to modify. The default is 'path'.

%B-r%b | %B--remove%b
    Remove paths instead of adding. -p and -f are ignored when removing
    directories from the path.

%B-v%b | %B--verbose%b
    Print extra information while processing directories.
EOF
)

    print -P $usage_string
}

function update-path
{
    zmodload zsh/zutil

    local -a o_args o_export o_force o_help o_prepend o_remove o_verbose o_pathvar
    zparseopts -a o_args -D -E -F - \
        {e,-export,-no-export}=o_export \
        {f,-force}=o_force \
        {h,-help}=o_help \
        {p,-prepend}=o_prepend \
        {P,-path}:=o_pathvar \
        {r,-remove}=o_remove \
        {v,-verbose}=o_verbose

    local -ri parse_status=$?
    if (( parse_status )); then
        _update-path-usage $0
        return 255
    fi

    if [[ $#o_help -ne 0 ]]; then
        _update-path-usage $0
        return 0
    fi

    local -r verbose=$#o_verbose
    local -r path_variable_name=${o_pathvar[-1]:-path}

    if ! typeset -p $path_variable_name &> /dev/null; then
        print "Invalid path variable: \$$path_variable_name" 1>&2
        return 255
    fi

    [[ $verbose -ne 0 ]] && print "Modifying $path_variable_name"

    local -i candidates_skipped=0
    local did_update_path

    local -a candidates=($@)
    if [[ $#o_prepend -ne 0 ]]; then
        # If prepending, reverse the list so paths are added in the right order.
        candidates=(${(Oa)@})
    fi

    for candidate in $candidates; do
        local candidate_index=${${(P)path_variable_name}[(Ie)$candidate]}

        # An empty $o_remove means we're adding to the array.
        if [[ $#o_remove -eq 0 ]]; then
            if [[ $candidate_index -ne 0 ]]; then
                [[ $verbose -ne 0 ]] && print "Skipping $candidate"
                (( candidates_skipped++ ))
                continue
            fi

            if [[ $#force -eq 0 && ! -d "$candidate" ]]; then
                [[ $verbose -ne 0 ]] && print "$candidate doesn't exist"
                (( candidates_skipped++ ))
                continue
            fi

            did_update_path=yes

            [[ $verbose -ne 0 ]] && print "Adding $candidate"
            if [[ $#o_prepend -ne 0 ]]; then
                eval "${path_variable_name}=(${candidate} ${path})"
            else
                eval "${path_variable_name}+=${candidate}"
            fi
        else
            if [[ $candidate_index -eq 0 ]]; then
                [[ $verbose -ne 0 ]] && print "Skipping $candidate"
                (( candidates_skipped++ ))
                continue
            fi

            [[ $verbose -ne 0 ]] && print "Removing $candidate"

            did_update_path=yes
            eval "${path_variable_name}[${candidate_index}]=()"
        fi
    done

    if [[ -n $did_update_path && $o_export[(I)--no-export] -ne 0 ]]; then
        [[ $verbose -ne 0 ]] && print "Exporting $path_variable_name"
        export $path_variable_name
        if [[ "$path_variable_name" == "path" ]]; then
            rehash
        fi
    fi

    return $candidates_skipped
}

update-path "$@"
