#!/bin/sh
# Copyright (c) 2017 Markus Weippert
# GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)

PARAMS="
    diff_peek/str
    force/bool//false
    original_basename=_original_basename/str
    path=dest=name/str/r
    recurse/bool//false
    src/str
    state/str
    $FILE_PARAMS
"
RESPONSE_VARS="state path appears_binary/bool"

init() {
    appears_binary=""
}

get_state() {
    local state
    state="$(ls -ld -- "$1" 2>/dev/null)" && {
            case "${state:0:1}" in
                l) state="link";;
                d) state="directory";;
                *) set -- $state; [ "$2" -gt 1 ] &&
                    state="hard" || state="file";;
            esac
            echo "$state"
        } || { echo "absent"; }
}

get_inode() {
    set -- $(ls -id -- "$1")
    echo "$1"
}

main() {
    [ -z "$diff_peek" ] || {
        appears_binary="0"
        hexdump -e '16/1 " %02x" " \n"' -c 8192 -- "$path" 2>/dev/null |
            grep -q " 00 " && appears_binary="1" || :
        exit 0
    }

    prev_state="$(get_state "$path")"
    [ -n "$state" ] ||
        case "$prev_state" in
            absent) [ -z "$recurse" ] && state="file" || state="directory";;
            *) state="$prev_state";;
        esac

    [ -n "$src" -o "$state" != "link" -a "$state" != "hard" ] || {
        [ "$state" != "link" -o -z "$follow" ] ||
            fail "src and dest are required for creating links"
        src="$(realpath "$path")"
    }

    [ "$state" = "link" -o "$state" = "absent" -o ! -d "$path" ] || {
        basename="$original_basename"
        [ -n "$basename" -o -z "$src" ] || basename="$(basename -- "$src")"
        [ -z "$basename" ] || {
            path="${path%/}/$basename"
            prev_state="$(get_state "$path")"
        }
    }

    [ -z "$recurse" -o "$state" = "directory" ] ||
        fail "recurse options requires state to be directory"

    [ "$state" = "$prev_state" ] && state_change="" || state_change="y"

    case "$state" in
        absent)
            [ ! -e "$path" ] || changed
            [ -n "$_ansible_check_mode" ] ||
                result="$(rm -rf -- "$path" 2>&1)" ||
                fail "removing failed: $result";;
        file)
            [ -z "$state_change" -o -z "$follow" -o "$prev_state" != "link" ] || {
                path="$(realpath "$path")"
                prev_state="$(get_state "$path")"
            }

            [ "$prev_state" = "file" -o "$prev_state" = "hard" ] ||
                fail "file ($path) is $prev_state, cannot continue"
            set_file_attributes "$path";;
        directory)
            [ -z "$follow" -o "$prev_state" != "link" ] || {
                path="$(realpath "$path")"
                prev_state="$(get_state "$path")"
            }

            [ -e "$path" ] || changed
            case "$prev_state" in
                absent)
                    [ -n "$_ansible_check_mode" ] || {
                        oIFS="$IFS"; IFS="/"; set -- $path; IFS="$oIFS"
                        path=""
                        for p; do
                            [ -n "$p" ] || continue
                            path="$path/$p"
                            [ ! -e "$path" ] || continue
                            result="$(mkdir -p -- "$path" 2>&1)" ||
                                fail "error creating $path: $result"
                            changed
                            set_file_attributes "$path"
                        done
                    };;
                directory) set_file_attributes "$path" "$recurse";;
                *) fail "$path already exists as a $prev_state";;
            esac;;
        link|hard)
            is_abs "$src" && abssrc="$src" || {
                [ ! -h "$path" -a -d "$path" ] && abs="$path/$src" ||
                    abssrc="$(dirname "$path")/$src"
            }
            [ -e "$abssrc" ] && {
                [ ! -d "$abssrc" -o "$state" != "hard" ] ||
                    fail "src is a directory, cannot hard link $abssrc"
            } || {
                [ "$state" != "hard" ] ||
                    fail "src file does not exist, cannot hard link $abssrc"
                [ -n "$force" ] ||
                    fail "src file does not exist, use force=yes if you want to link $abssrc"
            }

            if [ "$prev_state" = "absent" ]; then
                changed
            elif [ "$prev_state" != "$state" -a -z "$force" ]; then
                fail "refusing to convert between $prev_state and $state for $path"
            elif [ "$prev_state" = "link" ]; then
                [ "$state" = "link" -a "$(readlink "$path")" = "$src" ] ||
                    changed
            elif [ "$prev_state" = "hard" ]; then
                [ "$state" = "hard" -a "$(get_inode "$path")" = "$(get_inode "$abssrc")" ] || {
                    [ -n "$force" ] ||
                        fail "cannot link, different hard link exists at destination"
                    changed
                }
            elif [ "$prev_state" = "directory" ]; then
                [ -z "$(find "$path" -mindepth 1 -maxdepth 1 2>/dev/null)" ] ||
                    fail "the directory $path is not empty refusing to convert it"
                changed
            else
                changed
            fi

            [ -n "$_ansible_check_mode" ] || {
                [ -z "$CHANGED" ] || {
                    [ "$state" = "hard" ] && flags="" || flags="-s"
                    [ "$prev_state" = "absent" ] ||
                        result="$(rm -rf -- "$path" 2>&1)" ||
                        fail "error replacing $path: $result"
                    result="$(ln $flags -- "$src" "$path" 2>&1)" ||
                        fail "error while linking $path to $src: $result"
                }
                set_file_attributes "$path"
            };;
        touch)
            [ -n "$follow" -a "$prev_state" = "link" ] || {
                path="$(realpath "$path")"
                [ -n "$_ansible_check_mode" ] || {
                    result="$(touch -- "$path" 2>&1)" ||
                        fail "error touching $path: $result"
                    set_file_attributes "$path"
                }
                changed
            };;
    esac
}
