#!/bin/bash

installed() {
    command -v "$1" >/dev/null 2>&1
}

installed2() {
    dpkg --list | grep -qw "ii  $1"
}

apt_install() {
    sudo apt install -y --autoremove "$*"
}

require() {
    sw=()
    for package in "$@"; do
        if ! installed "$package" && ! installed2 "$package"; then
            sw+=("$package")
        fi
    done
    if [ ${#sw[@]} -gt 0 ]; then
        info "This packages will be installed in your system:\n${sw[*]}"
        apt_install "${sw[*]}"
        [ $? -gt 0 ] && die "installation cancelled" 201
    fi
}

require2() {
    sw=()
    for package in "$@"; do
        if ! installed "$package" && ! installed2 "$package"; then
            sw+=("$package")
        fi
    done
    if [ ${#sw[@]} -gt 0 ]; then
        die "This packages must be installed in your system:\n${sw[*]}" 200
    fi
}

title() {
    [ "$1" ] && title="$1" || title="$(grep -m 1 -oP "(?<=^##makedesc:\s).*$" ${BASH_SOURCE[1]})"
    info
    info "==============================================="
    info "$title"
    info "==============================================="
    info
}

unpak_targz() {
    require tar
    tar -xzf "$1" -C "$2"
}

symlink() {
    ln -sf "$1" "$2"
}

download() {
    require wget
    wget "$1" -O "$2"
}

clone() {
    require git
    git clone $*
}

clone_quick() {
    require git
    git clone $* --depth=1 --single-branch
}

abspath() {
    echo $(realpath -q "${1/#\~/$HOME}")
}

is_writable() {
    [ -w "$(abspath $1)" ]
}

is_dir() {
    [ -d "$(abspath $1)" ]
}

is_file() {
    [ -f "$(abspath $1)" ]
}

is_function() {
    declare -F "$1" > /dev/null
}

regex_match() {
    printf "%s" "$1" | grep -qP "$2"
}

in_array() {
	local find=$1
    shift
    for e in "$@"; do
        [[ "$e" == "$find" ]] && return 0
    done
    return 1
}

implode() {
    local d=${1-}
    local f=${2-}
    if shift 2; then
        printf %s "$f" "${@/#/$d}"
    fi
}

IINFO="( i )"
INOTE="( * )"
IWARN="( # )"
IERROR="( ! )"
IFATAL="( @ )"
ISUCCESS="( ! )"
IASK="( ? )"
IDEBUG="(DBG)"
IVRB="( + )"

BOLD="\e[1m"
DIM="\e[2m"
NOTBOLD="\e[22m" # sometimes \e[21m
NOTDIM="\e[22m"
NORMAL="\e[20m"
RESET="\e[0m"

FRESET="\e[39m"
FBLACK="\e[30m"
FWHITE="\e[97m"
FRED="\e[31m"
FGREEN="\e[32m"
FYELLOW="\e[33m"
FBLUE="\e[34m"
FLRED="\e[91m"
FLGREEN="\e[92m"
FLYELLOW="\e[93m"
FLBLUE="\e[94m"

BRESET="\e[49m"
BBLACK="\e[40m"
BWHITE="\e[107m"
BRED="\e[41m"
BGREEN="\e[42m"
BYELLOW="\e[43m"
BBLUE="\e[44m"
BLRED="\e[101m"
BLGREEN="\e[102m"
BLYELLOW="\e[103m"
BLBLUE="\e[104m"

dt() {
    echo "[$(date +'%H:%M:%S')] "
}

ask() {
    IFS= read -rp "$(print ${BOLD}${BBLUE}${FWHITE}${IASK}${BRESET}\ ${BOLD}$1 ): " $2
}

print() {
    echo -e "$*${RESET}"
}

debug() {
    if [ "$2" ]; then
        print "${DIM}${BOLD}${RESET}${DIM}${FUNCNAME[1]:-?}():${BASH_LINENO:-?}\t$1 "
    else
        print "${DIM}${BOLD}${RESET}${DIM}$1 "
    fi
}

verbose() {
    print "${BOLD}${IVRB}${RESET}${FYELLOW} $1 "
}

info() {
    print "${BOLD}${FWHITE}${BLBLUE}${IINFO}${RESET}${FWHITE} $1 "
}

note() {
    print "${BOLD}${DIM}${FWHITE}${INOTE}${RESET} $1 "
}

success() {
    print "${BOLD}${BGREEN}${FWHITE}${ISUCCESS}${BRESET}$FGREEN $1 "
}

warn() {
    print "${BOLD}${BYELLOW}${FBLACK}${IWARN}${BRESET}${FYELLOW} Warning:${RESET} $1 "
}

error() {
    print "${BOLD}${BLRED}${FWHITE}${IERROR} Error: ${BRESET}${FLRED} $1 " >&2
}

fatal() {
    print "${BOLD}${BRED}${FWHITE}${IFATAL} FATAL: $1 " >&2
    print_stacktrace
}

die() {
    error "${1:-halted}"
    exit ${2:-100}
}

print_stacktrace() {
    STACK=""
    local i
    local stack_size=${#FUNCNAME[@]}
    debug "Callstack:"
    # for (( i=$stack_size-1; i>=1; i-- )); do
    for (( i=1; i<$stack_size; i++ )); do
        local func="${FUNCNAME[$i]}"
        [ x$func = x ] && func=MAIN
        local linen="${BASH_LINENO[$(( i - 1 ))]}"
        local src="${BASH_SOURCE[$i]}"
        [ x"$src" = x ] && src=non_file_source
        debug "   at $func $src:$linen"
    done
}