#!/usr/bin/env bash

set -o errexit
set -o nounset
set -o pipefail

progname="$0"

error() {
    local line
    for line in "$@"; do
        echo "$progname: $line" 1>&2
    done

    exit 1
}

shopt -s nullglob globstar

if [[ ! -v PLOW_CACHE || -z $PLOW_CACHE ]]; then
    PLOW_CACHE=.plowcache
fi

args=$(
    getopt \
        --options f:t:Fivm: \
        --longoptions from:,to:,force,interactive,verbose,directory-mode: \
        --name "$0" \
        -- "$@"
)

eval set -- "$args"

from=${PLOW_FROM:-$PWD}
to=${PLOW_TO:-$HOME}
lnflags=()
mkdirflags=()
rmflags=()
while true; do
    case "$1" in
    -f | --from)
        from=$2
        shift 2
        ;;
    -t | --to)
        to=$2
        shift 2
        ;;
    -F | --force)
        lnflags+=(--force)
        rmflags+=(--force)
        shift
        ;;
    -i | --interactive)
        lnflags+=(--interactive)
        rmflags+=(--interactive)
        shift
        ;;
    -v | --verbose)
        lnflags+=(--verbose)
        mkdirflags+=(--verbose)
        rmflags+=(--verbose)
        shift
        ;;
    -m | --directory-mode)
        mkdirflags+=(--mode "$2")
        shift 2
        ;;
    --)
        shift
        break
        ;;
    esac
done

from=$(realpath --strip -- "$from")
to=$(realpath --strip -- "$to")

if (($# > 0)); then
    choices=("$@")
else
    choices=()
    for dir in "$from"/*/; do
        choices+=("$(basename -- "$dir")")
    done
fi

shopt -s dotglob

cache=()
if [[ -n $PLOW_CACHE ]]; then
    if [[ -r $PLOW_CACHE ]]; then
        while IFS= read -r link; do
            cache+=("$link")
        done <"$PLOW_CACHE"
    fi

    : >"$PLOW_CACHE"
fi

for choice in "${choices[@]}"; do
    prefix=$from/$choice
    for target in "$prefix"/**/*; do
        if [[ -f $target ]]; then
            link=$to${target#"$prefix"}
            parent=$(dirname -- "$link")
            mkdir --parents "${mkdirflags[@]}" -- "$parent"
            ln --symbolic "${lnflags[@]}" -- "$target" "$link"
            if [[ -n "$PLOW_CACHE" ]]; then
                echo "$link" >>"$PLOW_CACHE"
            fi
        fi
    done
done

for link in "${cache[@]}"; do
    if [[ -L "$link" && ! -f "$link" ]]; then
        rm "${rmflags[@]}" -- "$link"
    fi
done