#!/bin/sh

set -ue

readonly TARGET_PATH="${HOME}"


_detect_current_script_real_directory() {
    realpath -e -- "$(dirname -- "$(readlink -e -- "${0}")")"
}

DOTFILES_ROOT="$(_detect_current_script_real_directory)"
readonly DOTFILES_ROOT

readonly SUB="${DOTFILES_ROOT}/home/user"
export SUB

. "${DOTFILES_ROOT}/TARGETS.sh"


_die() {
    printf '%s: %s\n' "$(basename "${0}")" "${1}" >&2
    exit "${2}"
}

string_get_first_char() (
    printf '%.1s' "${1}"
)

string_exclude_first_char() (
    echo "${1}" | tail -c+2
)

map_get_value() (
    map="${1}"
    key="${2}"

    echo "${map}" | grep "^${key}:" | cut -d ':' -f2
)

map_get_keys() (
    map="${1}"

    echo "${map}" | cut -d ':' -f1
)

map_key_exists() (
    map="${1}"
    key="${2}"

    map_get_keys "${map}" | grep "^${2}" 1>/dev/null
)

_link_files_in_sandbox() (
    for targetfile in "$@"
    do
        printf 'installing: %s\n' "${targetfile}"
        if [ "$(string_get_first_char "${targetfile}")" = "%" ]; then
            files="$(map_get_value "${TARGETS}" "$(string_exclude_first_char "${targetfile}")")"
            #shellcheck disable=SC2086
            _link_files_in_sandbox ${files}
        else
            if [ ! "$(dirname "${targetfile}")" = "." ]; then
                mkdir -p "${SANDBOX_PATH}/$(dirname "${targetfile}")"
            fi
            ln -sT "${SUB}/${targetfile}" "${SANDBOX_PATH}/${targetfile}"
        fi
    done
)

_compare_sandbox_to_home() (
    comparisons="$(diff -rq "${SANDBOX_PATH}" "${TARGET_PATH}")" || true
    echo "${comparisons}" | grep -vE "^Only in .+" || true
)

_merge_sandbox_to_home() (
    cp -RTnP "${SANDBOX_PATH}" "${TARGET_PATH}" || true
)

__install_from_sandbox() (
    comparisons="$(_compare_sandbox_to_home)"

    if [ -n "${comparisons}" ]; then
        echo "${comparisons}" >&2
        _die "Found conflicting files. Exiting" 1
    fi

    echo "Merging to home..."
    _merge_sandbox_to_home
    echo "Successfully installed"
)

_execute_hook_if_executable() (
    # all hooks gets SUB and SANDBOX_PATH env variables
    target="${1}"
    hook_name="${2}"

    hook_path="${DOTFILES_ROOT}/install-hooks/${target}/${hook_name}"
    if [ -x "${hook_path}" ]; then
        printf 'Executing %s for target "%s"\n' "${hook_name}" "${target}"
        "${hook_path}"
    fi
)

execute_pre_hook() (
    _execute_hook_if_executable "${1}" "pre-install"
)

recursive_execute_pre_hooks() (
    files="$(map_get_value "${TARGETS}" "${1}")"

    for targetfile in ${files}
    do
        if [ "$(string_get_first_char "${targetfile}")" = "%" ]; then
            recursive_execute_pre_hooks "$(string_exclude_first_char "${targetfile}")"
            execute_pre_hook "$(string_exclude_first_char "${targetfile}")"
        fi
    done
)

execute_post_hook() (
    _execute_hook_if_executable "${1}" "post-install"
)

recursive_execute_post_hooks() (
    files="$(map_get_value "${TARGETS}" "${1}")"

    for targetfile in ${files}
    do
        if [ "$(string_get_first_char "${targetfile}")" = "%" ]; then
            recursive_execute_post_hooks "$(string_exclude_first_char "${targetfile}")"
            execute_post_hook "$(string_exclude_first_char "${targetfile}")"
        fi
    done
)

install_target() (
    target="${1}"

    files="$(map_get_value "${TARGETS}" "${target}")"

    execute_pre_hook "${target}"
    recursive_execute_pre_hooks "${target}"
    #shellcheck disable=SC2086
    _link_files_in_sandbox ${files}
    __install_from_sandbox
    recursive_execute_post_hooks "${target}"
    execute_post_hook "${target}"
)

is_target_installed() (
    target="${1}"
    not_fully_installed=false

    files="$(map_get_value "${TARGETS}" "${target}")"

    for targetfile in ${files}
    do
        if [ "$(string_get_first_char "${targetfile}")" = "%" ]; then
            is_target_installed "$(string_exclude_first_char "${targetfile}")" || not_fully_installed=true
        else
            if [ ! -e "${TARGET_PATH}/${targetfile}" ]; then
                printf '%s not linked\n' "${targetfile}"
                not_fully_installed=true
            fi
        fi
    done
    if ${not_fully_installed}; then
        printf 'Target "%s" not fully installed\n\n' "${target}"
        return 1
    fi
    return 0
)

find_targets_that_depend_on() (
    for target in $(map_get_keys "${TARGETS}")
    do
        files="$(map_get_value "${TARGETS}" "${target}")"

        if map_key_exists "${TARGETS}" "%${1}"; then
            printf '%s\n' "${target}"
        fi
    done
)

die_if_installed_targets_depend_on() (
    for reverse_dependecy in $(find_targets_that_depend_on "${1}")
    do
        if is_target_installed "${reverse_dependecy}" >/dev/null; then
            _die "target '${reverse_dependecy}' is depends on installed target '${1}'. Exiting..." 1
        fi
    done
)

cmd_unlink() (
    for target in "$@"
    do
        die_if_installed_targets_depend_on "${target}"

        files="$(map_get_value "${TARGETS}" "${target}")"
        for targetfile in ${files}
        do
            if [ "$(string_get_first_char "${targetfile}")" = "%" ]; then
                continue
            fi

            if [ -e "${TARGET_PATH}/${targetfile}" ]; then
                unlink "${TARGET_PATH}/${targetfile}"
            fi
        done
    done
)

cmd_no_target() (
    _die "TARGET '${1}' not exists" 1
)

cmd_list() (
    map_get_keys "${TARGETS}"
)

target_exists() (
    target="${1}"
    map_key_exists "${TARGETS}" "${target}"
)

cmd_install() (
    for target in "$@"
    do
        if ! target_exists "${target}"; then
            cmd_no_target "${target}"
        fi

        SANDBOX_PATH="$(mktemp -td "${USER:-user}.dotfiles_XXXXXXX")"
        export SANDBOX_PATH
        install_target "${target}"
        rm -rf "${SANDBOX_PATH}"
    done
)

cmd_help() (
    echo "Dotfiles installation script:
Usage: ./install TARGET...
Usage: ./install unlink TARGET...
Usage: ./install check TARGET
Usage: ./install list"
)

readonly executed_command="${1}"

case "${executed_command}" in
    unlink) shift;   cmd_unlink             "$@" ;;
    check) shift;    is_target_installed    "$@" ;;
    list) shift;     cmd_list               "$@" ;;
    help) shift;     cmd_help               "$@" ;;
    *) shift;        cmd_install "${executed_command}" "$@" ;;
esac
exit 0
