#!/bin/bash

if (( ${#} > 0 )) then
  set -o nounset -o noglob +o braceexpand -o posix
  posix='true'
else
  set -o nounset -o noglob +o braceexpand
  posix='false'
fi

shopt -s lastpipe
unset CDPATH TMOUT

########################################
############### variables ##############
########################################

declare -r -i number_of_background_jobs_to_create=8

declare -r -i max_time_to_wait=2
declare -r -i min_time_to_wait=1

declare -r valid_result_text="All ${number_of_background_jobs_to_create} background jobs have been waited"
declare -r wrong_result_text="!!! - Something went wrong with the waiting of the background jobs - !!!"
declare -r failed_result_text="!!! - The wait function did return a 127 return code - !!!"
declare -r wait_error_text="But also the wait command did return the following error message(s)"

declare -r red_color_ascii_code='\033[0;31m'
declare -r green_color_ascii_code='\033[1;32m'
declare -r blue_color_ascii_code='\033[1;34m'
declare -r color_reset_ascii_code='\033[0m'

########################################
####### background job functions #######
########################################

sleep_function () {
  sleep -- "$(( SRANDOM % max_time_to_wait + min_time_to_wait ))"
}

########################################
## managing background jobs functions ##
########################################

dash_n_parallel_jobs_without_array () {
  local -i background_job_return_code
  local -i loop_iterations=0

  local -a pid_set=()
  local -i process_id
  local has_failed='false'
  local this_wait_error_message
  local wait_error_message=''

  local -i i
  for (( i = 0; i < number_of_background_jobs_to_create; ++i )) do
    sleep_function &
    pid_set[${!}]=''
  done

  while
    this_wait_error_message="${ wait -p process_id -n 2>&1; }"
    background_job_return_code="${?}"
    if [[ -n ${this_wait_error_message} ]] then
      wait_error_message+="        ${this_wait_error_message//$'\n'/$'\n        '}"$'\n'
    fi
    (( background_job_return_code != 127 ))
  do
    printf '%s\n' "    ${process_id} finished ${pid_set[process_id]-again }with the following status : ${background_job_return_code}"
    if (( background_job_return_code != 0 )) then
      printf 'broken\n'
      has_failed='true'
      break
    fi
    unset pid_set[process_id]
    (( ++loop_iterations ))
  done

  printf '\n'
  if [[ ${has_failed} == 'false' ]] then
    printf '%s\n' "    Waited ${loop_iterations} times for $(( number_of_background_jobs_to_create - ${#pid_set[@]} )) background processes."
    if (( ${#pid_set[@]} > 0 )) then
      printf '%s\n' "    ${#pid_set[@]} background process(es) were never waited for."
    fi
  else
    printf '%s\n' "    ${failed_result_text}"
  fi

  if [[ -n ${wait_error_message} ]] then
    printf '\n'
    printf '%s\n' "      ${wait_error_text}"
    printf '\n'
    printf '%s\n' "${wait_error_message}"
  fi

}

########################################

dash_n_parallel_jobs_with_array () {
  local -i background_job_return_code
  local -i loop_iterations=0

  local -a pid_set=()
  local -i process_id
  local has_failed='false'
  local this_wait_error_message
  local wait_error_message=''

  local -i i
  for (( i = 0; i < number_of_background_jobs_to_create; ++i )) do
    sleep_function &
    pid_set[${!}]=''
  done

  while (( ${#pid_set[@]} > 0 )) do
    this_wait_error_message="${ wait -p process_id -n -- "${!pid_set[@]}" 2>&1; }"
    background_job_return_code="${?}"
    if [[ -n ${this_wait_error_message} ]] then
      wait_error_message+="        ${wait_error_message//$'\n'/$'\n        '}"$'\n'
    fi
    printf '%s\n' "    ${process_id} finished with the following status : ${background_job_return_code}"
    if (( background_job_return_code != 0 )) then
      printf 'broken\n'
      has_failed='true'
      break
    fi
    unset pid_set[process_id]
    (( ++loop_iterations ))
  done

  while
    this_wait_error_message="${ wait -p process_id -n 2>&1; }"
    background_job_return_code="${?}"
    if [[ -n ${this_wait_error_message} ]] then
      wait_error_message+="        ${this_wait_error_message//$'\n'/$'\n        '}"$'\n'
    fi
    (( background_job_return_code != 127 ))
  do
    printf '%s\n' "    ${process_id} finished again with the following status : ${background_job_return_code}"
    if (( background_job_return_code != 0 )) then
      printf 'broken\n'
      has_failed='true'
      break
    fi
    (( ++loop_iterations ))
  done

  printf '\n'
  if [[ ${has_failed} == 'false' ]] then
    printf '%s\n' "    Waited ${loop_iterations} times for $(( number_of_background_jobs_to_create - ${#pid_set[@]} )) background processes."
    if (( ${#pid_set[@]} > 0 )) then
      printf '%s\n' "    ${#pid_set[@]} background process(es) were never waited for."
    fi
  else
    printf '%s\n' "    ${failed_result_text}"
  fi

  if [[ -n ${wait_error_message} ]] then
    printf '\n'
    printf '%s\n' "      ${wait_error_text}"
    printf '\n'
    printf '%s\n' "${wait_error_message}"
  fi

}

standard_parallel_jobs () {
  local -i background_job_return_code
  local -i background_jobs_waited=0
  local -i loop_iterations=0

  local -i -a pid=()
  local has_failed='false'
  local this_wait_error_message
  local wait_error_message=''

  local -i i
  for (( i = 0; i < number_of_background_jobs_to_create; ++i )) do
    sleep_function &
    pid+=( "${!}" )
  done

  local -i this_pid
  local possible_again
  for this_pid in "${pid[@]}"; do
    possible_again=''
    while
      this_wait_error_message="${ wait -- "${this_pid}" 2>&1; }"
      background_job_return_code="${?}"
      if [[ -n ${this_wait_error_message} ]] then
        wait_error_message+="        ${this_wait_error_message//$'\n'/$'\n        '}"$'\n'
      fi
      (( background_job_return_code != 127 ))
    do
      printf '%s\n' "    ${this_pid} finished ${possible_again}with the following status : ${background_job_return_code}"
      if (( background_job_return_code != 0 )) then
        printf 'broken\n'
        has_failed='true'
        break
      fi
      (( ++loop_iterations ))
      possible_again="again "
    done
    if [[ ${possible_again} == 'again ' ]] then
      (( ++background_jobs_waited ))
    fi
  done

  printf '\n'
  if [[ ${has_failed} == 'false' ]] then
    printf '%s\n' "    Waited ${loop_iterations} times for ${background_jobs_waited} background processes."
    if (( background_jobs_waited < number_of_background_jobs_to_create )) then
      printf '%s\n' "    $(( number_of_background_jobs_to_create - background_jobs_waited )) background process(es) were never waited for."
    fi
  else
    printf '%s\n' "    ${failed_result_text}"
  fi

  if [[ -n ${wait_error_message} ]] then
    printf '\n'
    printf '%s\n' "      ${wait_error_text}"
    printf '\n'
    printf '%s\n' "${wait_error_message}"
  fi

}

########################################
####### ways of calling functions ######
########################################

in_shell () {
  "${1}"
}

command_substitution () {
  printf '%s\n' "$( "${1}" )"
}

subshell () {
  ( "${1}" )
}

command_substitution_and_subshell () {
  printf '%s\n' "$( subshell "${1}" )"
}

########################################
############ main function #############
########################################

main_function () {
  printf '\n'
  printf '%s\n' "All the following functions should create and wait on ${number_of_background_jobs_to_create} background jobs !"
  printf '\n'

  local -a test_case_type=( dash_n_parallel_jobs_without_array dash_n_parallel_jobs_with_array )
  if [[ ${posix} == 'true' ]] then
    test_case_type+=( standard_parallel_jobs )
  fi

  local this_test_case_type
  for this_test_case_type in "${test_case_type[@]}"; do

    printf '%b\n' "${green_color_ascii_code}###################################################${color_reset_ascii_code}"
    printf '%s\n' "${this_test_case_type//_/ }"
    printf '%b\n' "${green_color_ascii_code}###################################################${color_reset_ascii_code}"

    printf '\n'
    printf '%s\n' "  Function executed within the shell"
    printf '\n'

    in_shell "${this_test_case_type}"

    printf '\n'
    printf '%b\n' "  ${blue_color_ascii_code}##################################################${color_reset_ascii_code}"

    printf '\n'
    printf '%s\n' "  Function executed in command substitution"
    printf '\n'

    command_substitution "${this_test_case_type}"

    printf '\n'
    printf '%b\n' "  ${blue_color_ascii_code}##################################################${color_reset_ascii_code}"

    printf '\n'
    printf '%s\n' "  Function executed in an explicit subshell"
    printf '\n'

    subshell "${this_test_case_type}"

    printf '\n'
    printf '%b\n' "  ${blue_color_ascii_code}##################################################${color_reset_ascii_code}"

    printf '\n'
    printf '%s\n' "  Function executed in an explicit subshell inside of command substitution"
    printf '\n'

    command_substitution_and_subshell "${this_test_case_type}"

    printf '\n'

  done

}

########################################
############# execute main #############
########################################

main_function
