#!/usr/bin/env bash

COLOR_RED()   { test -t 1 && echo -n "$(tput setaf 1)"; }
COLOR_GREEN() { test -t 1 && echo -n "$(tput setaf 2)"; }
COLOR_YELLOW(){ test -t 1 && echo -n "$(tput setaf 3)"; }
COLOR_BLUE()  { test -t 1 && echo -n "$(tput setaf 4)"; }
COLOR_RESET() { test -t 1 && echo -n "$(tput sgr 0)"; }

function _test_git_scp()
{
	command -v rsync    > /dev/null || _error requires rsync
	command -v git      > /dev/null || _error requires git
	command -v ssh      > /dev/null || _error requires ssh
	command -v php      > /dev/null || _info  optional php
	command -v dos2unix > /dev/null || _info  optional dos2unix
}

function set_remote()
{
	remote=$1
	if [ "$(git remote | grep -c -- "^$remote$")" -eq 0 ]
	then
		COLOR_RED
		echo "Remote $remote does not exist in your git config"
		COLOR_RESET
		exit 1
	fi
}

# Check file for PHP syntax errors
# takes a list of filenames
function php_lint()
{
	local error_count=()
	for i
	do
		# check if file exists
		# check if file ends with ".php"
		test ! -f "$i" && continue
		case "$i" in
			*\.php|*\.phtml)
				if ! php -l "$i" > /dev/null; then
					error_count[${#error_count[@]}]="$i"
				fi
			;;
		esac
	done

	# syntax check fails, force exit
	test ${#error_count[@]} -gt 0 &&
		COLOR_RED &&
		echo "Error: ${#error_count[@]} PHP syntax error found" &&
		echo "${error_count[@]}" | tr " " '\n' &&
		COLOR_RESET &&
		exit 255

	return 0
}

function _dos2unix()
{
	command -v dos2unix > /dev/null && dos2unix "$@"
	return 0
}

function _sanitize()
{
	git config --get-all extras.scp.sanitize | while read -r i
	do
		case $i in
			php_lint) php_lint  "$@";; # git config --global --add extras.scp.sanitize php_lint
			dos2unix) _dos2unix "$@";; # git config --global --add extras.scp.sanitize dos2unix
		esac
	done
	return $?
}

function scp_and_stage
{
	local verbose=0 interactive=0 dry_run=0
	while :
	do
		case "$1" in
			-v|--verbose)     verbose=1; shift;;
			-i|--interactive) verbose=1; interactive=1; shift;;
			-n|--dry-run)     verbose=1; dry_run=1; shift;;
			*)                break;;
		esac
	done

	set_remote "$1"
	shift

	local refhead
	refhead="$(git rev-parse --quiet --verify "$1")"
	if [ -n "$refhead" ]
	then
		shift
		[ "$(git branch --contains "$refhead" | grep -c '\*')" -eq 0 ] &&
			_error "refhead provided is not part of current branch"
	fi

	if [ $# -ge 1 ]
	then
		list=$(git ls-files "$@")" "$(git ls-files -o "$@")
	elif [ -n "$refhead" ]
	then
		[ "$verbose" -eq 1 ] && git --no-pager diff --stat "$refhead"
		list=$(git diff "$refhead" --name-only)
	else
		[ "$verbose" -eq 1 ] && git --no-pager diff
		list=$(git diff --name-only)
	fi

	deleted=$(for i in $list; do [ -f "$i" ] || echo "$i"; done)
	   list=$(for i in $list; do [ -f "$i" ] && echo "$i"; done)

	if [ "$interactive" -eq 1 ] && [ "$dry_run" -eq 0 ] && { [ -n "$list" ] || [ -n "$deleted" ]; }
	then
		local reply
		read -r -p "Proceed with sync to $remote? [y/N] " reply
		case "$reply" in
			y|Y|yes|YES) ;;
			*) _info "Aborted, nothing synced."; exit 0;;
		esac
	fi

	local status=0

	if [ -n "$list" ]
	then
		local _TMP=${0///}
		# shellcheck disable=SC2086
		echo "$list" > "$_TMP"
		if [ "$dry_run" -eq 1 ] || _sanitize $list
		then
			_info "Pushing to $remote ($(git config "remote.$remote.url"))"
			if [ "$dry_run" -eq 1 ]
			then
				rsync -rlDvn --files-from="$_TMP" ./ "$(git config "remote.$remote.url")/"
			else
				rsync -rlDv --files-from="$_TMP" ./ "$(git config "remote.$remote.url")/" &&
				git add --force $list
			fi
			status=$?
		else
			status=1
		fi
		rm "$_TMP"
	fi

	deleted=$(for i in $deleted; do echo "$(git config "remote.$remote.url" | cut -d: -f2)/$i"; done)

	if [ -n "$deleted" ]
	then
		COLOR_RED
		echo Deleted remote files
		if [ "$dry_run" -eq 1 ]
		then
			echo "$deleted"
		else
			if ssh "$(git config "remote.$remote.url" | cut -d: -f1)" -t "rm $deleted"
			then
				echo "$deleted"
			else
				status=1
			fi
		fi
		COLOR_RESET
	fi

	return "$status"
}

function reverse_scp()
{
	local verbose=0 interactive=0 dry_run=0
	while :
	do
		case "$1" in
			-v|--verbose)     verbose=1; shift;;
			-i|--interactive) verbose=1; interactive=1; shift;;
			-n|--dry-run)     verbose=1; dry_run=1; shift;;
			*)                break;;
		esac
	done

	set_remote "$1"
	shift

	local _TMP=${0///}
	echo "$@" > "$_TMP"

	local status=0
	if [ "$verbose" -eq 1 ]
	then
		rsync -rlDvn --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./
		status=$?
	fi

	if [ "$dry_run" -eq 1 ]
	then
		rm "$_TMP"
		return "$status"
	fi

	if [ "$interactive" -eq 1 ]
	then
		local reply
		read -r -p "Proceed with copy from $remote? [y/N] " reply
		case "$reply" in
			y|Y|yes|YES) ;;
			*) _info "Aborted, nothing copied."; rm "$_TMP"; exit 0;;
		esac
	fi

	rsync -rlDv --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./ &&
		rm "$_TMP"
}

function _info()
{
	COLOR_YELLOW
	test $# -gt 0 && echo "$@"
	COLOR_RESET
}

function _usage()
{
	echo "Usage:
	git scp -h|help|?
	git scp  [options] <remote> [ref|file..]         # scp and stage your files to specified remote
	git scp  [options] <remote> [<ref>]              # show diff relative to <ref> and upload unstaged files to <remote>
	git rscp [options] <remote> [<file|directory>]   # copy <remote> files to current working directory

	OPTIONS:
	-v, --verbose      print what will be synced before syncing
	-i, --interactive  prompt for confirmation before syncing (implies --verbose)
	-n, --dry-run      show what would be synced, change nothing (implies --verbose; no staging, no remote writes/deletes)
	"

	case $1 in
		-v|verbose|--verbose) grep -A100 '^#* OPTIONS #*$' "$0" ;;
	esac
	exit
}

function _error()
{
	[ $# -eq 0 ] && _usage && exit 0

	echo
	echo "ERROR: $*"
	echo
	exit 1
}

### OPTIONS ###
case $(basename "$0") in
	git-scp)
		case $1 in
			''|-h|'?'|help|--help) shift; _test_git_scp; _usage "$@";;
			*)                   scp_and_stage "$@";;
		esac
	;;
	git-rscp)                  reverse_scp "$@";;
esac
