#!/bin/bash
set -e
# TODO: Rewrite it in clisp, it has overflown bash complexity.

pname="$(basename "$0")"
gsets=()
gdirs=("$HOME/.config/gpull-dirs"  "$(dirname "$0")/gdirs")
for dir in "${gdirs[@]}" ; do
    if [ -r "$dir" ] ; then
        source "$dir" || true # should define gsets
        break
    fi
done

gvers=$(git --version|sed -e 's/git version \([0-9]*\)\..*/\1/')


# BASE="$(cd $(dirname "$0") ; cd "$(pwd -P)/../.." ; pwd -P)"

push_all_remotes=0
psets=()
goterror=0
linked_names=(gpull gpush gstat gremotes)


function gset_to_ui(){
    echo "$1" | tr '_' '-'
}

function gset_from_ui(){
    echo "$1" | tr '-' '_'
}

function print_gsets(){
    local prefix="$1"
    for gset in "${gsets[@]}" ; do
        printf "%s%s\n" "$prefix" "$(gset_to_ui "$gset")"
    done
}


function usage(){
    printf "\n"
    printf "%s usage:\n\n" "$pname"
    printf "  %s [-n|--dry-run] [-v|--verbose] links  # creates the associated symlinks.\n"  "git-group"
    printf "  %s sets                                 # display the list of gsets.\n"        "git-group"
    printf "  git-group stat [-n|--dry-run] [-v|--verbose] [gset]\n"
    printf "  git-group pull [-n|--dry-run] [-v|--verbose] [gset] [--all-remotes]\n"
    printf "  git-group push [-n|--dry-run] [-v|--verbose] [gset] [--all-remotes]\n"
    printf "  git-group remotes [-n|--dry-run] [-v|--verbose] [gset]\n"
    printf "  gstat [-n|--dry-run] [-v|--verbose] [gset]\n"
    printf "  gpull [-n|--dry-run] [-v|--verbose] [gset] [--all-remotes]\n"
    printf "  gpush [-n|--dry-run] [-v|--verbose] [gset] [--all-remotes]\n"
    printf "  gremotes [-n|--dry-run] [-v|--verbose] [gset]\n"
    printf "\n"
    printf "gset should be one of: \n"
    print_gsets '             '
    printf "\n"
}


function dry_run(){
    printf "%s\n" "$*"
}
function verbose(){
    printf "%s\n" "$*"
    "$@"
}
function silent(){
    "$@"
}
run=silent
function run(){
    "$run" "$@"
}


function error(){
    goterror=1
    printf '%s error: %s\n' "$pname" "$*"  1>&2
}

function homedir(){
    local dir="$1"
    echo "$dir"|sed -e "s:^${HOME}/:\\\${HOME}/:" -e "s:^${HOME}\$:\\\${HOME}:"
}


function clone_all_gset_repositories(){
    local gset
    for gset in "${gsets[@]}" ; do
        # shellcheck disable=SC2086
        eval printf '%s\n' \${${gset}[@]}
    done \
        | sort -u \
        | while read dir ; do
        echo '#'
        first=0
        ( cd "${dir}" >/dev/null 2>&1 ; git-show-remotes ) \
            | sed -n -e 's/ (fetch)//p' \
            | while read name url ; do
            dstdir="$(homedir "$(dirname "${dir}")")"
            if [ $first -eq 0 ] ; then
                echo mkdir -p "${dstdir}"
                echo cd $"{dstdir}"
                echo git clone -o "${name}" "${url}" "$(basename "${dir}")"
                first=1
            else
                echo cd "${dstdir}/$(basename "${dir}")"
                echo git remote add "${name}" "${url}"
            fi
        done
    done
}


function main(){
    local gset
    for arg ; do
        case "$arg" in

        (-h|--help|help)
            usage
            exit 0
            ;;

        (-n|--dry-run)
            run=dry_run
            ;;

        (-v|--verbose)
            if [ "$run" = "silent" ] ; then
                run=verbose
            fi
            ;;

        (--all-remotes)
            push_all_remotes=1
            ;;

        (-*)
            printf "Error: invalid option: %s\n" "$arg"
            usage
            exit 1
            ;;

        (link|links)
            for link in "${linked_names[@]}" ; do
                run ln -v -s -f git-groups "$(dirname "$0")/${link}"
            done
            exit 0
            ;;

        (sets|gsets)
            print_gsets ''
            exit 0
            ;;

        (clone)
            clone_all_gset_repositories
            exit 0
            ;;

        (pull)
            pname=gpull
            ;;
        (push)
            pname=gpush
            ;;
        (stat)
            pname=gstat
            ;;
        (remotes)
            pname=gremotes
            ;;

        (*)
            gset="$(gset_from_ui "$arg")"
            gotit=0
            for e in "${gsets[@]}" ; do
                if [ "$gset" = "$e" ] ; then
                    gotit=1
                    break
                fi
            done

            if [ $gotit -eq 0 ] ; then
                error "invalid git set '%s'" "$(gset_to_ui "$gset")"
            else
                psets+=( "$gset" )
            fi
            ;;
        esac
    done

    case "$pname" in
    (gpull)    op=(pull)                        ;;
    (gpush)    op=(push)                        ;;
    (gstat)    op=(status --untracked-files=no) ;;
    (gremotes) op=(remote)                      ;;
    (*)
        error "It should be named gpull, gpush or gstat\nuse:\n\t git-groups link \nto make the symlinks.\n"
        ;;
    esac

    if [ $goterror -ne 0 ] ; then
        usage
        exit 1
    fi


    if [ ${#psets} -eq 0 ] ; then
        psets=( ${gsets[0]} )
    fi

    for pset in "${psets[@]}" ; do

        pdirs=()
        # shellcheck disable=SC2016
        eval 'pdirs=( ${'"$pset"'[@]} )'

        for dir in "${pdirs[@]}" ; do
            (
                cd "$dir" && (
                    printf "\n########################################################################"
                    printf "\n### set: %s" "$pset"
                    printf "\ncd %s\n" "$(pwd)"
                    if [ $push_all_remotes -ne 0 ] ; then
                        branch="$(git branch |awk '/\*/{print $2}')"
                        case "${op[0]}" in
                        (remote)
                            trap : INT
                            run git-show-remotes |expand -16
                            trap INT
                            ;;
                        (push)
                            printf "# pushing branch \"%s\".\n" "${branch}"
                            trap : INT
                            for r in $(git remote) ; do
                                printf "%s " "$r"
                                run git "${op[@]}"                         "$r" "${branch}"
                                run git "${op[@]}" --tags --follow-tags -f "$r" "${branch}"
                            done
                            trap INT
                            ;;
                        (pull)
                            printf "# pulling branch \"%s\".\n" "${branch}"
                            trap : INT
                            git remote | while read remote ; do
                                run git "${op[@]}" "${remote}" "${branch}"
                            done
                            trap INT
                            ;;
                        *)
                            trap : INT
                            run git "${op[@]}"
                            trap INT
                            ;;
                        esac
                    else
                        case "${op[0]}" in
                        (remote)
                            trap : INT
                            run git-show-remotes |expand -16
                            trap INT
                            ;;
                        (push)
                            printf "# pushing branch \"%s\".\n" "$(git branch |awk '/*/{print $2}')"
                            trap : INT
                            run git "${op[@]}"
                            run git "${op[@]}" --tags --follow-tags -f
                            trap INT
                            ;;
                        *)
                            trap : INT
                            run git "${op[@]}"
                            trap INT
                            ;;
                        esac
                    fi
                )
            )
        done

    done

    exit 0
}

main "$@"
ViewGit