# Usage: use_nix [...]
#
# Load environment variables from `nix-shell`.
# If you have a `default.nix` or `shell.nix` one of these will be used and
# the derived environment will be stored at ./.direnv/env-<hash>
# and symlink to it will be created at ./.direnv/default.
# Dependencies are added to the GC roots, such that the environment remains persistent.
#
# The resulting environment is cached for better performance.
#
# To trigger switch to a different environment:
# `rm -f .direnv/default`
#
# To derive a new environment:
# `rm -rf .direnv/env-$(md5sum {shell,default}.nix 2> /dev/null | cut -c -32)`
#
# To remove cache:
# `rm -f .direnv/dump-*`
#
# To remove all environments:
# `rm -rf .direnv/env-*`
#
# To remove only old environments:
# `find .direnv -name 'env-*' -and -not -name `readlink .direnv/default` -exec rm -rf {} +`
#

set -eo pipefail

use_nix() {
    # define all local variables
    local shell f env_hash dir default wd drv dump path_backup
    local files_to_watch=()

        declare opt
        declare OPTARG
        declare OPTIND

    while getopts ":s:w:" opt; do
        case "${opt}" in
            s)
                shell="${OPTARG}"
                files_to_watch=("${files_to_watch[@]}" "${shell}")
                ;;
            w)
                files_to_watch=("${files_to_watch[@]}" "${OPTARG}")
                ;;
            :)
                >&2 echo "Invalid option: $OPTARG requires an argument"
                ;;
            \?)
                >&2 echo "Invalid option: $OPTARG"
                exit 1
                ;;
        esac
    done
    shift $((OPTIND -1))

    if [[ -z "${shell}" ]]; then
        >&2 echo "ERR: no shell was given"
        exit 1
    fi

    for f in "${files_to_watch[@]}"; do
        if ! [[ -f "${f}" ]]; then
            >&2 echo "cannot watch file ${f} because it does not exist"
            exit 1
        fi
    done

    # compute the hash of all the files that makes up the development environment
    env_hash="$(hashContents "${files_to_watch[@]}")"

    dir="$(direnv_layout_dir)"
    default="${dir}/default"
    if [[ ! -L "${default}" ]] || [[ ! -d $(readlink "${default}") ]]; then
        wd="${dir}/env-${env_hash}"
        mkdir -p "${wd}"

        drv="${wd}/env.drv"
        if [[ ! -f "${drv}" ]]; then
            log_status "use nix: deriving new environment"
            IN_NIX_SHELL=1 nix-instantiate --add-root "${drv}" --indirect "${shell}" > /dev/null
            nix-store -r $(nix-store --query --references "${drv}") --add-root "${wd}/dep" --indirect > /dev/null
        fi

        rm -f "${default}"
        ln -s $(basename "${wd}") "${default}"
    fi

    drv=$(readlink "${default}/env.drv")
    dump="${dir}/dump-$(hashFile ".envrc")-$(hashFile ${drv})"

    if [[ ! -f "${dump}" ]] || [[ "${XDG_CONFIG_DIR}/direnv/direnvrc" -nt "${dump}" ]]; then
        log_status "use nix: updating cache"

        old=$(find "${dir}" -name 'dump-*')
        nix-shell --pure "${drv}" --show-trace --run "$(join_args "$direnv" dump bash)" > "${dump}"
        rm -f ${old}
    fi

    # evaluate the dump created by nix-shell earlier, but have to merge the PATH
    # with the current PATH
    # NOTE: we eval the dump here as opposed to direnv_load it because we don't
    # want to persist environment variables coming from the shell at the time of
    # the dump. See https://github.com/direnv/direnv/issues/405 for context.
    path_backup="${PATH}"
    eval $(cat "${dump}")
    export PATH="${PATH}:${path_backup}"

    for f in "${files_to_watch[@]}"; do
        watch_file "${f}"
    done
}

hashContents() {
    if has md5sum; then
        cat "${@}" | md5sum | cut -c -32
    elif has md5; then
        cat "${@}" | md5 -q
    fi
}

hashFile() {
    if has md5sum; then
        md5sum "${@}" | cut -c -32
    elif has md5; then
        md5 -q "${@}"
    fi
}

fail() {
    log_error "${@}"
    exit 1
}

validateVersion() {
    local version="$("${direnv}" version)"
    local major="$(echo "${version}" | cut -d. -f1)"
    local minor="$(echo "${version}" | cut -d. -f2)"
    local patch="$(echo "${version}" | cut -d. -f3)"

    if [[ "${major}" -gt 2 ]]; then return 0; fi
    if [[ "${major}" -eq 2 ]] && [[ "${minor}" -gt 18 ]]; then return 0; fi
    if [[ "${major}" -eq 2 ]] && [[ "${minor}" -eq 18 ]] && [[ "${patch}" -ge 2 ]]; then return 0; fi
    return 1
}

if ! validateVersion; then
    echo "This .envrc requires direnv version 2.18.2 or above."
    exit 1
fi

use_nix -s shell.nix
