#!/bin/bash
#
# Bash library and executable that calculate how many djis (Ɉ) may be created for each contribution in euros (€) to the the foopgp association.""
#
# Copyright © 2024 Jean-Jacques Brucker <jjbrucker@foopgp.org>
#
# SPDX-License-Identifier: LGPL-3.0-only
#
# shellcheck disable=SC2013 # To read lines rather than words
# shellcheck disable=SC2034 # variable appears unused
# shellcheck disable=SC2046 # Quote this to prevent word splitting
# shellcheck disable=SC2086 # Double quote warning
# shellcheck disable=SC2128 # expanding array without index warning


if [[ "$1" == --bash-completion ]] ; then
	BL_tmp_a=$("${BASH_SOURCE[0]}" --help | sed -nE 's:^ {2,4}([a-z0-9_]+\>).*:\1:p')
	BL_tmp_o=$(for a in $BL_tmp_a ; do
		echo "[$a]=\"$(eval echo $("$BASH_SOURCE" $a --help | sed -n 's,^  ... \(--[a-z_-]\+[^ ]*\).*,\1,p' | sed 's_<_{_ ; s_|_,_g ; s_>_}_ '))\""
	done)

	eval '_bl_foopgp_completion()
	{
		local cur coptions="--help --version"
		local a actions="'$BL_tmp_a'"
		local -A aoptions=('$BL_tmp_o')

		COMPREPLY=()
		cur=${COMP_WORDS[COMP_CWORD]}

		case ${COMP_WORDS[COMP_CWORD-1]} in
			--date)
				COMPREPLY=( $(date -I --date="-1 month" ; date -I ; date -I --date="+1 month") )
				return 0 ;;
			--help|--version)
				# should exit without executing any actions, so do not complete anything
				return 0 ;;
		esac
		case $cur in
			-*)
				if a=$(grep -o "\<\('${BL_tmp_a//$'\n'/\\|}'\)\>" <<< "${COMP_WORDS[@]}") ; then
					COMPREPLY=( $(compgen -W "$coptions ${aoptions[$a]}" -- $cur ) )
					return 0
				fi
				COMPREPLY=( $(compgen -W "$coptions --stingynalty --date" -- $cur ) )
				return 0 ;;
		esac
		# complete actions if none in ${COMP_WORDS[@]}
		grep -q "\<\('${BL_tmp_a//$'\n'/\\|}'\)\>" <<< "${COMP_WORDS[@]}" || COMPREPLY=( $(compgen -W "$actions" -- $cur ) )
		return 0
	}'
	unset BL_tmp_a BL_tmp_o
	complete -F _bl_foopgp_completion "$(basename "$BASH_SOURCE")" "$BASH_SOURCE"
	return 0
fi

# If sourcing while _bl_foopgp_parseoptions is already set, execute the function and return without reloading rest of file.
if [[ "$BASH_SOURCE" != "$0" ]] && [[ "$(type -t _bl_foopgp_parseoptions)" == function ]] ; then
	_bl_foopgp_parseoptions "$@"
	return $?
fi

### Constants ###

BL_FOOPGP_NAME="$(basename "$(readlink -f "$BASH_SOURCE")" )"
BL_FOOPGP_VERSION="0.3.5-1"
BL_FOOPGP_FUNCTIONS=( $(sed -n 's,^\(bl_[^( ]*\) *().*,\1,p' "$BASH_SOURCE") )
readonly BL_FOOPGP_NAME BL_FOOPGP_VERSION BL_FOOPGP_FUNCTIONS

declare -Ar BL_FOOPGP_STINGYNALTYS=(
[2023-05]=1.00000000000000000000
[2023-06]=1.00000000000000000000
[2023-07]=1.00000000000000000000
[2023-08]=1.00000000000000000000
[2023-09]=1.00000000000000000000
[2023-10]=1.00000000000000000000
[2023-11]=1.00000000000000000000
[2023-12]=1.00000000000000000000
[2024-01]=1.00000000000000000000
[2024-02]=1.00000000000000000000
[2024-03]=1.00000000000000000000
[2024-04]=1.00000000000000000000
[2024-05]=1.00000000000000000000
[2024-06]=1.00000000000000000000
[2024-07]=1.00500000000000000000 #$ bc -l <<<"a=1 ; for(i=0;i<36;i++) { a=a+a*5/1000 ; a }"
[2024-08]=1.01002500000000000000
[2024-09]=1.01507512500000000000
[2024-10]=1.02015050062500000000
[2024-11]=1.02525125312812500000
[2024-12]=1.03037750939376562500
[2025-01]=1.03552939694073445312
[2025-02]=1.04070704392543812538
[2025-03]=1.04591057914506531600
[2025-04]=1.05114013204079064258
[2025-05]=1.05639583270099459579
[2025-06]=1.06167781186449956876
[2025-07]=1.06698620092382206660
[2025-08]=1.07232113192844117693
[2025-09]=1.07768273758808338281
[2025-10]=1.08307115127602379972
[2025-11]=1.08848650703240391871
[2025-12]=1.09392893956756593830
[2026-01]=1.09939858426540376799
[2026-02]=1.10489557718673078682
[2026-03]=1.11042005507266444075
[2026-04]=1.11597215534802776295
[2026-05]=1.12155201612476790176
[2026-06]=1.12715977620539174126
[2026-07]=1.13279557508641869996
[2026-08]=1.13845955296185079345
[2026-09]=1.14415185072666004741
[2026-10]=1.14987260998029334764
[2026-11]=1.15562197303019481437
[2026-12]=1.16140008289534578844
[2027-01]=1.16720708330982251738
[2027-02]=1.17304311872637162996
[2027-03]=1.17890833432000348810
[2027-04]=1.18480287599160350554
[2027-05]=1.19072689037156152306
[2027-06]=1.19668052482341933067
[2027-07]=1.20266392744753642732
[2027-08]=1.20867724708477410945
[2027-09]=1.21472063332019797999
[2027-10]=1.22079423648679896988
[2027-11]=1.22689820766923296472
[2027-12]=1.23303269870757912954
[2028-01]=1.23919786220111702518
[2028-02]=1.24539385151212261030
[2028-03]=1.25162082076968322335
[2028-04]=1.25787892487353163946
[2028-05]=1.26416831949789929765
[2028-06]=1.27048916109538879413
)

[[ "$BASH_SOURCE" == "$0" ]] && declare -r BL_FOOPGP_isprogram=1 || declare -r BL_FOOPGP_isprogram=0

### Others Globals ###

BL_FOOPGP_STINGYNALTY="${BL_FOOPGP_STINGYNALTY:-${BL_FOOPGP_STINGYNALTYS[$(date --iso-8601 | head -c 7)]}}"

if ((BL_FOOPGP_isprogram)) ; then
	TEXTDOMAIN="bashlibs"
	TEXTDOMAINDIR="$(dirname "$(readlink -f "$BASH_SOURCE")" )/../share/locale"
	# Require Bash 5.2. - https://www.kurokatta.org/grumble/2023/11/bash-translated-strings#fnref3
	shopt -s noexpand_translation
fi

BL_FOOPGP_chelpmsg="
"$"Calculate how many djis (Ɉ) may be created for each contribution in euros (€) to the the foopgp association.""

MAIN OPTIONS:
  -v, --verbose            "$"output important infos on stderr, like stingynalty value""
  -s, --stingynalty VALUE  "$"set STINGYNALTY"" ($(date --iso-8601 | head -c 7): $BL_FOOPGP_STINGYNALTY)
  -d, --date STRING        "$"set STINGYNALTY according to given date"" (cf. <https://www.gnu.org/software/coreutils/date>)"

### external functions ###

source "$(dirname "$BASH_SOURCE")"/bl-markdown --
source "$(dirname "$BASH_SOURCE")"/bl-interactive --
source "$(dirname "$BASH_SOURCE")"/bl-pgpid --

### internal functions ###

_bl_foopgp_parseoptions() {
	local npp=$#
	local date verbose
	for ((;$#;)) ; do
		case "$1" in
			-h|--help) printf "%s\n%s%s" "$BL_FOOPGP_usage" "$BL_FOOPGP_chelpmsg" "$BL_FOOPGP_shelpmsg" ; return 1 ;;
			-V|--version) printf "%s %s\n" "$BL_FOOPGP_NAME" "$BL_FOOPGP_VERSION" ; return 1 ;;
			-v|--verbose) verbose="1" ;;
			-s|--stingy*) shift ; BL_FOOPGP_STINGYNALTY="$1" ;;
			-d|--date) shift ; BL_FOOPGP_STINGYNALTY=${BL_FOOPGP_STINGYNALTYS[$(date --iso-8601 --date "$1"  | head -c 7)]} ;;
			--) shift ; break ;;
			-*) printf "%s: unrecognized option '%s'\n\nTry '%s --help' for more information.\n" "$BL_FOOPGP_NAME" "$1" "$BASH_SOURCE" >&2 ; return 2 ;;
			*) break ;;
		esac
		shift
	done
	BL_FOOPGP_NOPTIONS=$((npp-$#))
	if [[ "$BL_FOOPGP_STINGYNALTY" =~ ^[0-9]+\.?[0-9]*$ ]] ; then
		[[ -z "$verbose" ]] || printf "%s: Info: stingynalty=%s\n" "$BL_FOOPGP_NAME" "$BL_FOOPGP_STINGYNALTY" >&2
	else
		printf "%s: Error: stingynalty: invalid number '%s'\n" "$BL_FOOPGP_NAME" "$BL_FOOPGP_STINGYNALTY" >&2
		return 2
	fi
}

### public functions / program actions ###

bl_foopgp_g2t() {
	local name
	local tokens
	((BL_FOOPGP_isprogram)) && name="$BL_FOOPGP_NAME ${FUNCNAME:10}" || name="$FUNCNAME"
	local usage="Usage: $name [OPTIONS] VALUE ..."
	local helpmsg="
"$"Calculate how many djis (Ɉ) would be created from a contribution in euros (€).""

OPTIONS:
  -t, --tokens DJISUM   "$"Set the base of djis (Ɉ) already created (default: 0)""
  -g, --gifts EURSUM    "$"Set the base of values in EUR (€) already contributed (default: 0)""
  -s, --siprefix SYMBOL "$"Output submultiple, accepted symbol: [d|m|µ|deci|milli|micro] (cf. International System of Units).""
  -d, --date STRING     "$"Use stingynalty of given date"" (cf. <https://www.gnu.org/software/coreutils/date>).
  -p, --printsym        "$"Also print symbol, eg: ' µɈ'.""
"
	local i t=0 g=0 symbol printsym outsym
	local stingynalty=$BL_FOOPGP_STINGYNALTY
	for ((;$#;)) ; do
		case "$1" in
			-t|--tokens)
				shift ; t=0${1/,/.}
				[[ "$t" =~ ^[0-9]*\.?[0-9]*$ ]] || { printf "$FUNCNAME: Error: "$"Given parameter '%s' is not a valid number.""\n" "${t: 1}" >&2 ; return 2 ; }
				;;
			-g|--gifts)
				shift ; g=0${1/,/.}
				[[ "$g" =~ ^[0-9]*\.?[0-9]*$ ]] || { printf "$FUNCNAME: Error: "$"Given parameter '%s' is not a valid number.""\n" "${g: 1}" >&2 ; return 2 ; }
				;;
			-s|--siprefix) shift ; symbol=$1 ;;
			-d|--date) shift ; stingynalty=${BL_FOOPGP_STINGYNALTYS[$(date --iso-8601 --date "$1"  | head -c 7)]} ;;
			-p|--printsym) printsym="yes" ;;
			-h|--help) printf "%s\n%s\n" "$usage" "$helpmsg" ; return ;;
			-V|--version) echo "$FUNCNAME $BL_FOOPGP_VERSION" ; return ;;
			--) shift ; break ;;
			-*) printf "$FUNCNAME: Error: "$"Unrecognized option"" '$1'.\n\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ;;
			*) break ;;
		esac
		shift
	done
	(($#)) || { printf "$FUNCNAME: Error: "$"no VALUE given as argument.""\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ; }

	for i in "$@" ; do
		[[ "$i" =~ ^[0-9]*\.?[0-9]*$ ]] || printf "$FUNCNAME: Warning: "$"Given argument '%s' is not a valid number.""\n" "$i" >&2
		tokens=$(bc -l <<<"y=l($i+$g+1)/l(2)/$stingynalty-$t ; scale=6 ; y=y*1000 ; if (y>0) y/1000 else 0 ; scale=16 ")
		case "$symbol" in
			d|deci)
				[[ "$printsym" ]] && outsym=" dɈ" || outsym=""
				LANG=C.UTF-8 printf "%0.5f%s\n" "$(bc -l <<<"$tokens*10")" "$outsym"
				;;
			c|centi)
				[[ "$printsym" ]] && outsym=" cɈ" || outsym=""
				LANG=C.UTF-8 printf "%0.4f%s\n" "$(bc -l <<<"$tokens*100")" "$outsym"
				;;
			m|milli)
				[[ "$printsym" ]] && outsym=" mɈ" || outsym=""
				LANG=C.UTF-8 printf "%0.3f%s\n" "$(bc -l <<<"$tokens*1000")" "$outsym"
				;;
			µ|micro)
				[[ "$printsym" ]] && outsym=" µɈ" || outsym=""
				LANG=C.UTF-8 printf "%d%s\n" "$(bc -l <<<"$tokens*1000000")" "$outsym"
				;;
			*)
				[[ -z "$symbol" ]] || printf "%s: Warning: Ignoring unrecognized symbol %s.\n" "$FUNCNAME" "$symbol" >&2
				[[ "$printsym" ]] && outsym=" Ɉ" || outsym=""
				LANG=C.UTF-8 printf "%0.6f%s\n" "$tokens" "$outsym"
				;;
		esac
	done
}

bl_foopgp_t2g() {
	local name
	((BL_FOOPGP_isprogram)) && name="$BL_FOOPGP_NAME ${FUNCNAME:10}" || name="$FUNCNAME"
	local usage="Usage: $name [OPTIONS] VALUE ..."
	local helpmsg="
"$"Calculate how much euros (€) should be given to create a targeted number of djis (Ɉ).""

OPTIONS:
  -t, --tokens DJISUM  "$"Set the base of djis Ɉ already created (default: 0)""
  -g, --gifts EURSUM   "$"Set the base of values in euros € already contributed (default: 0)""
"
	local i udid ccode="€"
	local t=0 g=0
	for ((;$#;)) ; do
		case "$1" in
			-t|--tokens)
				shift ; t=0${1/,/.}
				[[ "$t" =~ ^[0-9]*\.?[0-9]*$ ]] || { printf "$FUNCNAME: Error: "$"Given parameter '%s' is not a valid number.""\n" "${t: 1}" >&2 ; return 2 ; }
				;;
			-g|--gifts)
				shift ; g=0${1/,/.}
				[[ "$g" =~ ^[0-9]*\.?[0-9]*$ ]] || { printf "$FUNCNAME: Error: "$"Given parameter '%s' is not a valid number.""\n" "${g: 1}" >&2 ; return 2 ; }
				;;
			-h|--help) printf "%s\n%s\n" "$usage" "$helpmsg" ; return ;;
			-V|--version) echo "$FUNCNAME $BL_FOOPGP_VERSION" ; return ;;
			--) shift ; break ;;
			-*) printf "$FUNCNAME: Error: "$"Unrecognized option"" '$1'.\n\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ;;
			*) break ;;
		esac
		shift
	done
	(($#)) || { printf "$FUNCNAME: Error: "$"no VALUE given as argument.""\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ; }

	for i in "$@" ; do
		[[ "$i" =~ ^[0-9]*\.?[0-9]*$ ]] || printf "$FUNCNAME: Warning: "$"Given argument '%s' is not a valid number.""\n" "$i" >&2
		bc -l <<<"z=e(l(2)*$BL_FOOPGP_STINGYNALTY*($i+$t))-1-$g ; scale=2 ; z=z*100+1 ; z/100 ; scale=16"
	done
}

bl_foopgp_giftarray() {
	local name
	((BL_FOOPGP_isprogram)) && name="$BL_FOOPGP_NAME ${FUNCNAME:10}" || name="$FUNCNAME"
	local usage="Usage: $name [OPTIONS]"
	local helpmsg="
"$"Display an array to indicate how many djis (Ɉ) gonna be created from a contribution in euros (€).""

OPTIONS:
  -t, --tokens DJISUM  "$"Set the base of djis Ɉ already created (default: 0)""
  -g, --gifts EURSUM   "$"Set the base of values in EUR € already contributed (default: 0)""
  -m, --max VALUE      "$"Set a limit in term of targeted djis (defaut: 12)""
  -M, --max-line NUM   "$"Limit number of lines (defaut: 24)""
  -H, --header-only    "$"Only display header line, and return.""
  -F, --with-header    "$"Also display header line and separation line at the beginning of array.""
"
	local max=12 maxl=24 udid header=0 onlyheader=0 ccode=EUR
	local t g i j k tg=0 gg=0
	local -A csymb=([DJI]="Ɉ" [EUR]="€") # cf. ISO-4217 + https://en.wikipedia.org/wiki/Currency_symbol
	for ((;$#;)) ; do
		case "$1" in
			-t|--tokens)
				shift ; tg=${1/,/.}
				[[ "$tg" =~ ^[0-9]*\.?[0-9]*$ ]] || { printf "$FUNCNAME: Error: "$"Given parameter '%s' is not a valid number.""\n" "$tg" >&2 ; return 2 ; }
				;;
			-g|--gifts)
				shift ; gg=${1/,/.}
				[[ "$gg" =~ ^[0-9]*\.?[0-9]*$ ]] || { printf "$FUNCNAME: Error: "$"Given parameter '%s' is not a valid number.""\n" "$gg" >&2 ; return 2 ; }
				;;
			-m|--max) shift ; max=$(($1)) || return 2 ;;
			-M|--max-line) shift ; maxl=$(($1)) || return 2 ;;
			-H|--header-only) onlyheader=1 header=1 ;;
			-F|--with-header) onlyheader=0 header=1 ;;
			-h|--help) printf "%s\n%s\n" "$usage" "$helpmsg" ; return ;;
			-V|--version) echo "$FUNCNAME $BL_FOOPGP_VERSION" ; return ;;
			--) shift ; break ;;
			-*) printf "$FUNCNAME: Error: "$"Unrecognized option"" '$1'.\n\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ;;
			*) break ;;
		esac
		shift
	done

	((!header)) || printf "| %12s | %12s |\n" "EUR" "DJI"
	((!onlyheader)) || return
	((!header)) || echo "|--------------|--------------|"

	for ((i=1,j=1,k=1; i<=max && k<=maxl; i++,k++)) ; do
		g=$(bl_foopgp_t2g --tokens "$tg" --gifts "$gg" $i)
		while (( $(echo "$j < $g" | bc -l) )) ; do
			LC_NUMERIC=POSIX printf "| %7s.00 ${csymb[EUR]} | %13s |\n" "$j" "$(bl_foopgp_g2t --printsym --siprefix centi --tokens "$tg" --gifts "$gg" $j)"
			((j*=2))
			j=$(tr '4' '5' <<<"$j") # To follow base 10 approximation : "1 2 5 10 20 50 100 ..." instead of "1 2 4 8 16 32 64 128 ..."
			((++k<maxl)) || break
		done
		LC_NUMERIC=POSIX printf "| %10s ${csymb[EUR]} | %2s00.0000 cɈ |\n" "$g" "$i"
	done #| uniq
}

bl_foopgp_update_input() {
	local name
	((BL_FOOPGP_isprogram)) && name="$BL_FOOPGP_NAME ${FUNCNAME:10}" || name="$FUNCNAME"
	local usage=$"Usage:"" $name [OPTIONS] INDEX_DIR CSV_FILES..."
	local helpmsg="
"$"Update EUR-input.log in the dji directory tree.""
"
	for ((;$#;)) ; do
		case "$1" in
			-h|--help) printf -- "$usage\n$helpmsg\n" ; return ;;
			-V|--version) echo "$FUNCNAME $BL_FOOPGP_VERSION" ; return ;;
			--) shift ; break ;;
			-*) printf "$FUNCNAME: Error: "$"Unrecognized option"" '$1'.\n\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ;;
			*) break ;;
		esac
		shift
	done

	local indexdir=$(readlink -f "$1") && [[ "$2" ]] || { printf -- "${usage}\n" >&2 ; return 2 ; }
	shift

	local outcontent outfile="EUR-input.log"
	local format etc f csvversion line date debit credit lib solde ecriture ref

	read format etc < "$indexdir/.FORMAT" || { echo "$FUNCNAME: Error: "$"Unexpected dji directory tree." >&2 ; return 2 ; }

	[[ "$format" == 1.0 ]] || { printf "$FUNCNAME: Error: "$"Unsupported format %s.""\n" "($format)" >&2 ; return 2 ; }

	for f in "$@" ; do
		[[ "$(basename $f)" =~ ^([0-9]{4}).*.csv$ ]] || { printf "$FUNCNAME: Warning: "$"Skipping invalid filename %s.""\n" "'$f'" >&2 ; continue ; }
		csvversion=${BASH_REMATCH[1]}

		while read line ; do
			if ((csvversion < 2026)) ; then
				IFS='|' read date debit credit lib solde ecriture ref etc <<<"$line"
			else
				IFS='|' read ecriture date debit credit lib solde ref etc <<<"$line"
			fi
			if [[ "$credit" =~ [0-9,]+ ]] ; then
				credit=${BASH_REMATCH[0]}
				if [[ "$ref" =~ (u(did)?4)=?($BL_PGPID_U4_REGEX) ]] ; then
					u4=${BASH_REMATCH[3]}
					dir="$indexdir/by-id/u4/${u4:0:2}/${u4:0:4}/$u4"
#					if [[ "$ref" =~ ([A-Z0-9a-z._%+-]+)@(([A-Za-z0-9.-]+)\.([A-Za-z]{2,64})) ]] ; then
#						mkdir -p "$dir/emails"
#						touch "$dir/emails/${BASH_REMATCH[0]}"
#					fi
					ref="u4=$u4"
				elif [[ "$ref" =~ ([A-Z0-9a-z._%+-]+)@(([A-Za-z0-9.-]+)\.([A-Za-z]{2,64})) ]] ; then
					ref=${BASH_REMATCH[0]}
					dir="$indexdir/by-email/${BASH_REMATCH[4]}/${BASH_REMATCH[2]}/$ref"
					ref="<${ref}>"
				else
					continue
				fi

				# Check and rewrite date
				if [[ "$date" =~ [^0-9]?([0-3][0-9])[^0-9]([0-1][0-9])[^0-9]([1-2][0-9][0-9][0-9])[^0-9]? ]] ; then
					date=$(date --iso-8601 --date "${BASH_REMATCH[3]}-${BASH_REMATCH[2]}-${BASH_REMATCH[1]}")
				elif [[ "$date" =~ [^0-9]?([1-2][0-9][0-9][0-9])[^0-9]([0-1][0-9])[^0-9]([0-3][0-9])[^0-9]? ]] ; then
					date=$(date --iso-8601 --date "${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}")
				else
					echo "Error: invalid date: $date $debit $credit $lib $solde"
					exit 1
				fi

				mkdir -p "$dir"
				touch "$dir/$outfile"
				if grep -q "^| $date | " "$dir/$outfile" ; then
					echo "Notice: operation | $date | $credit | already written for $ref" >&2
				else
					echo -en "$ref\r\033[48C << "
					echo "| $date | $(printf "% 10.02f" "$credit") |" | tee -a "$dir/$outfile"
					outcontent=$(sort "$dir/$outfile")
					echo "$outcontent" > "$dir/$outfile"
				fi
				#echo -e "\033[1A\033[40C  >> $dir/$outfile"
			fi
		done <"$f"
	done
}

bl_foopgp_update_contribs() (
	local name
	((BL_FOOPGP_isprogram)) && name="$BL_FOOPGP_NAME ${FUNCNAME:10}" || name="$FUNCNAME"
	local usage=$"Usage:"" $name [INDEX_DIR]"
	local helpmsg="
"$"Update 'contributions.md' according to new entries in 'EUR-input.log'.""
"$"If no INDEX_DIR is given, search for all 'EUR-input.log' in current subtree"" ($PWD).
"
	for ((;$#;)) ; do
		case "$1" in
			-h|--help) printf -- "$usage\n$helpmsg\n" ; return ;;
			-V|--version) echo "$FUNCNAME $BL_FOOPGP_VERSION" ; return ;;
			--) shift ; break ;;
			-*) printf "$FUNCNAME: Error: "$"Unrecognized option"" '$1'.\n\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ;;
			*) break ;;
		esac
		shift
	done

	[[ "$1" ]] || set "."

	find "$@" -name EUR-input.log | while read file ; do
		path="$(dirname "$file")"
		t=0 ; g=0 ; n=0
		mapfile -t Tcontrib < <( bl_md_arraytobash -d Econtrib < "$path/contributions.md" )
		bl_md_arraytobash date EUR <"$file" | while read line ; do
			eval "${Tcontrib[$n]}"
			eval "$line"

			entry[DJI]=$(bl_foopgp_g2t --date ${entry[date]} -t $t -g $g ${entry[EUR]/,/.})
			g=$(bc -l <<<"$g+${entry[EUR]/,/.}")
			t=$(bc -l <<<"$t+${entry[DJI]}")

			if [[ "${Econtrib[registered]}" == yes ]] ; then
				[[ "${entry[date]}" == "${Econtrib[date]}" ]] || { echo "$FUNCNAME: Critical: $path: date line $n: ${entry[date]} != ${Econtrib[date]}" >&2 ; return 2 ; }
				[[ "${entry[EUR]}" == "${Econtrib[EUR]}" ]] || { echo "$FUNCNAME: Critical: $path: EUR line $n: ${entry[EUR]} != ${Econtrib[EUR]}" >&2 ; return 2 ; }
				[[ "${entry[DJI]}" == "${Econtrib[DJI]}" ]] || { echo "$FUNCNAME: Critical: $path: DJI line $n: ${entry[DJI]} != ${Econtrib[DJI]}" >&2 ; return 2 ; }
				entry[registered]="${Econtrib[registered]}"
			else
				entry[registered]="no"
			fi

			echo "$path ; n=$((n++)) ; g=$g ; t=$t " >&2
			declare -p entry
		done | bl_md_arrayfrombash -F date EUR DJI registered > "$path/contributions.md"
		echo
	done
)

# Because there is a 'cd', use sub-shell '(...' (instead of '{...').
bl_foopgp_symlinks() (
	local name
	((BL_FOOPGP_isprogram)) && name="$BL_FOOPGP_NAME ${FUNCNAME:10}" || name="$FUNCNAME"
	local usage=$"Usage:"" $name [OPTIONS] INDEX_DIR ..."
	local helpmsg="
"$"Update symlinks in the foopgp tree"".

OPTIONS:
  -c, --clean         "$"Just remove existing symlinks (without recreating them)"".
"
	local clean
	for ((;$#;)) ; do
		case "$1" in
			-c|--clean) clean="true" ;;
			-h|--help) printf -- "$usage\n$helpmsg\n" ; return ;;
			-V|--version) echo "$FUNCNAME $BL_FOOPGP_VERSION" ; return ;;
			--) shift ; break ;;
			-*) printf "$FUNCNAME: Error: "$"Unrecognized option"" '$1'.\n\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ;;
			*) break ;;
		esac
		shift
	done
	(($#)) || { printf -- "${usage}\n" >&2 ; return 2 ; }

	[[ -d "$1/by-id" ]] || { echo "$FUNCNAME: Error: "$"Unexpected foopgp index directory"" '$1'" >&2 ; return 2 ; }
	cd "$1" || return $?

	if [[ -d "by-email" ]] ; then
		printf -- $"Deleting existing symlinks in"" 'by-email/' ...\n" >&2
		find "by-email/" -type l -delete || return $?
	fi
	[[ -z "$clean" ]] || return 0

	local file email domain tld i
	printf -- $"Creating symlinks from"" 'by-id/' ...\n" >&2
	find by-id/u4/*/*/*/emails/ -type f | while read file
	do
		email=${file##*\/}
		[[ "$email" =~ ^${BL_INTERACTIVE_EMAIL_REGEX}$ ]] || { echo "$FUNCNAME: Error: "$"Invalid email"" '$email'." >&2 ; return 1 ;}
		domain=${BASH_REMATCH[2]}
		tld=${BASH_REMATCH[4]}
		mkdir -p "by-email/${tld}/${domain}" || return $?
		ln -s "../../../${file%%/emails/*}" "by-email/${tld}/${domain}/${email}" || return $?
		printf "\r%8d %-40s" $((++i)) "$email" >&2
	done
	printf "\r\t %-40s\n" "Done !" >&2
)

# Because there is a 'cd', use sub-shell '(...' (instead of '{...').
bl_foopgp_contribs() (
	local name
	((BL_FOOPGP_isprogram)) && name="$BL_FOOPGP_NAME ${FUNCNAME:10}" || name="$FUNCNAME"
	local usage=$"Usage:"" $name [OPTIONS] INDEX_DIR ..."
	local helpmsg="
"$"Calculate total contributions and total djis (Ɉ) created by contributions.""

OPTIONS:
  -S, --stats         "$"Only output statistics.""
  -U, --users         "$"Only output by-id array.""
"
	local stats=1 users=1
	for ((;$#;)) ; do
		case "$1" in
			-s|--stats) stats=1 users=0 ;;
			-u|--users) stats=0 users=1 ;;
			-h|--help) printf -- "$usage\n$helpmsg\n" ; return ;;
			-V|--version) echo "$FUNCNAME $BL_FOOPGP_VERSION" ; return ;;
			--) shift ; break ;;
			-*) printf "$FUNCNAME: Error: "$"Unrecognized option"" '$1.\n\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ;;
			*) break ;;
		esac
		shift
	done
	(($#)) || { printf -- "${usage}\n" >&2 ; return 2 ; }

	[[ -d "$1/by-id" ]] || { echo "$FUNCNAME: Error: "$"Unexpected foopgp index directory"" '$1'" >&2 ; return 2 ; }
	cd "$1" || return $?
	readarray -t files < <(find . -type f -name contributions.md)

	echo -e "\n### $name $BL_FOOPGP_VERSION $(date -R)\n" >&2

	if ((users)) ; then
		echo "| last contrib  |                    id                   | nb contribs | total contribution | total created | certification lvl | µJ^(1/2) |"
		echo "|---------------|-----------------------------------------|-------------|--------------------|---------------|-------------------|----------|"
	fi

	totid=0
	totcontribs=0
	certifiedtotcontribs=0
	totcontribution=0
	certifiedtotcontribution=0
	totcreated=0
	certifiedtotcreated=0

	## For each path in the array.
	for file in "${files[@]}" ; do
		dir=$(dirname "$file")

		## get id from path
		[[ "$file" =~ (udid4|u4|email)/[^/]+/[^/]+/([^/]+)/contributions.md$ ]] || { echo "$0: Warning: Unexpected path $file" >&2 ; continue ; }
		[[ "${BASH_REMATCH[1]}" == u*4 ]] && id="u4=${BASH_REMATCH[2]}" || id="<${BASH_REMATCH[2]}>"
		nbcontribs=0
		totEURbyid=0
		totDJIbyid=0

		## For the moment, certification level is just the number of certification. In the futur this level should also depend on the quality of the issuers/certifiers, an on the age of certifications.
		[[ -f "$dir/certifications.log" ]] && certlvl=$(wc -l < "$dir/certifications.log") || certlvl=0

		while read line ; do
			#grep -q '\[date\]=\"2025-' <<<"$line" && continue # To Dismiss 2025 Inputs
			eval "$line"
			totEURbyid=$(bc -l <<<"${totEURbyid}+${entry[EUR]/,/.}")
			totDJIbyid=$(bc -l <<<"${totDJIbyid}+${entry[DJI]/,/.}")
			((++nbcontribs))
		done < <(bl_md_arraytobash  < "$file")

		if ((users)) ; then
			statid=$(printf "|    ${entry[date]} | %-39s | %11d | %16s € | %11s Ɉ | %17s | %8s |\n"  "${id}" "$nbcontribs" "$totEURbyid" "$totDJIbyid" "$certlvl" "$(bc -l <<<"scale=0;sqrt(${totDJIbyid/./})")")
			output+=("$statid")
		fi
		printf "\t%12s\r" "$totid" >&2
		((++totid))
		((totcontribs+=nbcontribs))
		totcontribution=$(bc -l <<<"${totcontribution}+${totEURbyid}")
		totcreated=$(bc -l <<<"${totcreated}+${totDJIbyid}")
		if ((certlvl)) ; then
			((++nbcerts))
			((certifiedtotcontribs+=nbcontribs))
			certifiedtotcontribution=$(bc -l <<<"${certifiedtotcontribution}+${totEURbyid}")
			certifiedtotcreated=$(bc -l <<<"${certifiedtotcreated}+${totDJIbyid}")
		fi
	done
	printf "\t%12s\r" " " >&2

	((!users)) || printf "%s\n" "${output[@]}" | sort -rn

	if ((stats)) ; then
		echo >&2
		echo -e "- Total members:\t$totid - Total certified members: $nbcerts / $totid"
		echo -e "- Total contribs:\t$totcontribs - Total certified contribs: $certifiedtotcontribs / $totcontribs"
		echo -e "- Total contribution:\t$totcontribution € - Total certified contribution: $certifiedtotcontribution € / $totcontribution €"
		echo -e "- Total created:\t$totcreated Ɉ - Total certified created: $certifiedtotcreated Ɉ / $totcreated Ɉ"
		echo >&2
	fi
)

### Init ###

if ((BL_FOOPGP_isprogram)) ; then
	BL_FOOPGP_usage="Usage: $BASH_SOURCE [MAIN_OPTIONS]... ACTION [OPTIONS]... [ARGUMENTS]..."
	BL_FOOPGP_shelpmsg="
  -h, --help               "$"Show help and exit.""
  -V, --version            "$"Show version and exit.""

ACTIONS:
$(for f in "${BL_FOOPGP_FUNCTIONS[@]}" ; do printf "   %-19s%s\n" "${f:10}" "$($f --help | sed -n '/^$/{n;p;q}')" ; done)

All actions support a --help option, eg:
$ $BASH_SOURCE ${BL_FOOPGP_FUNCTIONS:10} --help

$BL_FOOPGP_NAME is also bash library, see:
$ source $BASH_SOURCE --help
"
else
	BL_FOOPGP_usage="Usage: source $BASH_SOURCE [MAIN_OPTIONS]..."
	BL_FOOPGP_shelpmsg="
      --bash-completion    set completion for $BASH_SOURCE program and return (without loading anything else)

Functions:
$(for f in "${BL_FOOPGP_FUNCTIONS[@]}" ; do printf "   %-19s%s\n" "$f" "$($f --help | sed -n '/^$/{n;p;q}')" ; done)

"$"Reminder: when used as a library, all functions calls share the same environment variables, i.e. the same global options.""
"
fi

# Parse Options
_BL_FOOPGP_RETVAL=0
_bl_foopgp_parseoptions "$@" || _BL_FOOPGP_RETVAL=$?

# Do nothing else if sourced
[[ "$BASH_SOURCE" == "$0" ]] || return $_BL_FOOPGP_RETVAL

[[ "$_BL_FOOPGP_RETVAL" != "1"  ]] || exit 0
[[ "$_BL_FOOPGP_RETVAL" < 2 ]] || exit $_BL_FOOPGP_RETVAL

### Run ###
set -e
shift $BL_FOOPGP_NOPTIONS
if function=$(grep -o "\<bl_foopgp_$1\>" <<< "${BL_FOOPGP_FUNCTIONS[@]}") ; then
	shift
	$function "$@"
	exit $?
else
	printf "$BL_FOOPGP_NAME: Error: "$"Unrecognized action"" '$1'.\n\n"$"Try '%s --help' for more information"".\n" "$BASH_SOURCE" >&2
	exit 2
fi

