#!/bin/bash
#
# Read and write config files from Bash


readonly PROGRAM="${0##*/}"
readonly VERSION='0.1.dev1'

readonly SEPARATOR_BASH='='
readonly SEPARATOR_INI=' = '


error(){
    echo "ERROR: $@" >&2
}

get_line_number(){
    set_mode
    local output=$(sed -n "/^${parameter}${separator}/=" "$config" 2>/dev/null)
    [[ ! -z ${output##*[!0-9]*} ]] && line_number="$output"
}

main(){
    if echo "$1" | grep -Eq '^(-b|--bash)$'; then
        option_mode=bash
    elif echo "$1" | grep -Eq '^(-c|--custom)$'; then
        option_mode=custom
    elif echo "$1" | grep -Eq '^(-i|--ini)$'; then
        option_mode=ini
    fi

    local args
    if [[ $option_mode = bash || $option_mode = ini ]]; then
        args=$(($# - 1))
        config=$2
        parameter=$3
        value=$4
    elif [[ $option_mode = custom ]]; then
        args=$(($# - 2))
        separator_custom=$2
        config=$3
        parameter=$4
        value=$5
    else
        args=$#
        config=$1
        parameter=$2
        value=$3
    fi

    if (( args == 0 )); then
        error "No arguments"
        see_help_msg
        exit 1
    elif (( args == 1 )) && echo "$1" | grep -Eq '^(-h|--help|help)$'; then
        usage
    elif (( args == 1 )) && echo "$1" | grep -Eq '^(-v|--version|version)$'; then
        echo "$PROGRAM $VERSION"
        exit 0
    elif (( args == 2 )); then
        if [[ $parameter = % ]]; then
            # DELETE CONFIG
            if [[ ! -f $config ]]; then
                error "'$config' does not exist"
                exit 1
            elif [[ ! -w $config ]]; then
                error "Lacking write perms to '$config'"
                exit 1
            else
                remove_config
            fi
        else
            # READ PARAMETER
            if [[ ! -f $config ]]; then
                error "'$config' does not exist"
                exit 1
            elif [[ ! -r $config ]]; then
                error "No read access to '$config'"
                exit 1
            else
                read_parameter
            fi
        fi
    elif (( args == 3 )); then
        if [[ $value = % ]]; then
            # DELETE PARAMETER
            if [[ ! -f $config ]]; then
                error "'$config' does not exist"
                exit 1
            elif [[ ! -w $config ]]; then
                error "No write access to '$config'"
                exit 1
            else
                remove_parameter
            fi
        else
            # WRITE PARAMETER
            if [[ -f $config && ! -w $config ]]; then
                error "No write access to '$config'"
                exit 1
            else
                write_parameter
            fi
        fi
    else
        error "Invalid arguments"
        see_help_msg
        exit 1
    fi
}

read_parameter(){
    set_mode

    local output=$(sed -n "s/^${parameter}${separator}//p" "$config" 2>/dev/null)

    if [[ -n $output ]]; then
        echo "$output"
    else
        if grep -q "^${parameter}${separator}$" "$config" 2>/dev/null; then
            error "'$parameter' is set to null in '$config'"
            exit 0
        else
            error "No match for '$parameter' in '$config'"
            exit 1
        fi
    fi
}

remove_config(){
    rm -f "$config" &>/dev/null

    if [[ -f $config ]]; then
        error "Failed to remove '$config'"
        exit 1
    fi
}

remove_parameter(){
    get_line_number
    [[ -n $line_number ]] && sed -i "${line_number}d" "$config" 2>/dev/null
    if grep -q "^${parameter}${separator}" "$config" 2>/dev/null; then
        error "Failed to remove parameter '$parameter' from '$config'"
        exit 1
    elif [[ -f $config && ! -s $config ]]; then  # remove config if empty
        remove_config
    fi
}

sanitize_parameter(){
    if ! echo "$parameter" | grep -q '^[A-Za-z0-9_-]\+$'; then
        error "Parameter may consist of A-Z, a-z, 0-9, underscores, and dashes (no spaces or special characters)"
        exit 1
    fi
}

see_help_msg(){
    echo "Try '$PROGRAM --help' for more information." >&2
}

set_mode(){
    grep -q "^[A-Za-z0-9_-]\+${SEPARATOR_BASH}" "$config" &>/dev/null && mode+=(bash)

    grep -q "^[A-Za-z0-9_-]\+${SEPARATOR_INI}" "$config" &>/dev/null && mode+=(ini)

    [[ $option_mode = custom ]] && grep -q "^[A-Za-z0-9_-]\+${separator_custom}" "$config" &>/dev/null && mode+=(custom)

    if (( ${#mode[@]} > 1 )); then
        error "Unable to determine a consistent configuration file format. Are you sure '$config' is a config file?"
        exit 1
    fi

    # OPTION IS SET
    if [[ -n $option_mode ]]; then

        if [[ -n ${mode[0]} && ( ${mode[0]} != $option_mode ) ]]; then
            # use detected mode in the event of a conflicting option
            echo "WARNING: '$config' was intified as ${mode[0]} mode (ignoring $option_mode option)" >&2
            mode=${mode[0]}
        else
            # use optioned mode
            mode=$option_mode
        fi

    # NO OPTION IS SET and we have detected mode
    elif [[ -n ${mode[0]} ]]; then
        # use detected mode
        mode=${mode[0]}
    fi

    set_separator "$mode"
}

set_separator(){
    if [[ $1 = custom ]]; then
        separator=$separator_custom
    elif [[ $1 = ini ]]; then
        separator=$SEPARATOR_INI
    else
        # default to bash mode
        separator=$SEPARATOR_BASH
    fi
}

usage(){
        cat <<-EOF
		Usage: $PROGRAM [OPTION] <config file> <parameter> [value]
		Read and write config files from bash.

		  -b, --bash            use bash mode ex. '='
		  -c, --custom          use custom separator string
		  -i, --ini             use ini mode ex. ' = '
		  -v, --version         show program's version number and exit

		* Enter a value to create or update an entry
		* Specify only a parameter to print the currently set value (if any)
		* Use '%' to delete the config file or a specific parameter
		EOF
        exit 0
}

verify_write(){
    local fullstring=${parameter}${separator}${value}
    if ! grep -q "^${fullstring}$" "$config" &>/dev/null; then
        error "Failed to write '${fullstring}' to '$config'"
        exit 1
    fi
}

write_parameter(){
    if [[ -f $config ]]; then
        get_line_number
        if [[ -n $line_number ]]; then
            sed -i "${line_number}c${parameter}${separator}${value}" "$config" 2>/dev/null
        else
            echo "${parameter}${separator}${value}" >> "$config"
            sort -o "$config" "$config"
        fi
    else
        set_separator "$option_mode"
        echo "${parameter}${separator}${value}" >> "$config"
    fi
    verify_write
}

main "$@"
