#!/usr/bin/env bash

### Helpers begin
check_deps() {
    local missing
    for d in "${deps[@]}"; do
        if [[ -z $(command -v "$d") ]]; then
            # Force absolute path
            if [[ ! -e "/$d" ]]; then
                err "$d was not found"
                missing="true"
            fi
        fi
    done; unset d
    [[ -z $missing ]] || exit 128
}
err() { echo -e "${color:+\e[31m}[!] $*\e[0m"; }
errx() { err "${*:2}"; exit "$1"; }
good() { echo -e "${color:+\e[32m}[+] $*\e[0m"; }
info() { echo -e "${color:+\e[37m}[*] $*\e[0m"; }
long_opt() {
    local arg shift="0"
    case "$1" in
        "--"*"="*) arg="${1#*=}"; [[ -n $arg ]] || return 127 ;;
        *) shift="1"; shift; [[ $# -gt 0 ]] || return 127; arg="$1" ;;
    esac
    echo "$arg"
    return $shift
}
subinfo() { echo -e "${color:+\e[36m}[=] $*\e[0m"; }
warn() { echo -e "${color:+\e[33m}[-] $*\e[0m"; }
### Helpers end

generate_comp_funcs() {
    local comp
    local decl
    local param
    local sym="$2"

    case "$1" in
        "Add"|"Set"|"Sub")
            decl=", val $thetype"
            param="val"
            ;;
    esac

    case "$sym" in
        "==") comp="Equal" ;;
        ">") comp="Greater" ;;
        ">=") comp="GreaterEqual" ;;
        "!=") comp="NotEqual" ;;
        "<") comp="Less" ;;
        "<=") comp="LessEqual" ;;
    esac

    cat <<EOF
// ${comp^}$1 will check if the value is $sym the expected value. If
// it is, it will call the appropriate function and return true,
// otherwise it will simply return false.
func (p *${thetype^}) ${comp^}$1(expected $thetype$decl) bool {
	p.Lock()
	defer p.Unlock()

    if p.value $sym expected {
        p.Unsafe$1($param)
        return true
    }

    return false
}

EOF
}

generate_type_funcs() {
    local op
    local thetype="$1"

    cat <<EOF
// ${thetype^} is a thread-safe $thetype implementation.
type ${thetype^} struct {
	sync.RWMutex
    value $thetype
}

// New${thetype^} will return a pointer to a new ${thetype^} instance.
func New${thetype^}() *${thetype^} {
	return &${thetype^}{}
}

// Get will return the current value.
func (p *${thetype^}) Get() $thetype {
	p.RLock()
	defer p.RUnlock()

    return p.value
}

// Set will set the current value.
func (p *${thetype^}) Set(val $thetype) {
	p.Lock()
	defer p.Unlock()

    p.value = val
}

// UnsafeSet will set the current value, with no Lock.
func (p *${thetype^}) UnsafeSet(val $thetype) {
    p.value = val
}

EOF

    generate_comp_funcs "Set" "=="
    generate_comp_funcs "Set" "!="

    case "$thetype" in
        "bool") return ;;
    esac

    generate_comp_funcs "Set" ">="
    generate_comp_funcs "Set" "<="

    case "$thetype" in
        "float"*|*"int"*)
            for op in Add Dec Inc Sub; do
                generate_comp_funcs "$op" ">"
                generate_comp_funcs "$op" ">="
                generate_comp_funcs "$op" "<"
                generate_comp_funcs "$op" "<="
            done; unset op

            cat <<EOF
// Add will add the value and return the new value.
func (p *${thetype^}) Add(value $thetype) $thetype {
	p.Lock()
	defer p.Unlock()

    p.value += value

    return p.value
}

// Dec will decrement the current value by 1 and return the new value.
func (p *${thetype^}) Dec() $thetype {
	p.Lock()
	defer p.Unlock()

    return p.UnsafeSub(1)
}

// Inc will increment the current value by 1 and return the new value.
func (p *${thetype^}) Inc() $thetype {
	p.Lock()
	defer p.Unlock()

    return p.UnsafeAdd(1)
}

// Sub will subtract the value and return the new value.
func (p *${thetype^}) Sub(value $thetype) $thetype {
	p.Lock()
	defer p.Unlock()

    p.value -= value

    return p.value
}

// UnsafeAdd will add the value and return the new value, with no
// Lock.
func (p *${thetype^}) UnsafeAdd(value $thetype) $thetype {
    p.value += value
    return p.value
}

// UnsafeDec will decrement the current value by 1 and return the new
// value, with no Lock.
func (p *${thetype^}) UnsafeDec() $thetype {
    return p.UnsafeSub(1)
}

// UnsafeInc will increment the current value by 1 and return the new
// value, with no Lock.
func (p *${thetype^}) UnsafeInc() $thetype {
    return p.UnsafeAdd(1)
}

// UnsafeSub will subtract the value and return the new value, with no
// Lock.
func (p *${thetype^}) UnsafeSub(value $thetype) $thetype {
    p.value -= value
    return p.value
}
EOF
            ;;
    esac
}

usage() {
    cat <<EOF
Usage: ${0##*/} [OPTIONS]

DESCRIPTION
    Create generated.go.

OPTIONS
    -h, --help        Display this help message
        --no-color    Disable colorized output

EOF
    exit "$1"
}

declare -a args
unset help
color="true"

# Parse command line options
while [[ $# -gt 0 ]]; do
    case "$1" in
        "--") shift; args+=("$@"); break ;;
        "-h"|"--help") help="true" ;;
        "--no-color") unset color ;;
        *) args+=("$1") ;;
    esac
    case "$?" in
        0) ;;
        1) shift ;;
        *) usage $? ;;
    esac
    shift
done
[[ ${#args[@]} -eq 0 ]] || set -- "${args[@]}"

# Help info
[[ -z $help ]] || usage 0

# Check for missing dependencies
declare -a deps
deps+=("go")
check_deps

# Check for valid params
[[ $# -eq 0 ]] || usage 1

file="generated.go"

cat >"$file" <<EOF
// Code generated by ${0#./}; DO NOT EDIT.
package safety

import "sync"

$(
    for thetype in \
        bool \
        float32 float64 \
        int int8 int16 int32 int64 \
        string \
        uint uint8 uint16 uint32 uint64
    do
        generate_type_funcs $thetype
    done; unset thetype
)
EOF

go fmt "$file" >/dev/null
