#!/usr/bin/env bash

# history
# 2018-02-10 initial version
# 2020-06-10 added -a option
# ????-??-?? gzip result
# 2021-03-22 put current date into output filename
# 2021-04-06 code refactoring with more functions and input sanitisation
# 2021-12-22 prefer zstd over gzip
# 2022-01-23 don't create symlink if there is no previous file
# 2022-01-30 added xz to compressors
# 2022-12-08 some cleanup, added -C and -K options
# 2022-12-09 bug fixes

die() {
    echo "${@}" > /dev/stderr
    exit 1
}

usage() {
    cat <<-EOF
	Usage: $(basename "$0") [-o NAME] [DIR]
	A wrapper to tree, it writes the content of DIR into a text file.
	The file is named after DIR and the current date, and symlink to the
	most recent version is set. The file is automatically compressed to
	zstd, xz or gzip, whichever is available in that order.

	Options:
	  -a    access attributes: owner, group, permissions
	  -C    do not compress result file
	  -K    do not keep backup of existing file in case of overwriting
	  -o    The destination where to write the trees.
	        Default: .
	        If it is a directory: write in there
	        If it is a filename: use that as base. If not,
	        use the name of the directory as name base.
	EOF
}

test_writable() {
    touch "$1" 2>/dev/null || die "Cannot create file in $(dirname "$1")."
}

# run tree and redirect output to destination file
call_to_tree() {
    WHICH="$1"
    OUTPATH="$2-$1"
    shift 2

    declare TREE_ARGS
    TREE_ARGS+=("$@")
    TREE_ARGS+=("-o")
    TREE_ARGS+=("$DATED_PATH")

    local DATED_PATH="$OUTPATH-$TODAY"
    local EXT
    local PACK
    local CREATE_SYMLINK=no

    if [ "$COMPRESS" = "no" ]; then
        0
    elif command -v zstd > /dev/null; then
        PACK="zstd --rm -q -13"
        EXT=".zst"
    elif command -v xz > /dev/null; then
        PACK="xz"
        EXT=".xz"
    else
        PACK="gzip"
        EXT=".gz"
    fi

    ls "$OUTPATH-"* > /dev/null 2>&1 && CREATE_SYMLINK=yes

    # pack yet unpacked file
    if [ "$COMPRESS" != "no" ]; then
        [ -f "$DATED_PATH" ] && [ ! -f "$DATED_PATH$EXT" ] && $PACK "$DATED_PATH"
    fi
    # move away old file
    if [ -f "$DATED_PATH$EXT" ]; then
        [ "$KEEP_OLD" = "no" ] && rm -f "$DATED_PATH$EXT" || mv -f "$DATED_PATH$EXT" "$DATED_PATH.old$EXT"
    fi

    test_writable "$DATED_PATH"
    echo "Writing $WHICH tree to $(realpath "$DATED_PATH")$EXT"

    BEGIN_TIME=$(date +%s)
    "${TREE_ARGS[@]}"
    END_TIME=$(date +%s)

    echo >> "$DATED_PATH"
    df -BM . | awk 'NR==2 { print "Size: " $3 "   Available: " $4 "   Usage: " $5; }' >> "$DATED_PATH"
    [ "$PACK" ] && $PACK "$DATED_PATH"

    if [ "$CREATE_SYMLINK" = yes ]; then
        if [ -e "$OUTPATH" ] && [ ! -L "$OUTPATH" ]; then
            echo "Cannot set symlink, filename already taken."
        else
            echo "Setting symlink $OUTPATH -> $(basename "$DATED_PATH")"
            ln -sfn "$(basename "$DATED_PATH$EXT")" "$OUTPATH$EXT"
        fi
    fi

    echo "Reading of directory took $((END_TIME - BEGIN_TIME)) seconds."
}

# parse arguments
unset ACCESSRIGHTS
unset COMPRESS
unset KEEP_OLD

while getopts "CKaho:" OPTION
do
    case $OPTION in
        C) COMPRESS=no ;;
        K) KEEP_OLD=no ;;
        a) ACCESSRIGHTS=yes ;;
        h) usage; exit 0 ;;
        o) OUTARG="$OPTARG" ;;
    esac
done
shift $((OPTIND-1)); OPTIND=1

# input sanitisation and derivation of destination paths
DIR="${1:-$(pwd)}"
[ -d "$DIR" ] || die "The given directory does not exist."

[ "$OUTARG" ] && OUTARG="$(realpath "$OUTARG")" || OUTARG="$(pwd)"
if [ -d "$OUTARG" ]; then
    OUTDIR="$OUTARG"
    OUTNAME="$(basename "$(realpath "$DIR")")" # realpath to catch `videotree .`
    [ "$OUTNAME" = "/" ] && OUTNAME=root
else
    OUTDIR="$(dirname "$OUTARG")"
    OUTNAME="$(basename "$OUTARG")"
fi
TODAY="$(date +%F)"

cd "$DIR"

# write a very verbose file with permission and owner information
call_to_tree "detailed" "$OUTDIR/$OUTNAME"      tree -af -DFins --dirsfirst --du --timefmt "%Y-%m-%d %T" ${ACCESSRIGHTS:+-pug}

# write a smaller version with only file size information
#call_to_tree "simple"   "$OUTDIR/$OUTNAME-tree" tree -ax -n --du -h --dirsfirst

echo "Done."