#!/bin/bash

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3 or later of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see
# https://www.gnu.org/licenses/licenses/gpl-3.0.en.html.
#
# Copyleft (C) 2025  Johann Brault-Baron <johann.brault_baron@yahoo.com>
#

N=1
if grep -q -m1 nproc <(nproc --version) ; then
   N="$(nproc --all)"
else
   echo 'nproc not found, assuming 1 core.' >&2
fi

NB=0
if [[ "$1" =~ ^[0-9]+$ ]] ; then
   NB=$(( (N + "$1" - 1) / "$1" ))
   shift
else
   NB="$N"
fi

if [ "$1" = "-b" ] ; then
   BALANCE=1
   shift
fi

if [ "$1" = "-h" ] || [ "$1" = "--help" ] || [ -z "$*" ] ; then cat << EOF
usage  $(basename "$0") <threads_per_call> [-b] <command> <options> -- <args> ..
  Splits <args> list into n sublists <sublist_1>, .., <sublist_n> and
  for each 1 <= k <= n, calls <command> <options> <sublist_k> in a subprocess.

  If -b is set, balances input size a bit. This is not worth it for already fast linters.
  This flag expects that <args> .. are valid filenames.

  The stdandard output and errors are concatenated, and the return value is the
  logical or of all return codes.

  n is computed as follows: n = nb_cores / <threads_per_call>.
  Here nb_cores is $N.
  <threads_per_call> might be omitted, in which case 1 is assumed.
EOF
exit
fi

prgopt=( "$1" )
shift
while [ -n "$*" ] && ! [ "$1" = "--" ] ; do
   prgopt+=( "$1" )
   shift
done

if [ "$1" = "--" ] ; then
   shift
fi

if [ "$#" -lt "$NB" ] ; then
   NB="$#"
fi

if [ "$NB" -le 1 ] ; then
   echo 'mono-threading.' >&2
   # shellcheck disable=SC2154
   "${prgopt[@]}" "$@"
   exit $?
else
   echo "$NB"'-threading.' >&2
fi

LIST=()
m=0
if [ -n "$BALANCE" ] ; then
   readarray -t LIST <<< "$( ls -S "$@" )"
else
    m=$(( ($# + NB - 1) / NB ))
fi

ID=()
for i in $(seq 1 "$NB"); do
   TMP=$(mktemp)
   OUT+=( "$TMP" )
   if [ -z "$BALANCE" ] ; then
      "${prgopt[@]}" "${@:1:$m}" &> "$TMP" & ID+=( "$!" )
      shift "$m"
   else
      ARGS=()
      for j in $(seq "$(( i - 1 ))" "$NB" "$(( ${#LIST[@]} - 1 ))") ; do
         ARGS+=("${LIST[j]}")
      done
      "${prgopt[@]}" "${ARGS[@]}" &> "$TMP" & ID+=( "$!" )
   fi
done

res=0
for i in $(seq 0 $((NB-1))); do
   wait "${ID[$i]}"
   cres="$?"
   if [ ! "$cres" = "0" ] ; then
      res="$cres"
      cat "${OUT[$i]}"
   fi
done
exit "$res"
