# Note: this script should be sourced into your shell. For this reason it does
# not specify a shebang.
# 
# set-ps1: set the command prompt (PS1) variable to a named 'theme'.
# 
# example:
#
#   set-ps1 simple
#
# If no 'theme' is provided, the original PS1 value that was set in the shell
# startup files is used.

# Keep track of the original PS1 value.
_original_ps1="${PS1}"

set_ps1() {
    # Foreground colours.
    local fg_black=$(tput setaf 0)     # \033[30m
    local fg_red=$(tput setaf 1)       # \033[31m
    local fg_green=$(tput setaf 2)     # \033[32m
    local fg_yellow=$(tput setaf 3)    # \033[33m
    local fg_blue=$(tput setaf 4)      # \033[34m
    local fg_magenta=$(tput setaf 5)   # \033[35m
    local fg_cyan=$(tput setaf 6)      # \033[36m
    local fg_white=$(tput setaf 7)     # \033[37m

    # Background colours.
    local bg_black=$(tput setab 0)     # \033[40m
    local bg_red=$(tput setab 1)       # \033[41m
    local bg_green=$(tput setab 2)     # \033[42m
    local bg_yellow=$(tput setab 3)    # \033[43m
    local bg_blue=$(tput setab 4)      # \033[44m
    local bg_magenta=$(tput setab 5)   # \033[45m
    local bg_cyan=$(tput setab 6)      # \033[46m
    local bg_white=$(tput setab 7)     # \033[47m

    # Text styles and reset. Note that on some terminals 'bold' will produce
    # light colours for bright colours, on others it will actually show the text
    # in bold.
    local bold=$(tput bold)            # \033[1m
    local dim=$(tput dim)              # \033[2m
    local start_underline=$(tput smul) # \033[4m
    local stop_underline=$(tput rmul)  # \033[24m
    local reset=$(tput sgr0)           # \033[0m

    # If you need them, you can get other terminal capabilties such as the
    # number of colours, lines, columns, etc.
    local colors=$(tput colors)        # Number of colours, e.g. '8' or '256'
    local lines=$(tput lines)          # Number of lines in the terminal
    local cols=$(tput cols)            # Number of columns in the terminal

    # Special characters for PS1 strings:
    # \a     an ASCII bell character (07)
    # \d     the date in "Weekday Month Date" format (e.g., "Tue May 26")
    # \D{format}
    #        the format is passed to strftime(3) and the result is inserted into the prompt string; an empty format results in  a  locale-specific  time  representation.   The
    #        braces are required
    # \e     an ASCII escape character (033)
    # \h     the hostname up to the first `.'
    # \H     the hostname
    # \j     the number of jobs currently managed by the shell
    # \l     the basename of the shell's terminal device name
    # \n     newline
    # \r     carriage return
    # \s     the name of the shell, the basename of $0 (the portion following the final slash)
    # \t     the current time in 24-hour HH:MM:SS format
    # \T     the current time in 12-hour HH:MM:SS format
    # \@     the current time in 12-hour am/pm format
    # \A     the current time in 24-hour HH:MM format
    # \u     the username of the current user
    # \v     the version of bash (e.g., 2.00)
    # \V     the release of bash, version + patch level (e.g., 2.00.0)
    # \w     the current working directory, with $HOME abbreviated with a tilde (uses the value of the PROMPT_DIRTRIM variable)
    # \W     the basename of the current working directory, with $HOME abbreviated with a tilde
    # \!     the history number of this command
    # \#     the command number of this command
    # \$     if the effective UID is 0, a #, otherwise a $
    # \nnn   the character corresponding to the octal number nnn
    # \\     a backslash
    # \[     begin a sequence of non-printing characters, which could be used to embed a terminal control sequence into the prompt
    # \]     end a sequence of non-printing characters

    # Depending on the name of the theme provided, set the prompt.
    case $1 in
        basic)
            # Debian/Ubuntu style:
            #   \u@\h - username@host
            #   \w - working directory
            #   \$ - prompt
            PS1="\u@\h:\w\\$ "
        ;;

        debian)
            # Debian/Ubuntu style:
            #   \u@\h - username@host (bold/green)
            #   \w - working directory (bold/blue)
            #   \$ - prompt (# if root, otherwise $) (bold/white)
            PS1="\[${bold}${fg_green}\]\u@\h:\[${fg_blue}\]\w\[${fg_white}\]\\$\[${reset}\] "
        ;;

        datetime)
            # A style that shows the date and time:
            #   \D{%Y-%m-%d} - the year/month/date (in white)
            #   \@ - the time (in green)
            #   \$ - prompt (# if root, otherwise $) (bold/white)
            PS1="\[${fg_white}\]\D{%Y-%m-%d} \[${bold}${fg_green}\]\@\[${fg_white}\] \\$\[${reset}\] "
        ;;

        git)
            # A Debian style prompt with the git info above it.
            PS1="\$(_git_info)"$'\n'"\\[${bold}${fg_green}\]\u@\h:\[${fg_blue}\]\w\[${fg_white}\]\\$\[${reset}\] "
        ;;

        dwmkerr)
            # The current folder, up to 3 items shown, the git info, then the prompt.
            # Starts with a leading newline to space out commands.
            PS1=$'\n'"\[${bold}${fg_blue}\$(_pwd_max_folders 3)${reset}\] \$(_git_info)"$'\n'"\[${bold}${fg_white}\]\\$\[${reset}\] "
        ;;


        # Add your own themes here!

        *)
            # Restore PS1 to its original value.
            PS1="${_original_ps1}"
        ;; 

    esac

    # If we are in Z-Shell convert the PS1 to use Z-Shell format.
    [ -n "$ZSH_VERSION" ] && PS1=$(_to_zsh "$PS1")
}

# Build a string that shows:
# - The branch (underlined if 'main') in green
# - A red exclamation if there are any local changes not committed
# - An indicator of the number of stashed items, if any.
_git_info() {
    # Don't write anything if we're not in a folder tracked by git.
    if ! [ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" == "true" ]
    then
        return
    fi

    # Local copies of the colours for this function.
    local fg_red=$(tput setaf 1)
    local fg_green=$(tput setaf 2)
    local fg_yellow=$(tput setaf 3)
    local start_underline=$(tput smul)
    local stop_underline=$(tput rmul)
    local reset=$(tput sgr0)
    # Git details.
    local git_branch_name="$(git branch --show-current)"
    local git_any_local_changes="$(git status --porcelain=v1 2>/dev/null)"
    local git_stash_count="$(git rev-list --walk-reflogs --count \
        refs/stash -- 2>/dev/null)" # Ignore error when no stashes
    local git_info=""
    if [ "${git_branch_name}" = "main" ]; then
        git_info="${fg_green}${start_underline}${git_branch_name}${reset}"
    else
        git_info="${fg_green}${git_branch_name}${reset}"
    fi
    if ! [ -z "${git_any_local_changes}" ]; then
        # Note that we have to be careful to put the exclamation mark
        # in single quotes so that it is not expanded to the last command!
        git_info="${git_info} ${fg_red}"'!'"${reset}"
    fi
    if [ "${git_stash_count:-0}" -gt 0 ]; then
        git_info="${git_info} ${fg_yellow}${git_stash_count} in stash${reset}"
    fi
    printf "${git_info}"
}

# Show the pwd, limited to a certain number of folders.
_pwd_max_folders() {
    local max_folders="$1"
    # Write the PWD, replace the home path with a tilde, then cut out the last
    # of the directories, up to the max folders value.
    echo "${PWD/#$HOME/~}" | rev | cut -d'/' -f1-${max_folders} | rev
}

# Convert a Bash PS1 string to a ZSH PS1 string.
_to_zsh() {
    # Note that the following sequences are not supported as they don't have
    # an Z-Shell equivalent. Mappings have been worked out from this document:
    #   https://zsh.sourceforge.io/Doc/Release/Prompt-Expansion.html#Prompt-Expansion
    #
    # \l     the basename of the shell's terminal device name
    # \s     the name of the shell, the basename of $0 (the portion following the final slash)
    # \T     the current time in 12-hour HH:MM:SS format
    # \v     the version of bash (e.g., 2.00)
    # \V     the release of bash, version + patch level (e.g., 2.00.0)
    # \W     the basename of the current working directory, with $HOME abbreviated with a tilde
    # \#     the command number of this command

    # Replace Bash special characters with Z-Shell special characters.
    # Before we echo out the PS1 value, we need to replace '\u' with '%n'. If we
    # don't do this here with shell parameter expansion, then 'echo' thinks '\u'
    # is the beginning of a unicode character. This took quite a while to work
    # out. I have left the replacement of '\u' to '%n' in the 'sed' call below
    # but it not needed as the shell expansion below handles it.
    # Note that before we replace '\$' with '%#', we replace '\$(' temporarily
    # to SHELL_EXPAND, otherwise things like '\$(_my_func)' would be transformed
    # to '%#(_my_func)', i.e. we need to replace the prompt special character
    # but not escaped shell functions.
    local zsh_ps1="$(echo ${1/\\u/%n} | sed \
        -e 's/\\\[/%{/g' \
        -e 's/\\\]/%}/g' \
        -e 's/\\\d/%w/g' \
        -e 's/\\\D/%D/g' \
        -e 's/\\\u/%n/g' \
        -e 's/\\\h/%m/g' \
        -e 's/\\\H/%M/g' \
        -e 's/\\\j/%j/g' \
        -e 's/\\\t/%*/g' \
        -e 's/\\\A/%T/g' \
        -e 's/\\\@/%@/g' \
        -e 's/\\\w/%~/g' \
        -e 's/\\\!/%!/g' \
        -e 's/\\\$(/SHELL_EXPAND/g' \
        -e 's/\\\$/%#/g' \
        -e 's/SHELL_EXPAND/\\\$(/g' \
        )"

    # Print the sequence. Note that we need to escape the % symbol.
    local zsh_ps1_escaped=$(echo "${zsh_ps1}" | sed 's/%/%%/g')
    printf "${zsh_ps1_escaped}"
}
