#!/usr/bin/env zsh

setopt localoptions extendedglob clobber
integer idx is_locked counter=0
local name busyfile busywith

#
# Helper functions
#

# FUNCTION: __zconvey_pinfo {{{
function __zconvey_pinfo() {
    print -- "\033[0;32m$*\033[0m";
}
# }}}
# FUNCTION: __zconvey_pinfo2 {{{
function __zconvey_pinfo2() {
    print -- "\033[0;33m$*\033[0m";
}
# }}}
# FUNCTION: __zconvey_get_name_of_id {{{
function __zconvey_get_name_of_id() {
    local id="$1"

    REPLY=""
    local f="$ZCONVEY_NAMES_DIR/${id}.name"
    if [[ -e "$f" ]]; then
        REPLY=${(f)"$(<$f)"}
        REPLY="${REPLY#:}"
        REPLY="${REPLY%:}"
        return 0
    fi

    return 1
}
# }}}
# FUNCTION: __zconvey_is_session_active {{{
function __zconvey_is_session_active() {
        setopt localoptions extendedglob
        local res idx="$1"

        if [[ "$idx" != <-> || "$idx" = "0" || "$idx" -gt "100" ]]; then
            __zconvey_pinfo "Incorrect sesion ID occured: $idx"
            return 2
        fi

        if [[ "$idx" = "$ZCONVEY_ID" ]]; then
            # Return true - current session is active
            return 0;
        fi

        integer is_locked=0
        local idfile="$ZCONVEY_LOCKS_DIR/zsh_nr${idx}" tmpfd

        if [[ -e "$idfile" ]]; then
            # Use zsystem only if non-blocking call is available (Zsh >= 5.3)
            if [[ "${ZCONVEY_CONFIG[use_zsystem_flock]}" = "1" ]]; then
                zsystem 2>/dev/null flock -t 0 -f tmpfd "$idfile"
                res="$?"
            else
                exec {tmpfd}>"$idfile"
                "${ZCONVEY_REPO_DIR}/myflock/flock" -nx "$tmpfd"
                res="$?"

                # Close the fd immediately - unlocking if gained lock
                exec {tmpfd}>&-
            fi

            if [[ "$res" = "101" || "$res" = "1" || "$res" = "2" ]]; then
                is_locked=1
            elif [[ "${ZCONVEY_CONFIG[use_zsystem_flock]}" = "1" ]]; then
                # Close the lock immediately
                zsystem flock -u "$tmpfd"
            fi
        fi

        return $(( 1-is_locked ))
}
# }}}
# FUNCTION: __zconvey_usage_zc-bg-notify {{{
__zconvey_usage_zc-bg-notify() {
    __zconvey_pinfo2 "Sends notification to all unbusy sessions (also busy when -f passed)"
    __zconvey_pinfo "Usage: zc-bg-notify [-h|--help] [-f|--force] [-a|--ask] [-q|--quiet] [-v|--verbose] {notification-text}"
    print -- "-h/--help                - this message"
    print -- "-q/--quiet               - don't output status messages about target sessions"
    print -- "-v/--verbose             - output more status messages"
    print -- "-a/--ask                 - ask for notification text (if not provided)"
    print -- "-f/--force               - send also to busy sessions; notification can time-out"
}
# }}}

#
# Send function
#

# FUNCTION __zconvey_zc {{{
function __zconvey_zc() {
    local id="$1" ntext="$2" ts

    # Obtain current time stamp
    [[ "${+modules}" = 1 && "${modules[zsh/datetime]}" != "loaded" && "${modules[zsh/datetime]}" != "autoloaded" ]] && zmodload zsh/datetime 2>/dev/null
    (( ${+modules} == 0 )) && zmodload zsh/datetime 2>/dev/null
    (( ${+EPOCHSECONDS} )) && ts="$EPOCHSECONDS" || ts="$(date +%s )"

    local fd datafile="${ZCONVEY_IO_DIR}/${id}.io"
    local lockfile="${datafile}.lock"
    { echo "PID $$ is sending command" >! "$lockfile"; } 2>/dev/null

    # 1. Zsystem lock with timeout (2 seconds)
    if (( ${ZCONVEY_CONFIG[use_zsystem_flock]} > 0 )); then
        (( ${verbose} )) && print "Will use zsystem flock..."
        if ! zsystem flock -t 2 -f fd "$lockfile"; then
            __zconvey_pinfo2 "Communication channel of session $id is busy, could not send"
            exit 1
        fi
    # 2. Provided flock binary (two calls)
    else
        (( ${verbose} )) && print "Will use provided flock..."
        exec {fd}>"$lockfile"
        "${ZCONVEY_REPO_DIR}/myflock/flock" -nx "$fd"
        if [ "$?" = "101" ]; then
            (( ${verbose} )) && print "First attempt failed, will retry..."
            LANG=C sleep 1
            "${ZCONVEY_REPO_DIR}/myflock/flock" -nx "$fd"
            if [ "$?" = "101" ]; then
                __zconvey_pinfo2 "Communication channel of session $id is busy, could not send"
                exit 1
            fi
        fi
    fi

    # >> - multiple messages can be accumulated
    print -r -- "n$ts $ntext" >> "$datafile"

    # Release the lock by closing the lock file
    exec {fd}>&-

    if (( ${quiet} == 0 )); then
        __zconvey_get_name_of_id "$id"
        if [[ -n "$REPLY" ]]; then
            print "Notification sent to session $id (name: $REPLY)"
        else
            print "Notification sent to session $id"
        fi

        integer is_locked
        __zconvey_is_session_active "$id" && is_locked=1 || is_locked=0
        (( is_locked == 0 )) && print "The session is ABSENT, the message will time out"
    fi
}
# }}}

#
# Options
#

# {{{

local -A opthash
zmodload zsh/zutil
zparseopts -D -A opthash h -help q -quiet v -verbose f -force a -ask || { __zconvey_usage_zc-bg-notify; return 1; }

integer verbose=0 quiet=0 force=0 ask=0

# Help
(( ${+opthash[-h]} + ${+opthash[--help]} )) && { __zconvey_usage_zc-bg-notify; return 0; }

# ASK (requests command and/or ID)
(( ask = ${+opthash[-a]} + ${+opthash[--ask]} ))
if [ "$ask" = "0" ]; then
    local ask_setting
    zstyle -b ":plugin:zconvey" ask ask_setting || ask_setting="no"
    [ "$ask_setting" = "yes" ] && ask=1 || ask=0
fi

# VERBOSE, QUIET, FORCE
(( verbose = ${+opthash[-v]} + ${+opthash[--verbose]} ))
(( quiet = ${+opthash[-q]} + ${+opthash[--quiet]} ))
(( force = ${+opthash[-f]} + ${+opthash[--force]} ))

# }}}

#
# Configuration
#

# {{{

zmodload zsh/system || typeset -g no_zsystem=1
zsystem supports flock || typeset -g no_zsystem=1

typeset -g ZCONVEY_REPO_DIR ZCONVEY_CONFIG_DIR ZCONVEY_IO_DIR ZCONVEY_LOCKS_DIR ZCONVEY_NAMES_DIR ZCONVEY_OTHER_DIR
typeset -gA ZCONVEY_CONFIG

: ${ZCONVEY_REPO_DIR:=$HOME/.zinit/plugins/zdharma---zconvey}    # TODO: add configuration file
: ${ZCONVEY_CONFIG_DIR:=${XDG_CONFIG_HOME:-$HOME/.config}/zconvey}
: ${ZCONVEY_IO_DIR:=${ZCONVEY_CONFIG_DIR}/io}
: ${ZCONVEY_LOCKS_DIR:=${ZCONVEY_CONFIG_DIR}/locks}
: ${ZCONVEY_NAMES_DIR:=${ZCONVEY_CONFIG_DIR}/names}
: ${ZCONVEY_OTHER_DIR:=${ZCONVEY_CONFIG_DIR}/other}

autoload is-at-least

if [[ "$no_zsystem" != 1 ]]; then
    if ! is-at-least 5.3; then
        if [[ -e ${ZCONVEY_REPO_DIR}/myflock/flock ]]; then
            # Use, but not for acquire
            ZCONVEY_CONFIG[use_zsystem_flock]="2"
        else
            print "Couldn't find ${ZCONVEY_REPO_DIR}/myflock/flock, and Zsh is older than 5.3, cannot notify"
            exit 1
        fi
    else
        # Use for all operations
        ZCONVEY_CONFIG[use_zsystem_flock]="1"
    fi
else
    if [[ -e ${ZCONVEY_REPO_DIR}/myflock/flock ]]; then
        # Use flock for all operations
        ZCONVEY_CONFIG[use_zsystem_flock]="0"
    else
        print "Couldn't find ${ZCONVEY_REPO_DIR}/myflock/flock, and Zsh doesn't have \`zsystem flock', cannot notify"
        exit 1
    fi
fi

# }}}

#
# Input establishing
#

# {{{

local -a args
args=( "$@" )

local ntext="${args[*]}"

# Resupply missing input if requested
if (( $ask )); then
    # Supply command?
    if [[ -z "$ntext" ]]; then
        vared -cp "Enter notification-text to send: " ntext
        ntext="${ntext##[[:space:]]#}"
        ntext="${ntext%%[[:space:]]#}"
        if [ -z "$ntext" ]; then
            __zconvey_pinfo "No command enterred, exiting"
            return 0
        fi
    fi
fi

if [ -z "$ntext" ]; then
    __zconvey_pinfo "No command provided, aborting"
    return 1
fi

# }}}

#
# Business logic
#

# {{{

for (( idx = 1; idx <= 100; idx ++ )); do
    name=""
    busywith=""
    is_locked=0

    __zconvey_is_session_active "$idx" && is_locked=1 || is_locked=0

    if (( is_locked )); then
        busyfile="$ZCONVEY_OTHER_DIR/${idx}.busy"
        if [[ -e "$busyfile" && "$idx" != "$ZCONVEY_ID" ]]; then
            busywith="\033[1;33m$(<$busyfile)\033[0m"

            __zconvey_get_name_of_id "$idx"
            if (( force )); then
                counter+=1
                (( quiet )) || print "\033[1;35mSending to busy\033[0m session $idx (name: $REPLY, busy with: $busywith)"
                __zconvey_zc "$idx" "$ntext"
            else
                (( quiet )) || print "Session $idx (name: $REPLY) busy ($busywith)"
            fi
        elif [[ "$idx" != "$ZCONVEY_ID" ]]; then
            counter+=1
            if (( verbose )); then
                __zconvey_zc "$idx" "$ntext"
            else
                __zconvey_zc "$idx" "$ntext"
            fi
        fi
    fi
done

(( quiet )) || __zconvey_pinfo "Sent command to $counter other sessions"

# }}}

return 0

# vim:ft=zsh
