#!/usr/bin/env bash

# Quick script to handle installing/uninstalling relevant dotfiles components

# A better class of script...
set -o errexit          # Exit on most errors (see the manual)
set -o errtrace         # Make sure any error trap is inherited
set -o nounset          # Disallow expansion of unset variables
set -o pipefail         # Use last non-zero exit code in a pipeline
#set -o xtrace          # Trace the execution of the script (debug)

# Path to the metadata directory
META_DIR='metadata'
# Path to the stow executable
STOW_BIN='stow/bin/stow'

# DESC: Usage help
# ARGS: None
# OUTS: None
function script_usage() {
    cat << EOF
Usage:
     -h|--help                  Displays this help
     -v|--verbose               Displays verbose output
    -nc|--no-colour             Disables colour output

    Stow Options:
     -r|--reinstall             Equivalent to an uninstall and subsequent
                                reinstall. This is the default and recommended
                                option as it will prune any obsolete symlinks.
     -i|--install               Installs all detected applicable components
     -u|--uninstall             Uninstalls all detected applicable components

    Detection Options:
    -dr|--detection-report      Displays a report of the detection results
EOF
}

# DESC: Parameter parser
# ARGS: $@ (optional): Arguments provided to the script
# OUTS: Variables indicating command-line parameters and options
function parse_params() {
    local param

    # Default stow method
    stow_method=reinstall

    while [[ $# -gt 0 ]]; do
        param="$1"
        shift
        case $param in
            -h | --help)
                script_usage
                exit 0
                ;;
            -v | --verbose)
                verbose=true
                ;;
            -nc | --no-colour)
                no_colour=true
                ;;
            -i | --install)
                stow_method=install
                ;;
            -r | --reinstall)
                stow_method=reinstall
                ;;
            -u | --uninstall)
                stow_method=uninstall
                ;;
            -dr | --detection-report)
                detection_report=true
                ;;
            *)
                script_exit "Invalid parameter was provided: $param" 2
                ;;
        esac
    done
}

# DESC: Find some important paths we need before script_init() is called
# ARGS: None
# OUTS: $script_real_path: The full canonicalized path to the script
#       $script_real_dir: The canonicalized directory path of the script
#       $script_real_name: The canonicalized file name of the script
#       $dotfiles_path: The dotfiles directory path
#       $dotfiles_name: The dotfiles directory name
# shellcheck disable=SC2034
function early_init() {
    # Determine the physical directory this script resides in. This approach is
    # necessarily clumsy as simple methods like "readlink -f" and "realpath"
    # are not platform independent. We have to work on Linux, BSD, OS X, etc...
    script_real_path="$(perl -MCwd -le 'print Cwd::abs_path(shift)' "${BASH_SOURCE[0]}")"
    script_real_dir="$(dirname "$script_real_path")"
    script_real_name="$(basename "$script_real_path")"
    readonly script_real_path script_real_dir script_real_name

    # Dotfiles directory path & name
    dotfiles_path="$(cd -P "$script_real_dir/../../" && pwd)"
    dotfiles_name="$(basename "$dotfiles_path")"
    readonly dotfiles_path dotfiles_name

    # Source in common metadata functions
    # shellcheck source=metadata/templates/common.sh
    source "$dotfiles_path/$META_DIR/templates/common.sh"
}

# DESC: Create some directories in advance so stow doesn't symlink them
# ARGS: $@ (required): Directories to be created
# OUTS: None
function create_dirs() {
    while [[ $# -gt 0 ]]; do
        if [[ ! -d $1 ]]; then
            mkdir -p "$1"
        fi
        shift
    done
}

# DESC: Quick, simple & dirty sorting of arguments
# ARGS: $@ (required): Arguments to be sorted
# OUTS: None
function sort_args() {
    printf '%s ' "$@" | xargs -n 1 | sort | xargs
}

# DESC: Detect the status of each component
# ARGS: None
# OUTS: $stow_list: Array of components to be passed to GNU Stow
#       $detect_success: Array of successfully detected components
#       $detect_always_install: Array of components to always install
#       $detect_never_install: Array of components to never install
#       $detect_not_available: Array of components which are unavailable
#       $detect_no_logic: Array of components missing detection logic
#       $detect_ignore: Array of components to explicitly ignore
#       $detect_failure: Array of components with a detection failure
function detect_components() {
    pretty_print '*** Detecting applicable dotfiles components ...'

    local comp_list comp_name comp_path detect_status
    declare -g -a stow_list \
        detect_success detect_failure detect_ignore detect_always_install \
        detect_never_install detect_not_available detect_no_logic

    comp_list="$(find "$dotfiles_path" \
                    -maxdepth 1 \
                    -type d \
                    -not -path '*/\.*' -and \
                    -not -name "$dotfiles_name")"

    for comp_path in $comp_list; do
        cd "$dotfiles_path"
        comp_name="$(basename "$comp_path")"

        if [[ -x $META_DIR/$comp_name.sh ]]; then
            if "$META_DIR/$comp_name.sh"; then
                stow_list+=("$comp_name")
                detect_success+=("$comp_name")
            else
                detect_status=$?
                if [[ $detect_status -eq $DETECTION_ALWAYS_INSTALL ]]; then
                    stow_list+=("$comp_name")
                    detect_always_install+=("$comp_name")
                elif [[ $detect_status -eq $DETECTION_NEVER_INSTALL ]]; then
                    detect_never_install+=("$comp_name")
                elif [[ $detect_status -eq $DETECTION_NOT_AVAILABLE ]]; then
                    detect_not_available+=("$comp_name")
                elif [[ $detect_status -eq $DETECTION_NO_LOGIC ]]; then
                    detect_no_logic+=("$comp_name")
                elif [[ $detect_status -eq $DETECTION_IGNORE ]]; then
                    detect_ignore+=("$comp_name")
                else
                    detect_failure+=("$comp_name")
                fi
            fi
        elif command -v "$comp_name" > /dev/null; then
            stow_list+=("$comp_name")
            detect_success+=("$comp_name")
        else
            detect_not_available+=("$comp_name")
        fi
    done
}

# DESC: Output a report with the results of component detection
# ARGS: None
# OUTS: None
# shellcheck disable=SC2311
function detect_report() {
    local detection_result

    printf '%s\t\t' 'Detected:'
    if [[ -n ${detect_success[*]-} ]]; then
        detection_result="$(sort_args "${detect_success[@]}")"
        printf '%b%s%b\n' "$fg_green" "$detection_result" "$ta_none"
    else
        printf '%b%s%b\n' "$fg_cyan" 'None' "$ta_none"
    fi

    printf '%s\t\t' 'Always Install:'
    if [[ -n ${detect_always_install[*]-} ]]; then
        detection_result="$(sort_args "${detect_always_install[@]}")"
        printf '%b%s%b\n' "$fg_green" "$detection_result" "$ta_none"
    else
        printf '%b%s%b\n' "$fg_cyan" 'None' "$ta_none"
    fi

    printf '%s\t\t' 'Never Install:'
    if [[ -n ${detect_never_install[*]-} ]]; then
        detection_result="$(sort_args "${detect_never_install[@]}")"
        printf '%b%s%b\n' "$fg_green" "$detection_result" "$ta_none"
    else
        printf '%b%s%b\n' "$fg_cyan" 'None' "$ta_none"
    fi

    printf '%s\t\t' 'Not Available:'
    if [[ -n ${detect_not_available[*]-} ]]; then
        detection_result="$(sort_args "${detect_not_available[@]}")"
        printf '%b%s%b\n' "$fg_green" "$detection_result" "$ta_none"
    else
        printf '%b%s%b\n' "$fg_cyan" 'None' "$ta_none"
    fi

    printf '%s\t' 'Detection Ignored:'
    if [[ -n ${detect_ignore[*]-} ]]; then
        detection_result="$(sort_args "${detect_ignore[@]}")"
        printf '%b%s%b\n' "$fg_green" "$detection_result" "$ta_none"
    else
        printf '%b%s%b\n' "$fg_cyan" 'None' "$ta_none"
    fi

    printf '%s\t' 'No Detection Logic:'
    if [[ -n ${detect_no_logic[*]-} ]]; then
        detection_result="$(sort_args "${detect_no_logic[@]}")"
        printf '%b%s%b\n' "$fg_yellow" "$detection_result" "$ta_none"
    else
        printf '%b%s%b\n' "$fg_green" 'None' "$ta_none"
    fi

    printf '%s\t' 'Detection Failure:'
    if [[ -n ${detect_failure[*]-} ]]; then
        detection_result="$(sort_args "${detect_failure[@]}")"
        printf '%b%s%b\n' "$fg_red" "$detection_result" "$ta_none"
    else
        printf '%b%s%b\n' "$fg_green" 'None' "$ta_none"
    fi
}

# DESC: Invokes the stow executable on detected components
# ARGS: $1 (required): The operation type (-S, -D or -R)
# OUTS: $stow_success: Array of components Stow successfully installed
#       $stow_failure: Array of components Stow failed to install
# shellcheck disable=SC2311
function stow_components() {
    local stow_comp stow_result
    declare -g -a stow_success stow_failure

    cd "$dotfiles_path"
    for stow_comp in "${stow_list[@]}"; do
        if "$STOW_BIN" "$1" \
                -d "$dotfiles_path" \
                -t "$HOME" \
                "$stow_comp" 2> /dev/null; then
            stow_success+=("$stow_comp")
        else
            stow_failure+=("$stow_comp")
        fi
    done

    if [[ -n ${stow_failure[*]-} ]]; then
        printf '%s\t\t' 'Stow Failed:'
        stow_result="$(sort_args "${stow_failure[@]}")"
        printf '%b%s%b\n' "$fg_red" "$stow_result" "$ta_none"
    fi

    if [[ -n ${stow_success[*]-} ]]; then
        printf '%s\t\t' 'Stow Succeeded:'
        stow_result="$(sort_args "${stow_success[@]}")"
        printf '%b%s%b\n' "$fg_green" "$stow_result" "$ta_none"
    fi
}

# DESC: Main control flow
# ARGS: $@ (optional): Arguments provided to the script
# OUTS: None
function main() {
    early_init

    # shellcheck source=scripts/bin/bash-template.sh
    source "$script_real_dir/bash-template.sh"

    trap script_trap_err ERR
    trap script_trap_exit EXIT

    script_init "$@"
    parse_params "$@"
    colour_init

    detect_components

    if [[ -n ${detection_report-} ]]; then
        detect_report
        return
    fi

    create_dirs "$HOME/.config"
    create_dirs "$HOME/.local/share"
    create_dirs "$HOME/share/perl"
    for section in $(seq 1 8); do
        create_dirs "$HOME/share/man/man$section"
    done

    # shellcheck disable=SC2249
    case $stow_method in
        install)
            pretty_print '*** Installing all dotfiles components ...'
            stow_components -S
            ;;
        reinstall)
            pretty_print '*** Reinstalling all dotfiles components ...'
            stow_components -R
            ;;
        uninstall)
            pretty_print '*** Uninstalling all dotfiles components ...'
            stow_components -D
            ;;
    esac
}

# Stow all the things!
main "$@"

# vim: syntax=sh cc=80 tw=79 ts=4 sw=4 sts=4 et sr
