#!/usr/bin/bash

# This project is licensed under the MIT License (see LICENSE).

set -euo pipefail

readonly VERSION=v0.2.1

readonly config="${XDG_CONFIG_HOME:-$HOME/.config}"/snapback/snapback.conf
readonly snapshots=~/.snapback

usage() {
    echo 'usage: snapback [-hriv]

Enable with `systemctl --user enable snapback.timer`.

options:
    -h  Show help message
    -r  Remove subvolumes and backups
    -i  Initialize subvolumes in config
    -v  Show version'
}

check_dir() {
    if [[ ! -d "$1" ]]; then
        echo "$1 does not exist" >&2
        exit 1
    fi
}

check_space() {
    if [[ $(df "$1" | tail -n 1 | awk '{print $5}') == 100% ]]; then
        echo "ERROR: $1: insufficient free space" >&2
        exit 1
    fi
}

create_subvol() {
    if [[ ! -d "$1" ]]; then
        echo "Creating subvolume $1"
        btrfs subvolume create "$1"
    fi
}

is_subvol() {
    sudo btrfs subvolume show "$1" > /dev/null 2>&1
}

init_subvol() {
    if [[ -e "$1" ]]; then
        if is_subvol "$1"; then
            echo "subvolume $1 already exists"
            return
        fi
        [[ -d "$1" ]] && mv "$1" "$1".tmp
    fi

    create_subvol "$1"

    if [[ -d "$1".tmp ]]; then
        (
            shopt -s dotglob
            mv "$1".tmp/* "$1"
            rmdir "$1".tmp
        )
    fi
}

snapshot() {
    dir="$1"
    path=~/"$dir"

    check_dir "$path"
    create_subvol "$snapshots"

    today="$(date +%F)"

    mkdir -p "$snapshots/$today"

    if [[ ! -d "$snapshots/$today/$dir" ]]; then
        echo "Creating snapshot $path -> $snapshots/$today/$dir"
        btrfs subvolume snapshot -r "$path" "$snapshots/$today/$dir"
    fi
}

backup() {
    dir="$1"
    backdir="$2"

    check_space "$backdir"

    today="$(date +%F)"
    prev="$(date -d "$today -1 days" +%F)"

    check_dir "$snapshots/$today/$dir"
    create_subvol "$backdir"

    if [[ -d "$backdir/$today/$dir" ]]; then
        return
    fi

    if [[ -d "$snapshots/$prev" && -d "$backdir/$prev" ]]; then
        echo "Backing up $snapshots/$today/$dir: sending incremental stream"
        btrfs send -p "$snapshots/$prev/$dir" "$snapshots/$today/$dir" | btrfs receive "$backdir/$today"
    else
        echo "Backing up $snapshots/$today/$dir: sending subvolume"
        btrfs send "$snapshots/$today/$dir" | btrfs receive "$backdir/$today"
    fi
}

clean() {
    dir="$1"
    path="$2"
    keep="$3"

    if [[ ! "${keep-}" =~ ^[1-9]+$ ]]; then
        echo "$keep is not a valid integer" >&2
        exit 1
    fi

    for sub in $(find "$path" -maxdepth 2 -name "$dir" | head -n "-$keep"); do
        echo "Cleaning up $sub"
        btrfs property set -ts "$sub" ro false
        chmod +w -R "$sub"
        rm -rf "$sub"
    done

    for d in $(find "$path" -maxdepth 1 -empty); do
        rmdir "$d"
    done
}

while getopts ':hriv' opt; do
    case "$opt" in
        h) usage; exit ;;
        r) remove=true ;;
        i) init=true ;;
        v) echo "$VERSION"; exit ;;
        *) usage >&2; exit 2
    esac
done

if [[ ! -f "$config" ]]; then
    echo "missing config: $config" >&2
    exit 1
fi

if ${init-false}; then
    for dir in $(iniq "$config"); do
        init_subvol ~/"$dir"
    done
    exit
fi

for dir in $(iniq "$config"); do
    p="${dir//\./\\.}"

    while IFS='=' read -r k v; do
        if ! ${remove-false}; then
            snapshot "$dir"
        fi

        case "$k" in
            backup)
                if ! ${remove-false}; then
                    backup "$dir" "$v"
                fi
                ;;
            keep_snapshots)
                clean "$dir" "$snapshots" "$v"
                ;;
            keep_backups)
                if backdir="$(iniq -p "$p.backup" "$config")"; then
                    clean "$dir" "$backdir" "$v"
                fi
                ;;
        esac
    done <<< "$(iniq -q -p "$p" -f '%k=%v' "$config")"
done
