#!/bin/bash

function echo2 () {
    echo "$@" 1>&2
}
function err () {
    echo2 Error: "$@"
    return 1
}
function die () {
    err "$@"
    exit 1
}

function lc_assert_user_is () {
    [[ "$(whoami)" = "$1" ]] || exit 2
}
function lc_assert_user_is_not () {
    [[ "$(whoami)" != "$1" ]] || exit 2
}

function lc_include () {
    # for every included shell script, spawn sub shell,
    #   in the subshell, source the script, call (eval) the function name specified by global variable $LCI_SUBSHELL_OP
    # print the return code of the eval-ed function to stderr, but this function should always return success.

    for script in "$@"; do
        echo2 "$script -- $LCI_SUBSHELL_OP started as $(whoami), at $(pwd)"
        (
            source "$script"
            eval "$LCI_SUBSHELL_OP"
        )
        echo2 "$script -- $LCI_SUBSHELL_OP completed with status $return_code"
    done

    return 0
}

function lci_fsmap () {
    # If existing symlink is correct, do nothing & return 0 (success).
    # Otherwise, delete the existing symlink. If the existing item is file/folder, move to '$filename.linuxsync_backup'.
    # Then create a symlink.

    local content_path="$(realpath "$1")"
    local symlink_path="$2"

    if [ -L "$symlink_path" ]; then
        if [ "$(readlink "$symlink_path")" = "$content_path" ]; then
            return 0
        else
            rm "$symlink_path" || return $?
        fi
    elif [ -e "$symlink_path" ]; then
        mv "$symlink_path" "${symlink_path}.linuxsync_backup" || rm -f "$symlink_path" || return $?
    fi

    ln -s "$content_path" "$symlink_path"
}

function lc_fsmap () {
    lci_fsmap "$@"
    local result=$?
    [ $result = 0 ] || err "lc_fsmap returned error $result"
    return $result
}

function lci_state_file_append () {
    local fname="$1"
    local prefix="$2"
    local uname="$3"

    echo "${prefix}_u_$uname=1" | tee -a "$fname"
    return $?
}
function lci_state_file_contains () {
    local fname="$1"
    local prefix="$2"
    local uname="$3"

    grep "^${prefix}_u_$uname=1" "$fname"
    return $?
}
function lci_state_file_list () {
    # stdout: uname list
    local fname="$1"
    local prefix="$2"
    local ar_uname=()

    grep "^${prefix}_u_.*=1$" "$fname" | sed "s/^${prefix}_u_//" | sed "s/=1$//"
    return $?
}

function lci_overwrite_conf () {
    local fname="$1"    # (output) lc config path
    local confpath="$2" # user masterconf script path

    local newconf="masterconf=$(realpath "$confpath")"

    # Don't re-create if old config already looks good. In this case, init_done should be preserved. TODO: rename lc register to lc init, then should I remove this behavior??
    if [[ -f "$fname" ]]; then
        local oldconf="$(grep ^masterconf= "$fname" 2>/dev/null)"
        [[ "$oldconf" = "$newconf" ]] && return 0
    fi

    echo "$newconf" | tee "$fname" || err "lci_overwrite_conf: unable to create $fname" || return $?
    chmod ugo+rw "$fname"
}
function lci_conf_get_masterconf_path () {
    local fname="$1"    # lc config path
    grep ^masterconf= "$fname" | sed s/^masterconf=//
}

function lci_register () {
    local confpath="$1"
    [[ -f "$confpath" ]] || die "lci_register: $confpath not exist"

    # For new register (or register a different dir), clear all existing conf. This will trigger init again.
    lci_overwrite_conf /etc/linuxconf.conf "$confpath" || die "lci_register cannot write new conf"
}

function lci_init_if_needed () {
    local uname="$(whoami)"
    lci_state_file_contains /etc/linuxconf.conf init_done "$uname" && return 0

    # call lc_init()
    export LCI_SUBSHELL_OP=lc_init
    local masterconf="$(lci_conf_get_masterconf_path /etc/linuxconf.conf)" || die "unable to call lc_init. Cannot read masterconf path from /etc/linuxconf.conf"
    local workdir="$(dirname "$masterconf")"
    cd "$workdir" || die "unable to enter config directory: $workdir"
    lc_include "$masterconf"
    export LCI_SUBSHELL_OP=__lc_operation_undefined
    
    lci_state_file_append /etc/linuxconf.conf init_done "$uname" || die "lc_init functions succeeded, but unable to update /etc/linuxconf.conf"
}

function lci_startup_if_needed () {
    local uname="$(whoami)"

    local state_file=/tmp/.linuxconf-startup-state
    if [[ ! -f $state_file ]]; then
        touch $state_file && chmod ugo+rw $state_file || die "failed to create tmp file $state_file"
    fi

    lci_state_file_contains $state_file startup_done "$uname" && continue

    # call lc_startup()
    export LCI_SUBSHELL_OP=lc_startup
    local masterconf="$(lci_conf_get_masterconf_path /etc/linuxconf.conf)" || die "unable to call lc_init. Cannot read masterconf path from /etc/linuxconf.conf"
    local workdir="$(dirname "$masterconf")"
    cd "$workdir" || die "unable to enter config directory: $workdir"
    lc_include "$masterconf"
    export LCI_SUBSHELL_OP=__lc_operation_undefined
    
    lci_state_file_append $state_file startup_done "$uname" || die "lc_startup functions succeeded, but unable to update $state_file"
}

function lci_usage () {
    echo2 "linuxconf v$lci_version"
    echo2 "This tool helps you manage all linux customization in one centralized directory, making backup/sync much easier."
    echo2 "Usage: Run '$0 register <path/to/masterconf.sh>', then it will work out-of-box."
    exit 1
}

lci_version=0.1.0
subcommand="$1"
if [[ "$subcommand" != register ]] && [[ "$subcommand" != "" ]]; then
    [[ ! -f /etc/linuxconf.conf ]] && die "Please run '$0 register <path/to/masterconf.sh>' at least once"
fi
if [[ "$subcommand" = register ]]; then
    lci_register "$2"
    lci_init_if_needed
    lci_startup_if_needed
elif [[ "$subcommand" = _cron ]]; then
    # TODO: implement cron. with crontab or same systemd service?
    :
elif [[ "$subcommand" = _startup ]]; then
    lci_startup_if_needed
elif [[ "$subcommand" = _startup_all ]]; then
    # systemd should call this service as root, and it will spawn subprocess for all users with sudo
    [[ "$(whoami)" != root ]] && die "$0 _startup_all started as non-root. Exit because sudo might fail."
    local ar_uname=($(lci_state_file_list /etc/linuxconf.conf init_done)) || die "List all initialized users: lci_state_file_list failed"
    for uname in "${ar_uname[@]}"; do
        echo2 "Spawn subprocess '$0 _startup' as user $uname..."
        sudo -u "$uname" "$0" _startup
    done
else
    lci_usage
    exit
fi

