#!/usr/bin/env zsh

# patterns for option matching
boolpat='^(true|yes|on|1|false|no|off|0)$'
booltpat='^(true|yes|on|1)$'
boolfpat='^(false|no|off|0)$'

intpat='^[-+]?[0-9]+$'
unsignedpat='^[0-9]+$'
posunsignedpat='^[1-9][0-9]*$'
floatpat='^[-+]?[0-9]+(\.[0-9]*|e(0|[-+]?[1-9][0-9]*)|\.[0-9]*e(0|[-+]?[1-9][0-9]*$'

termcolourpat='^(black|light_black|red|light_red|green|light_green|yellow|light_yellow|blue|light_blue|magenta|light_magenta|cyan|light_cyan|white|light_white)$'

# $1 - colour
termcolourpat2escape() {
	[[ $#@ -eq 1 ]] || {echo 'badcolour'; return 1}
	[[ $1 =~ $termcolourpat ]] || {echo 'badcolour'; return 1}

	case $1 in
		'black') printf '\e[30m' ;;
		'light_black') printf '\e[1;30m' ;;
		'red') printf '\e[31m' ;;
		'light_red') printf '\e[1;31m' ;;
		'green') printf '\e[32m' ;;
		'light_green') printf '\e[1;32m' ;;
		'yellow') printf '\e[33m' ;;
		'light_yellow') printf '\e[1;33m' ;;
		'blue') printf '\e[34m' ;;
		'light_blue') printf '\e[1;34m' ;;
		'magenta') printf '\e[35m' ;;
		'light_magenta') printf '\e[1;35m' ;;
		'cyan') printf '\e[36m' ;;
		'light_cyan') printf '\e[1;36m' ;;
		'white') printf '\e[37m' ;;
		'light_white') printf '\e[1;37m' ;;
	esac
}

# $1 - message
print-error() {
	1>&2 echo "\e[1;31merror:\e[0m $1"
}

# $1 - message
abort() {
	print-error $1
	exit 1
}

# $1 - seconds
sec2time() {
	zmodload zsh/datetime || return 1

	if [[ $1 -lt 0 ]]; then
		printf "??:??:??"
		return 0
	fi

	if [[ $1 -lt 86400 ]]; then
		TZ=UTC strftime '%H:%M:%S' $1
		return 0
	fi

	if [[ $1 -ge 8640000 ]]; then
		printf "forever?"
		return 0
	fi

	printf "~%02d days" "$(($1 / 86400))"
}

# $1 - count
# $2 - message (opt)
# $3 - colour (opt)
progress-bar() {
	zmodload zsh/mathfunc zsh/datetime || return 1

	[[ $#@ -ne 0 ]] && [[ $#@ -lt 4 ]] || return 1

	local count
	[[ $1 =~ $posunsignedpat ]] && count=$1 || return 1
	[[ ! -z $3 ]] && {
		[[ $3 =~ $termcolourpat ]] || return 1
	}

	local message
	local colour

	[[ -z $2 ]] && message='waiting' || message=$2
	[[ -z $3 ]] && colour='yellow' || colour=$3

	colour=$(termcolourpat2escape $colour)

	local i=0
	local starttime=$EPOCHREALTIME
	local lasttime=$starttime
	local thistime=$starttime
	local now=$starttime
	local elapsed=0
	while true; do
		[[ $i -eq $count ]] && break

		now=$EPOCHREALTIME

		local width=$COLUMNS
		local statwidth=$(( ($#count * 2) + 35))
		local progwidth=$(($width - $statwidth - 3))
		[[ $progwidth -gt 30 || $progwidth -le 1 ]] && progwidth=30

		if [[ $i -eq 0 ]]; then
			eta='??:??:??'
		else
			eta=$(sec2time ${$(( ceil( ($count - $i) * ($elapsed / $i) - ($now - $lasttime) ) )):s/\./})
		fi

		local rt=$(sec2time ${$(( floor( ($now - $starttime) ) )):s/\./} )

		local fpart=""
		local epart=""
		local progress=$(($i * $progwidth / $count))
		repeat $progress fpart="$fpart#"
		repeat $(($progwidth - $progress)) epart="$epart-"

		printf \
			"\e[G\e[K$colour%s...\e[0m\n[%${#count}s/$count] [rt: $rt] [eta: $eta] [$fpart$epart]\e[J" \
			$message \
			$i \

		read -t 0.2 \
			&& thistime=$EPOCHREALTIME \
			&& elapsed=$(($elapsed - $lasttime + $thistime)) \
			&& lasttime=$thistime \
			&& i=$(($i + 1))

		printf "\e[F"
	done

	[[ $i -eq $count ]] \
		&& echo "\e[G\e[32m$message... done in $(sec2time ${$(( floor($elapsed) )):s/\./})\e[0m\e[J" \
		|| echo "\e[G\e[31m$message... failed!\e[0m\e[J"
}

# $1 - pid
# $2 - message (opt)
# $3 - colour (opt)
wait-anim() {
	[[ $#@ -ne 0 ]] && [[ $#@ -lt 4 ]] || return 1

	[[ $1 =~ $posunsignedpat ]] || return 1
	[[ ! -z $3 ]] && {
		[[ $3 =~ $termcolourpat ]] || return 1
	}

	local message
	local colour

	[[ -z $2 ]] && message='waiting' || message=$2
	[[ -z $3 ]] && colour='yellow' || colour=$3

	colour=$(termcolourpat2escape $colour)

	local frames
	case $(($RANDOM % 7)) in
		0) frames=('▁' '▂' '▃' '▄' '▅' '▆' '▇' '█' '▇' '▆' '▅' '▄' '▃' '▂') ;;
		1) frames=(' ' '▖' '▚' '▝' ' ' '▘' '▞' '▗') ;;
		2) frames=('╀' '╂' '╁' '┼' '┽' '┿' '┾' '┼' '╁' '╂' '╀' '┼' '┾' '┿' '┽' '┼') ;;
		3) frames=('─' '└' '│' '┌' '─' '┐' '│' '┘') ;;
		4) frames=('.' '。' '×' ':' '*' '.' '°' '♪' '×' '♫' '°' '♡') ;;
		5) frames=(' ' ' ' '░' '░' '▒' '▒' '▓' '▓' '█' '█' '▓' '▓' '▒' '▒' '░' '░') ;;
		6) frames=('O' 'o' '.' 'o') ;;
	esac

	local curframe
	coproc while true; do
		[[ -z $curframe ]] && curframe=0
		printf "\e[G\e[K|\e[1;32m${frames[$(($curframe + 1))]}\e[0m| $colour$message...\e[0m\n"
		curframe=$(( ($curframe + 1) % $#frames ))
		read -t 0.2 && break
	done

	while read -p line; do
		printf $line
	done &
	local printer=$!

	local rval
	2>/dev/null wait $1 && rval=$? && echo "done" >&p
	2>/dev/null wait $printer

	[[ $rval -eq 0 ]] && printf "\e[G\e[K\e[32m$message... done!\e[0m\n" \
		|| printf "\e[G\e[K\e[31m$message... failed!\e[0m\n"

	return $rval
}