# Text formatting utilities
bold=$(tput bold)
underline=$(tput sgr 0 1)
reset=$(tput sgr0)
purple=$(tput setaf 171)
red=$(tput setaf 1)
green=$(tput setaf 76)
tan=$(tput setaf 3)
blue=$(tput setaf 38)
header() {
    printf "\n${bold}${purple}==========  %s  ==========${reset}\n" "$@"
}
arrow() {
    printf " ➜ %s\n" "$@"
}
success() {
    printf "${green} ✔ %s${reset}\n" "$@"
}
error() {
    printf "${red} ✖ %s${reset}\n" "$@"
}
warning() {
    printf "${tan} ➜ %s${reset}\n" "$@"
}
underline() {
    printf "${underline}${bold}%s${reset}\n" "$@"
}
bold() {
    printf "${bold}%s${reset}\n" "$@"
}
note() {
    printf "${underline}${bold}${blue}Note:${reset}  ${blue}%s${reset}\n" "$@"
}

# Browser interaction utilities
function browse() {
    open -na "$DEFAULT_BROWSER" --args "$1"
}
function google() {
    browse "https://www.google.com/search?q=$*"
}
function stackoverflow() {
    browse "https://www.google.com/search?q=site:stackoverflow.com $*"
}
function github() {
    browse "https://github.com/search?q=$*"
}
function hacker() {
    browse "https://hn.algolia.com/?sort=byDate&query=$*"
}
function gmail() {
    browse "https://mail.google.com/mail/u/0"
}
function gmail2() {
    browse "https://mail.google.com/mail/u/1"
}
function cicdboard() {
    browse "$JIRA_URL/secure/RapidBoard.jspa?rapidView=457&view=planning.nodetail"
}
function cicddashboard() {
    browse "$JIRA_URL/secure/Dashboard.jspa?selectPageId=13131"
}
function calendar() {
    browse "https://calendar.google.com/calendar/r?tab=mc"
}
function asana() {
    browse "https://app.asana.com"
}
function confluencetasks() {
    browse "$CONFLUENCE_URL/plugins/inlinetasks/mytasks.action"
}
function trello_web() {
    browse "$TRELLO_BOARD_URL"
}
function bookmarks() {
    browse "https://github.com/MorganGeek/bookmarks/blob/master/README.md"
}
function spotify() {
    browse "https://open.spotify.com/search/$*"
}
function lob() {
    browse "https://lobste.rs"
}
function archive() {
    browse "https://web.archive.org/web/*/$*"
}

# Jira utilities
function issues() {
    jira issue jql "status = Open AND text ~ \"$*\" ORDER BY Created DESC"
}
function istherenewissues() {
    LASTISSUE=$(newissues | sed $'s,\x1b\\[[0-9;]*[a-zA-Z],,g' | awk 'FNR==2{print $2}')
    if [[ -f "$HOME/.newjiraissue" ]]; then
        previous_jira_issue=$(\cat "$HOME/.newjiraissue" | sed $'s,\x1b\\[[0-9;]*[a-zA-Z],,g') # the sed part is for removing output colors
        if [ "$LASTISSUE" != "$previous_jira_issue" ]; then
            newissues
        else
            success "no new issue"
        fi
    fi
    echo "$LASTISSUE" >"$HOME/.newjiraissue"
}

# Prolog / Logtalk
function logtalk() {
    path_swilgt=$(find /usr/local/Cellar/logtalk -name "*swilgt.sh" 2>/dev/null)
    sh "$path_swilgt"
}

# History / Aliases helpers
function top_commands() {
    local filter="$1"
    local max_results=${2:-'50'}
    history | \cat | awk '{CMD[$2]++;count++;}END { for (a in CMD)print CMD[a] " " CMD[a]/count*100 "% " a; }' | grep -v "./" | column -c3 -s " " -t | grep "$filter" | sort -nr | nl | head "-n$max_results"
}
function top_commands_full() {
    local filter="$1"
    local max_results=${2:-'50'}
    history | \cat | awk '{$1=$1};1' | sed 's/^[0-9 TAB]*//g' | awk '{CMD[$0]++;count++;}END { for (a in CMD)print CMD[a] " " CMD[a]/count*100 "%\t" a; }' | grep "$filter" | sort -nr | nl | head "-n$max_results"
}
# Where is a function defined?
function whichfunc() {
    whence -v $1
    type -a $1
}
function suggest_aliases() {
    local search_input_size=${1:-'50'}
    header "alias recommendations"
    while read -r line; do
        local matching_aliases=$(ag "$line")
        if [ ! -z "$matching_aliases" ]; then
            success "there is an alias for $line :"
            while read -r alias_line; do
                arrow "$alias_line"
            done < <(echo "$matching_aliases")
            echo
        else
            warning "no alias for $line"
        fi
    done < <(top_commands_full "" "$search_input_size" | awk '{ $1=""; $2=""; $3="";  print}' | awk 'NF' | awk '{$1=$1};1' | awk -v COUNT=1 'NF>COUNT' | head "-$search_input_size")
}

# Web Crawling
function aboutpage() {
    year=$(echo "$*" | egrep -Eo '\b[[:digit:]]{4}\b' | head -n1)
    if [ -z "$year" ]; then
        year=$(curl -sSL "$*" | tr '<' '\r' | \egrep -i "date|datetime" -A 1 | \grep -Eo '\b[[:digit:]]{4}\b' | head -n1)
    fi
    author=$(curl -sSL "$*" | tr '<' '\r' | \egrep -i "author" -A 1 | \grep -Eo '([A-Z][A-Za-z]+\s([A-Za-z ]+)*)' | head -n1)
    title=$(curl -sSL "$*" | tr '<' '<\n' | \grep title -A 1 | head -n1 | sed -E 's/.*<title>(.*)<\/title>.*/\1/' | sed "s/ [^[:alnum:]]*$author//")
    yearint=$(($year + 0))
    currentyear=$(echo $(date +"%Y"))
    if [ ! -z "$author" ]; then
        echo "by $author"
    fi
    if [ ! -z "$title" ]; then
        echo "-> $title"
    fi
    if [[ $yearint -ge 1970 && $yearint -le $currentyear ]]; then
        echo "$yearint"
    fi
    if [ ! -z "$title" ] && [ ! -z "$author" ] && [[ $yearint -ge 1970 && $yearint -le $currentyear ]]; then
        echo "[$author]($*) - ($yearint) $title"
    fi
}

# Extract a column from a tabular output
# via https://blog.developer.atlassian.com/ten-tips-for-wonderful-bash-productivity/
function col() {
    awk -v col=$1 '{print $col}'
}
# Skip first x words in line
# via https://blog.developer.atlassian.com/ten-tips-for-wonderful-bash-productivity/
function skip() {
    n=$(($1 + 1))
    cut -d' ' -f$n-
}

# Code search / stats helpers
cmd_loc="find . -type f \( \
        -name '*.py' \
        -o -name '*.rb' \
        -o -name '*.go' \
        -o -name '*.java' \
        -o -name '*.kt' \
        -o -name '*.c' -o -name '*.h' \
        -o -name '*.js' \
        -o -name '*.tsx' \
        -o -name '*.sh' \
        -o -name '*.md' \
        -o -name '*.xml' \
        -o -name '*.yaml' -o -name '*.yml' \
        -o -name '*.groovy' \
        -o -name '*.gradle' \
        -o -name '*.properties' \
        \) -exec \cat \{\} \; | LANG=C LC_CTYPE=C sed -e 's/^[ \t]*//;s/[ \t]*$//'"
# Unique lines of code
# Via https://text.causal.agency/004-uloc.txt
function uloc() {
    eval "$cmd_loc | LANG=C LC_CTYPE=C sort -u | wc -l"
}
# Top lines of code
function toploc() {
    eval "$cmd_loc | LANG=C LC_CTYPE=C cut -c 1-100 | LANG=C LC_CTYPE=C sort | uniq -c | LANG=C LC_CTYPE=C sort -nr | head -50"
}
function how_in() {
    where="$1"
    shift
    IFS=+ curl "cht.sh/${where}/$*"
}
# File name references in file
function filerefs() {
    \grep -Eo "\b([a-zA-Z.]+)\.([a-z]{2,4}\b)" "$1" \
        | sort -u \
        | egrep -vi "\.com|\.git|\.io|\net|\.org|\.se|\.me|\.fr|contributing\.md"
}
function invalid_file_refs() {
    while read -r file_ref; do
        arrow "processing $file_ref ..."
        find . -name "$file_ref" 1>/dev/null
        local exit_status=$?
        if [ $exit_status -eq 1 ]; then
            warning "$file_ref does not exist in the project"
        else
            success "$file_ref was found in the project"
        fi
        arrow "checking if $file_ref is present in the source code..."
        source_refs=$(\grep "$file_ref" * -r -l | grep -v "$1" | wc -l 2>/dev/null | trim)
        if [ "$source_refs" -eq 0 ]; then
            error "$file_ref was not found in source code"
        else
            arrow "searching for $file_ref references in soure code..."
            while read -r source_ref; do
                success "$file_ref was found in $source_ref"
            done < <(\grep "$file_ref" * -r -l)
        fi
    done < <(filerefs "$1")
}

# File stats helpers
# Find files bigger than X size and sort them by size
function biggerthan() {
    find . -size "+$*" -type f -print0 | xargs -0 ls -Ssh | sort -z
}
# To automatically ls when changing directory
function cd() {
    builtin cd "$@" && ls -latr
}
function mouse() {
    case "$(uname -s)" in
    Darwin)
        sh ~/.scripts/mouse_bluetooth.sh
        ;;
    esac
}

# Conversion
function epub2pdf() {
    local epub_file_name=$(echo $1 | sed 's/.epub$/.pdf/')
    ebook-convert "$1" "$epub_file_name"
}

# Information gathering
function meteo() {
    curl "fr.wttr.in/$*"
}
function rate() {
    curl "http://rate.sx/$*"
}

# Uploaders
function transfer() {
    # check arguments
    if [ $# -eq 0 ]; then
        warning "No arguments specified. Usage:\necho transfer /tmp/test.md\ncat /tmp/test.md | transfer test.md"
        return 1
    fi

    # get temporarily filename, output is written to this file show progress can be showed
    tmpfile=$(mktemp -t transferXXX)

    # upload stdin or file
    file=$1

    if tty -s; then
        basefile=$(basename "$file" | sed -e 's/[^a-zA-Z0-9._-]/-/g')

        if [ ! -e $file ]; then
            error "File $file doesn't exists."
            return 1
        fi

        if [ -d $file ]; then
            # zip directory and transfer
            zipfile=$(mktemp -t transferXXX.zip)
            cd $(dirname $file) && zip -r -q - $(basename $file) >>$zipfile
            curl --progress-bar --upload-file "$zipfile" "https://transfer.sh/$basefile.zip" >>$tmpfile
            rm -f $zipfile
        else
            # transfer file
            curl --progress-bar --upload-file "$file" "https://transfer.sh/$basefile" >>$tmpfile
        fi
    else
        # transfer pipe
        curl --progress-bar --upload-file "-" "https://transfer.sh/$file" >>$tmpfile
    fi
    # cat output link
    cat $tmpfile

    # cleanup
    rm -f $tmpfile
}

function checkov() { docker run -i --rm -v "$(pwd):/tf" bridgecrew/checkov -d /tf "$@"; }
function vimat() {
    vim +/$1 $2
}
function httperr() {
    curl -s "https://http.cat/$1" | imgcat
}

# Config / Infra as code
function terraform-compliance() { docker run --rm -v "$(pwd):/target" -i -t eerkunt/terraform-compliance "$@"; }
function terragrunt_color() {
    BOLD=$(tput bold)
    BLACK=$(tput setaf 0)
    RED=$(tput setaf 1)
    GREEN=$(tput setaf 2)
    YELLOW=$(tput setaf 3)
    BLUE=$(tput setaf 4)
    CYAN=$(tput setaf 6)
    RESET=$(tput sgr0)
    REDBOLD=${RED}${BOLD}
    REDRESET=${RESET}${RED}
    BLUEBOLD=${BLUE}${BOLD}
    BLUERESET=${RESET}${BLUE}

    terragrunt ${*} 2>&1 | sed \
        -e "s/\(\\[terragrunt\\] \\[.*\\]\)\( [0-9\\/]* [0-9:]*\) /${BLUEBOLD}\1${BLUERESET}\2${RESET} /" \
        -e "s/\(\\[terragrunt\\]\)\( [0-9\\/]* [0-9:]*\) /${BLUEBOLD}\1${BLUERESET}\2${RESET} /" \
        -e "s/\(Error: \)\(.*\)/${REDBOLD}\1${REDRESET}\2${RESET}/" \
        -e "s/\(Hit multiple errors:\)/${REDBOLD}\1${RESET}/" \
        -e "s/\(exit status 1\)/${RED}\1${RESET}/" \
        -e "s/\( WARNING: \)\(.*\)/${YELLOW}${BOLD}\1${RESET}${YELLOW}\2${RESET}/" \
        -e "s/\( Running command: \)\(.*\)/\1${CYAN}\2${RESET}/" \
        -e "s/\(  *.*:  *\)\(\".*\"\)\( => \)\(\".*\"\)/${YELLOW}\1${RED}\2${BLACK}\3${GREEN}\4${RESET}/" \
        -e "s/\(  *.*:  *\".*\"\)/${GREEN}\1${RESET}/"
}
function jenkins-cli() {
    local script_location=$(find $HOME/code/jenkins-cloudbees-core -name "jenkins-cli.sh")
    eval "$script_location $*"
}

# Security / Secrets helpers
function passwords() {
    bw list items --search "$1" | jq -c '.[] | .name + " " + .login.username + ":" + .login.password + " " +  .login.uris[0].uri'
}
function password() {
    bw get password "$1"
}
function vaultgetsecret() {
    local secret=$(grep -A 500 "ANSIBLE_VAULT" "$1" | awk '{$1=$1;print}' | \grep -Eo "^[0-9a-z^ ]+$")
    local secret_string=$(echo "\$ANSIBLE_VAULT;1.1;AES256\n$secret")
    echo "$secret_string" | awk '{$1=$1;print}' | ansible-vault decrypt --vault-password-file=$VAULT_PASSWORD_FILE
}

### Git helpers
function backupgithub() {
    cd "$REPO_PATH"
    curl -sL "https://api.github.com/users/$1/repos" | jq -r '.[] | .ssh_url' | xargs -n1 git clone --mirror --no-hardlinks
}
function gitydiff() {
    local path_to_file="$1"
    git show "HEAD:$path_to_file" | colordiff -y - "$path_to_file"
}
function gcrb() {
    branch=$1
    git checkout -b $branch origin/$branch
}
function installhooks() {
    pre-commit install --install-hooks --overwrite --allow-missing-config
}
function copyhooks() {
    cp -f ~/.git-template/.pre-commit-config.yaml ./
    installhooks
    runhooks
}
function git_listobjectsbysize() {
    tempFile=$(mktemp)
    IFS=$'\n'
    for commitSHA1 in $(git rev-list --all); do
        git ls-tree -r --long "$commitSHA1" >>"$tempFile"
    done

    # sort files by SHA1, de-dupe list and finally re-sort by filesize
    sort --key 3 "$tempFile" |
        uniq |
        sort --key 4 --numeric-sort --reverse
    # remove temp file
    rm -f "$tempFile"
}
function setorigin() {
    gra origin "$1" 2>/dev/null
    grset origin "$1"
    if [[ "$1" =~ *"$COMPANY_NAME"* ]]; then
        copyhooks
    fi
}
function gitpushcurrentremote() {
    gitpushallremote "$(git_current_branch)"
}
function gitpushallremote() {
    local param_branch="$1"
    grv
    grv | grep push | awk '{print $1}' | while read -r remote; do
        if [ -z "$param_branch" ]; then
            arrow "pushing all branches to $remote"
            git push --all "$remote"
        else
            arrow "pushing $param_branch to $remote"
            git push "$remote" "$param_branch"
        fi
    done
}
function clone() {
    local folder=$(basename $1 | sed 's/\.git.*//g')
    arrow "git project identified as $folder"
    arrow "cloning repository ..."
    if gcls "$1"; then
        success "repository cloned"
        if [[ -n "$folder" ]]; then
            cd "$folder" || exit
            if [[ "$1:u" =~ *"$COMPANY_NAME:u"* ]]; then
                arrow "copying pre-commit hooks ..."
                copyhooks
            fi
        else
            error "unable to change current directory to : $folder"
        fi
    else
        error "unable to clone repository url : $1"
    fi
}
function git-project() {
    if [ -d "$REPO_PATH" ]; then
        REPO_PATH="$(pwd)"
    fi
    local preview='lsd --color always --icon always --group-dirs first {}'
    local dir=$(find "$REPO_PATH" -maxdepth 3 -type d -name ".git" | sed 's#.git$##' | fzf --select-1 --query="$*" --preview "$preview")
    if [[ -n "$dir" ]]; then
        cd "$dir" || exit
    fi
}
function gitignorefor() {
    local language=${1:-''}
    if [ ! -d "$HOME/Code/gitignore" ]; then
        arrow "cloning https://github.com/github/gitignore into $REPO_PATH/gitignore"
        git clone "https://github.com/github/gitignore $_"
    fi
    local gitignore_file=$(fd "$language" "$REPO_PATH/gitignore" | head -n1)
    if [ ! -z "$gitignore_file" ]; then
        success "matched gitignore file : $gitignore_file"
        if [ ! -f "$(pwd)/.gitignore" ]; then
            arrow "you don't have a $(pwd)/.gitignore file, but that's not an issue :-) ..."
        fi
        arrow "copying the file content to your $(pwd)/.gitignore file"
        adhoc blockinfile -a "block='{{ lookup('file', '$gitignore_file') }}' dest='$(pwd)/.gitignore' create=yes"
    else
        error "no gitignore file found for $language"
    fi
}

# Miscellaneous helpers
function colorpic() {
    local picture_url="$1"
    arrow "Colorizing $picture_url"
    local result_url=$(\curl -F "image=@$picture_url" -H "api-key:$COLORPIC_APIKEY" https://api.deepai.org/api/colorizer -s | jq '.output_url' | strings)
    success "Generated $result_url"
    arrow "Display in progress..."
    eval "\curl -s $result_url | imgcat"
}

function bookmarkadd() {
    adhoc lineinfile -a "path=$HOME/Code/bookmarks/README.md insertafter='"$1"' line='* "$2"'"
}
function rssadd() {
    adhoc lineinfile -a "path=~/.newsboat/urls line='"$1"'"
    newsboat
}

# Via https://stackoverflow.com/a/58598185/2309958
# capture the output of a command so it can be retrieved with ret
function cap () { tee /tmp/capture.out}
# return the output of the most recent command that was captured by cap
function ret () { \cat /tmp/capture.out }

# Package / Dependencies management helpers
function adhocbis() {
    local ansible_output=$(adhoc "$*")
    echo $ansible_output | sed 's/127.0.0.1.*SUCCESS/WOKE/g'
}
function brewadd() {
    brew install "$1"
    adhoc lineinfile -a "path=~/Brewfile line='brew \"$1\"'"
}
function pipadd() {
    pip install "$1"
    pip freeze >"$HOME/requirements.txt"
}
function goadd() {
    adhoc lineinfile -a "path=~/.scripts/godeps.sh line='go get -u -v $1'"
    go get -u -v "$1"
}


# Make a directory and cd to it
function take() {
    mkdir -p "$@" && cd "${@:$#}"
}

# Date / Time management helpers
function endofday() {
    local planned_end=$(moro status 2>&1 | \grep -Eo "Working until ([0-9:]+) will make.*" | uniq | \grep -Eo "([0-9]+:[0-9]+)")
    local max_hour="$planned_end"
    local min_hour=$(current_time)
    if [ -z "$planned_end" ]; then
        local clockout=$(moro report 2>&1 | \grep -Eo "Clock out.*([0-9:]+)" | \grep -Eo "([0-9]+:[0-9]+)")
        max_hour="$clockout"
    fi
    if is_earlier "$min_hour" "$max_hour"; then
        arrow "you should keep working, it's only $min_hour while you should work until $max_hour"
        moro status
    else
        warning "you should stop working now because it's later than $max_hour"
        moro report
    fi
}
function convtimetodate() {
    date -j -f '%H:%M' "$1" +'%Y/%m/%d %H:%M'
}
function convtimetotimestamp() {
    date -j -f '%H:%M' "$1" +'%s'
}
function is_earlier() {
    local first=$(convtimetotimestamp "$1")
    local second=$(convtimetotimestamp "$2")
    if [ "$second" -gt "$first" ]; then
        true
    else
        false
    fi
}

function dl_stopwords() {
    curl -Lks https://raw.githubusercontent.com/MorganGeek/bookmarks/master/stopwords.txt -o "$HOME/stopwords.txt"
}
function file_getwords() {
    dl_stopwords
    \cat "$1" | tr '[:upper:]' '[:lower:]' | \grep -o -E '\w{3,}' | \grep --invert-match --word-regexp --fixed-strings --file="$HOME/stopwords.txt" | \sed 's/s$//g' | \sed 's/ing$//g' | sort_count
}
function file_getpairs() {
    dl_stopwords
    \cat "$1" | filter_pairs
}
function file_dups() {
    \cat "$1" | sort_count
}
function foreach_run() {
    find . -name "$1" -exec "$2" {} \;
}
# input should be something like : 1-10 to generate one number between 1 and 10
function chance() {
    [[ $(shuf -i "$1" -n 1) == 1 ]]
}
function runiflucky() {
    if chance "1-10"; then
        if alias "$1" 2>/dev/null || (compgen -A function | grep "$1" 1>/dev/null && compgen -A function "$1" 1>/dev/null); then
            eval "$1"
        fi
    fi
}