#!/usr/bin/env bash

set -exE -o errtrace

dump_cache() {
    printf '\nTest failure: contents of cache:\n\n' >&2
    cat "$l_out" >&2
}

expect_fail() {
    set +e
    "$@"
    local status=$?
    set -e
    (( status != 0 ))
}

if ! (( NO_PID_NAMESPACE )) && (( EUID )); then
    export _UNSHARED=1
    exec unshare --user --map-root-user --pid --mount --fork --mount-proc "$0" "$@"
fi

if (( _UNSHARED )); then
    # Get our own tmp
    mount -t tmpfs unshared_tmp /tmp
fi

cd "${0%/*}"/..
make clean debug
cd src

export PATH=$PWD:$PATH

expect_fail env CM_CONFIG=relative CM_DIR="$(mktemp -d)" clipmenu
expect_fail env CM_DIR=relative CM_CONFIG="$(mktemp)" clipmenu

export CM_CONFIG=$(mktemp)
export CM_DIR=$(mktemp -d)
export CM_DEBUG=1
export ASAN_OPTIONS='halt_on_error=1:abort_on_error=1'
export UBSAN_OPTIONS='halt_on_error=1:abort_on_error=1'
uid=$(id -u)
[[ "$(clipctl version)" == 7 ]]
[[ "$(clipctl cache-dir)" == "$CM_DIR/clipmenu.7.$uid" ]]

launcher=$(mktemp)
l_out=$(mktemp)

trap dump_cache ERR

cat > "$launcher" << EOF
#!/bin/bash

cat > "$l_out"

if [[ \$SELECT ]]; then
    printf '%s\\n' "\$SELECT"
fi
EOF
chmod a+x "$launcher"
export CM_LAUNCHER="$launcher"

tilde_home=$(mktemp -d)
tilde_dir="$tilde_home/clipmenu_tilde"
mkdir -p "$tilde_dir"
HOME="$tilde_home" CM_DIR="~/clipmenu_tilde" CM_CONFIG="$(mktemp)" \
    clipmenu || true
[[ -d "$tilde_dir/clipmenu.7.$uid" ]]
rm -rf "$tilde_home"

tilde_conf_home=$(mktemp -d)
printf 'max_clips nope\n' > "$tilde_conf_home/bad.conf"
err=$(HOME="$tilde_conf_home" CM_CONFIG="~/bad.conf" CM_DIR="$(mktemp -d)" \
    clipmenu 2>&1 || true)
[[ "$err" == *"for max_clips"* ]]
rm -rf "$tilde_conf_home"

kill_background_jobs() {
    local -a bg
    readarray -t bg < <(jobs -p)
    (( ${#bg[@]} )) && kill -- "${bg[@]}" 2>/dev/null
}

primary() {
    printf '%s' "${1?}" | xsel -p
}

trap 'kill_background_jobs' EXIT

settle() {
    sleep 0.2
}

long_settle() {
    sleep 2
}

check_nr_clips() {
    if [[ $SELECT ]]; then
        clipmenu
    else
        # On no selection, we exit 1. Avoid masking real problems by only doing
        # it when no selection is specified.
        clipmenu || true
    fi
    (( $(wc -l < "$l_out") == "${1?}" ))
}

wait_nr_clips() {
    local want=${1?}
    local deadline=$((SECONDS + 5))

    while (( SECONDS < deadline )); do
        if check_nr_clips "$want"; then
            return 0
        fi
        sleep 0.1
    done

    check_nr_clips "$want"
}

if ! (( USE_CURRENT_DISPLAY )); then
    export DISPLAY=:1911
    Xvfb "$DISPLAY" &
    sleep 2
fi

# Clear selections
xsel -bc
xsel -pc
xsel -sc

clipmenud &
clipmenud_pid=$!
settle

# Should be empty
check_nr_clips 0

primary foo
settle

check_nr_clips 1

# Put the same one repeatedly, should ignore it
primary foo
primary foo
primary foo
settle

settle
check_nr_clips 1
[[ "$(< "$l_out")" == "[1] foo" ]]

# Put possible partials, should update it
primary fooa
primary fooab
primary fooabc

# Should have replaced the old one
settle
check_nr_clips 1
[[ "$(< "$l_out")" == "[1] fooabc" ]]

primary fooab
primary fooa
primary foo

# Should have replaced the old one
settle
check_nr_clips 1
[[ "$(< "$l_out")" == "[1] foo" ]]

# Put some more content on the clipboard for testing
primary bar
primary baz
settle

check_nr_clips 3

# Nothing gets deleted, but we recognise the right clips
[[ $(clipdel a) == $'bar\nbaz' ]]
check_nr_clips 3

# Likewise but inversion
[[ $(clipdel -v a) == foo ]]
check_nr_clips 3

# Real delete with inversion
[[ $(clipdel -dv a) == foo ]]
check_nr_clips 2

# Test literal match
primary '*foo'
settle
check_nr_clips 3

[[ $(clipdel -dF '*') == '*foo' ]]
check_nr_clips 2

# Test -n
primary latest
settle
check_nr_clips 3

# Should delete the newest entry
[[ $(clipdel -dn 1) == 'latest' ]]
check_nr_clips 2

# Test -N
primary wibble
primary wobble
settle
check_nr_clips 4

# Should delete everything other than the oldest 2 entries
[[ $(clipdel -dvN 2) == $'wibble\nwobble' ]]
check_nr_clips 2

# Test clipdelmenu
primary test1
primary test2
primary test3
settle
check_nr_clips 5

# Should delete the selected entry
SELECT='[4] test2' clipdelmenu
settle
check_nr_clips 4

# Verify test2 is gone and test1, test3 remain
clipmenu || true
[[ "$(< "$l_out")" == *"test1"* ]]
[[ "$(< "$l_out")" == *"test3"* ]]
[[ "$(< "$l_out")" != *"test2"* ]]

# Delete the others
SELECT='[3] test1' clipdelmenu
SELECT='[3] test3' clipdelmenu

# Test multi-select deletion
primary multi1
primary multi2
settle
check_nr_clips 4

SELECT=$'[3] multi1\n[4] multi2' clipdelmenu
check_nr_clips 2

# Check selecting starts serving
xsel -pc

SELECT='[1] bar' clipmenu
settle
[[ "$(xsel -po)" == bar ]]

SELECT='[2] baz' clipmenu
settle
[[ "$(xsel -po)" == baz ]]

# Disable populates no new clips
clipctl disable
[[ "$(clipctl status)" == disabled ]]
primary wibble
primary wobble
settle
check_nr_clips 2

# Enable enables again
clipctl enable
[[ "$(clipctl status)" == enabled ]]
primary wibble
primary wobble
settle
check_nr_clips 4

# Toggle works both ways
clipctl toggle
[[ "$(clipctl status)" == disabled ]]
clipctl toggle
[[ "$(clipctl status)" == enabled ]]

# Test INCR support
set +x
printf '%.0sa' {1..9999999} | xsel -p
set -x
long_settle  # This is a big one, give it some time
len_before=$(xsel -po | wc -c)
check_nr_clips 5
SELECT='[5] aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...' \
        clipmenu
long_settle
len_after=$(xsel -po | wc -c)

# Make sure we got the whole thing
(( len_before == len_after ))

# Issue #241. Put a large clipboard selection via INCR, then put something else
# small immediately after.
xsel -bc
settle
check_nr_clips 5

# Large selection (INCR)
printf '%.0sa' {1..4001} | xsel -b
wait_nr_clips 6

# After large selection is stored, put something small that's a possible #
# partial (non-INCR)
printf a | xsel -b
settle

check_nr_clips 6

# own_selections: default does not own PRIMARY
kill "$clipmenud_pid"
wait "$clipmenud_pid" || true
pkill -x clipserve 2>/dev/null || true
CM_OWN_CLIPBOARD=1 CM_OWN_SELECTIONS=clipboard clipmenud &
clipmenud_pid=$!
settle
xsel -bc
xsel -pc
printf 'primary_stale' | xsel -p
printf 'clip_default' | xsel -b
settle
[[ "$(xsel -bo)" == clip_default ]]
primary_out=$(xsel -po 2>/dev/null || true)
[[ "$primary_out" != clip_default ]]

# own_selections: owns all listed selections
kill "$clipmenud_pid"
wait "$clipmenud_pid" || true
pkill -x clipserve 2>/dev/null || true
CM_OWN_CLIPBOARD=1 CM_OWN_SELECTIONS='clipboard primary' clipmenud &
clipmenud_pid=$!
settle
xsel -bc
xsel -pc
printf 'primary_stale' | xsel -p
printf 'own_both' | xsel -b
settle
[[ "$(xsel -bo)" == own_both ]]
[[ "$(xsel -po)" == own_both ]]

if (( _UNSHARED )); then
    umount -l /tmp
fi
